From eec288af5fb49795a0c0596ead0b833eaaa24623 Mon Sep 17 00:00:00 2001 From: Sean Norwood Date: Sun, 3 Apr 2022 00:26:53 +0000 Subject: [PATCH] chore: add external js files --- tubearchivist/www/public/js/cast-videos.js | 148 ++++ tubearchivist/www/public/js/progress.js | 106 +++ tubearchivist/www/public/js/script.js | 902 +++++++++++++++++++++ 3 files changed, 1156 insertions(+) create mode 100644 tubearchivist/www/public/js/cast-videos.js create mode 100644 tubearchivist/www/public/js/progress.js create mode 100644 tubearchivist/www/public/js/script.js diff --git a/tubearchivist/www/public/js/cast-videos.js b/tubearchivist/www/public/js/cast-videos.js new file mode 100644 index 0000000..867093e --- /dev/null +++ b/tubearchivist/www/public/js/cast-videos.js @@ -0,0 +1,148 @@ +function initializeCastApi() { + cast.framework.CastContext.getInstance().setOptions({ + receiverApplicationId: chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID, // Use built in reciver app on cast device, see https://developers.google.com/cast/docs/styled_receiver if you want to be able to add a theme, splash screen or watermark. Has a $5 one time fee. + autoJoinPolicy: chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED + }); + + var player = new cast.framework.RemotePlayer(); + var playerController = new cast.framework.RemotePlayerController(player); + + // Add event listerner to check if a connection to a cast device is initiated + playerController.addEventListener( + cast.framework.RemotePlayerEventType.IS_CONNECTED_CHANGED, function() { + castConnectionChange(player) + } + ); + playerController.addEventListener( + cast.framework.RemotePlayerEventType.CURRENT_TIME_CHANGED, function() { + castVideoProgress(player) + } + ); + playerController.addEventListener( + cast.framework.RemotePlayerEventType.IS_PAUSED_CHANGED, function() { + castVideoPaused(player) + } + ); +} + + +function castConnectionChange(player) { + // If cast connection is initialized start cast + if (player.isConnected) { + // console.log("Cast Connected."); + castStart(); + } else if (!player.isConnected) { + // console.log("Cast Disconnected."); + } +} + +function castVideoProgress(player) { + var videoId = getVideoPlayerVideoId(); + if (player.mediaInfo.contentId.includes(videoId)) { + var currentTime = player.currentTime; + var duration = player.duration; + if ((currentTime % 10) <= 1.0 && currentTime != 0 && duration != 0) { // Check progress every 10 seconds or else progress is checked a few times a second + postVideoProgress(videoId, currentTime); + setProgressBar(videoId, currentTime, duration); + if (!getVideoPlayerWatchStatus()) { // Check if video is already marked as watched + if (watchedThreshold(currentTime, duration)) { + isWatched(videoId); + } + } + } + } +} + +function castVideoPaused(player) { + var videoId = getVideoPlayerVideoId(); + var currentTime = player.currentTime; + var duration = player.duration; + if (player.mediaInfo != null) { + if (player.mediaInfo.contentId.includes(videoId)) { + if (currentTime != 0 && duration != 0) { + postVideoProgress(videoId, currentTime); + } + } + } +} + +function castStart() { + var castSession = cast.framework.CastContext.getInstance().getCurrentSession(); + // Check if there is already media playing on the cast target to prevent recasting on page reload or switching to another video page + if (!castSession.getMediaSession()) { + var videoId = getVideoPlayerVideoId(); + var videoData = getVideoData(videoId); + var contentId = getURL() + videoData.data.media_url; + var contentTitle = videoData.data.title; + var contentImage = getURL() + videoData.data.vid_thumb_url; + + contentType = 'video/mp4'; // Set content type, only videos right now so it is hard coded + contentCurrentTime = getVideoPlayerCurrentTime(); // Get video's current position + contentActiveSubtitle = []; + // Check if a subtitle is turned on. + for (var i = 0; i < getVideoPlayer().textTracks.length; i++) { + if (getVideoPlayer().textTracks[i].mode == "showing") { + contentActiveSubtitle =[i + 1]; + } + } + contentSubtitles = []; + var videoSubtitles = videoData.data.subtitles; // Array of subtitles + if (typeof(videoSubtitles) != 'undefined' && videoData.config.downloads.subtitle) { + for (var i = 0; i < videoSubtitles.length; i++) { + subtitle = new chrome.cast.media.Track(i, chrome.cast.media.TrackType.TEXT); + subtitle.trackContentId = videoSubtitles[i].media_url; + subtitle.trackContentType = 'text/vtt'; + subtitle.subtype = chrome.cast.media.TextTrackType.SUBTITLES; + subtitle.name = videoSubtitles[i].name; + subtitle.language = videoSubtitles[i].lang; + subtitle.customData = null; + contentSubtitles.push(subtitle); + } + } + + mediaInfo = new chrome.cast.media.MediaInfo(contentId, contentType); // Create MediaInfo var that contains url and content type + // mediaInfo.streamType = chrome.cast.media.StreamType.BUFFERED; // Set type of stream, BUFFERED, LIVE, OTHER + mediaInfo.metadata = new chrome.cast.media.GenericMediaMetadata(); // Create metadata var and add it to MediaInfo + mediaInfo.metadata.title = contentTitle.replace("&", "&"); // Set the video title + mediaInfo.metadata.images = [new chrome.cast.Image(contentImage)]; // Set the video thumbnail + // mediaInfo.textTrackStyle = new chrome.cast.media.TextTrackStyle(); + mediaInfo.tracks = contentSubtitles; + + var request = new chrome.cast.media.LoadRequest(mediaInfo); // Create request with the previously set MediaInfo. + // request.queueData = new chrome.cast.media.QueueData(); // See https://developers.google.com/cast/docs/reference/web_sender/chrome.cast.media.QueueData for playlist support. + request.currentTime = shiftCurrentTime(contentCurrentTime); // Set video start position based on the browser video position + request.activeTrackIds = contentActiveSubtitle; // Set active subtitle based on video player + // request.autoplay = false; // Set content to auto play, true by default + castSession.loadMedia(request).then( + function() { + castSuccessful(); + }, + function() { + castFailed(errorCode); + } + ); // Send request to cast device + } +} + +function shiftCurrentTime(contentCurrentTime) { // Shift media back 3 seconds to prevent missing some of the content + if (contentCurrentTime > 5) { + return(contentCurrentTime - 3); + } else { + return(0); + } +} + +function castSuccessful() { + // console.log('Cast Successful.'); + getVideoPlayer().pause(); // Pause browser video on successful cast +} + +function castFailed(errorCode) { + console.log('Error code: ' + errorCode); +} + +window['__onGCastApiAvailable'] = function(isAvailable) { + if (isAvailable) { + initializeCastApi(); + } +} diff --git a/tubearchivist/www/public/js/progress.js b/tubearchivist/www/public/js/progress.js new file mode 100644 index 0000000..34a586e --- /dev/null +++ b/tubearchivist/www/public/js/progress.js @@ -0,0 +1,106 @@ +/** + * Handle multi channel notifications + * + */ + +checkMessages() + +// page map to notification status +const messageTypes = { + "download": ["message:download", "message:add", "message:rescan", "message:playlistscan"], + "channel": ["message:subchannel"], + "channel_id": ["message:playlistscan"], + "playlist": ["message:subplaylist"], + "setting": ["message:setting"] +} + +// start to look for messages +function checkMessages() { + var notifications = document.getElementById("notifications"); + if (notifications) { + var dataOrigin = notifications.getAttribute("data"); + getMessages(dataOrigin); + } +} + +// get messages for page on timer +function getMessages(dataOrigin) { + fetch('/progress/').then(response => { + return response.json(); + }).then(responseData => { + var messages = buildMessage(responseData, dataOrigin); + if (messages.length > 0) { + // restart itself + setTimeout(function() { + getMessages(dataOrigin); + }, 3000); + }; + }); +} + +// make div for all messages, return relevant +function buildMessage(responseData, dataOrigin) { + // filter relevan messages + var allMessages = responseData["messages"]; + var messages = allMessages.filter(function(value) { + return messageTypes[dataOrigin].includes(value["status"]) + }, dataOrigin); + // build divs + var notificationDiv = document.getElementById("notifications"); + var nots = notificationDiv.childElementCount; + notificationDiv.innerHTML = ""; + for (let i = 0; i < messages.length; i++) { + var messageData = messages[i]; + var messageStatus = messageData["status"]; + var messageBox = document.createElement("div"); + var title = document.createElement("h3"); + title.innerHTML = messageData["title"]; + var message = document.createElement("p"); + message.innerHTML = messageData["message"]; + messageBox.appendChild(title); + messageBox.appendChild(message); + messageBox.classList.add(messageData["level"], "notification"); + notificationDiv.appendChild(messageBox); + if (messageStatus === "message:download") { + checkDownloadIcons(); + }; + }; + // reload page when no more notifications + if (nots > 0 && messages.length === 0) { + location.reload(); + }; + return messages +} + +// check if download icons are needed +function checkDownloadIcons() { + var iconBox = document.getElementById("downloadControl"); + if (iconBox.childElementCount === 0) { + var downloadIcons = buildDownloadIcons(); + iconBox.appendChild(downloadIcons); + }; +} + +// add dl control icons +function buildDownloadIcons() { + var downloadIcons = document.createElement('div'); + downloadIcons.classList = 'dl-control-icons'; + // stop icon + var stopIcon = document.createElement('img'); + stopIcon.setAttribute('id', "stop-icon"); + stopIcon.setAttribute('title', "Stop Download Queue"); + stopIcon.setAttribute('src', "/static/img/icon-stop.svg"); + stopIcon.setAttribute('alt', "stop icon"); + stopIcon.setAttribute('onclick', 'stopQueue()'); + // kill icon + var killIcon = document.createElement('img'); + killIcon.setAttribute('id', "kill-icon"); + killIcon.setAttribute('title', "Kill Download Queue"); + killIcon.setAttribute('src', "/static/img/icon-close.svg"); + killIcon.setAttribute('alt', "kill icon"); + killIcon.setAttribute('onclick', 'killQueue()'); + // stich together + downloadIcons.appendChild(stopIcon); + downloadIcons.appendChild(killIcon); + return downloadIcons +} diff --git a/tubearchivist/www/public/js/script.js b/tubearchivist/www/public/js/script.js new file mode 100644 index 0000000..bc9696d --- /dev/null +++ b/tubearchivist/www/public/js/script.js @@ -0,0 +1,902 @@ + +function sortChange(sortValue) { + var payload = JSON.stringify({'sort_order': sortValue}); + sendPost(payload); + setTimeout(function(){ + location.reload(); + return false; + }, 500); +} + +// Updates video watch status when passed a video id and it's current state (ex if the video was unwatched but you want to mark it as watched you will pass "unwatched") +function updateVideoWatchStatus(input1, videoCurrentWatchStatus) { + if (videoCurrentWatchStatus) { + videoId = input1; + } else if (input1.getAttribute("data-id")) { + videoId = input1.getAttribute("data-id"); + videoCurrentWatchStatus = input1.getAttribute("data-status"); + } + + postVideoProgress(videoId, 0); // Reset video progress on watched/unwatched; + removeProgressBar(videoId); + + if (videoCurrentWatchStatus == "watched") { + var watchStatusIndicator = createWatchStatusIndicator(videoId, "unwatched"); + var payload = JSON.stringify({'un_watched': videoId}); + sendPost(payload); + } else if (videoCurrentWatchStatus == "unwatched") { + var watchStatusIndicator = createWatchStatusIndicator(videoId, "watched"); + var payload = JSON.stringify({'watched': videoId}); + sendPost(payload); + } + + var watchButtons = document.getElementsByClassName("watch-button"); + for (let i = 0; i < watchButtons.length; i++) { + if (watchButtons[i].getAttribute("data-id") == videoId) { + watchButtons[i].outerHTML = watchStatusIndicator; + } + } +} + +// Creates a watch status indicator when passed a video id and the videos watch status +function createWatchStatusIndicator(videoId, videoWatchStatus) { + if (videoWatchStatus == "watched") { + var seen = "seen"; + var title = "Mark as unwatched"; + } else if (videoWatchStatus == "unwatched") { + var seen = "unseen"; + var title = "Mark as watched"; + } + var watchStatusIndicator = `${seen}-icon`; + return watchStatusIndicator; +} + +// function isWatched(youtube_id) { +// var payload = JSON.stringify({'watched': youtube_id}); +// sendPost(payload); +// var seenIcon = document.createElement('img'); +// seenIcon.setAttribute('src', "/static/img/icon-seen.svg"); +// seenIcon.setAttribute('alt', 'seen-icon'); +// seenIcon.setAttribute('id', youtube_id); +// seenIcon.setAttribute('title', "Mark as unwatched"); +// seenIcon.setAttribute('onclick', "isUnwatched(this.id)"); +// seenIcon.classList = 'seen-icon'; +// document.getElementById(youtube_id).replaceWith(seenIcon); +// } + +// Removes the progress bar when passed a video id +function removeProgressBar(videoId) { + setProgressBar(videoId, 0, 1); +} + +function isWatchedButton(button) { + youtube_id = button.getAttribute("data-id"); + var payload = JSON.stringify({'watched': youtube_id}); + button.remove(); + sendPost(payload); + setTimeout(function(){ + location.reload(); + return false; + }, 1000); +} + +// function isUnwatched(youtube_id) { +// postVideoProgress(youtube_id, 0); // Reset video progress on unwatched; +// var payload = JSON.stringify({'un_watched': youtube_id}); +// sendPost(payload); +// var unseenIcon = document.createElement('img'); +// unseenIcon.setAttribute('src', "/static/img/icon-unseen.svg"); +// unseenIcon.setAttribute('alt', 'unseen-icon'); +// unseenIcon.setAttribute('id', youtube_id); +// unseenIcon.setAttribute('title', "Mark as watched"); +// unseenIcon.setAttribute('onclick', "isWatched(this.id)"); +// unseenIcon.classList = 'unseen-icon'; +// document.getElementById(youtube_id).replaceWith(unseenIcon); +// } + +function unsubscribe(id_unsub) { + var payload = JSON.stringify({'unsubscribe': id_unsub}); + sendPost(payload); + var message = document.createElement('span'); + message.innerText = "You are unsubscribed."; + document.getElementById(id_unsub).replaceWith(message); +} + +function subscribe(id_sub) { + var payload = JSON.stringify({'subscribe': id_sub}); + sendPost(payload); + var message = document.createElement('span'); + message.innerText = "You are subscribed."; + document.getElementById(id_sub).replaceWith(message); +} + +function changeView(image) { + var sourcePage = image.getAttribute("data-origin"); + var newView = image.getAttribute("data-value"); + var payload = JSON.stringify({'change_view': sourcePage + ":" + newView}); + sendPost(payload); + setTimeout(function(){ + location.reload(); + return false; + }, 500); +} + +function toggleCheckbox(checkbox) { + // pass checkbox id as key and checkbox.checked as value + var toggleId = checkbox.id; + var toggleVal = checkbox.checked; + var payloadDict = {}; + payloadDict[toggleId] = toggleVal; + var payload = JSON.stringify(payloadDict); + sendPost(payload); + setTimeout(function(){ + var currPage = window.location.pathname; + window.location.replace(currPage); + return false; + }, 500); +} + +// download page buttons +function rescanPending() { + var payload = JSON.stringify({'rescan_pending': true}); + animate('rescan-icon', 'rotate-img'); + sendPost(payload); + setTimeout(function(){ + checkMessages(); + }, 500); +} + +function dlPending() { + var payload = JSON.stringify({'dl_pending': true}); + animate('download-icon', 'bounce-img'); + sendPost(payload); + setTimeout(function(){ + checkMessages(); + }, 500); +} + +function toIgnore(button) { + var youtube_id = button.getAttribute('data-id'); + var payload = JSON.stringify({'ignore': youtube_id}); + sendPost(payload); + document.getElementById('dl-' + youtube_id).remove(); +} + +function downloadNow(button) { + var youtube_id = button.getAttribute('data-id'); + var payload = JSON.stringify({'dlnow': youtube_id}); + sendPost(payload); + document.getElementById(youtube_id).remove(); + setTimeout(function(){ + checkMessages(); + }, 500); +} + +function forgetIgnore(button) { + var youtube_id = button.getAttribute('data-id'); + var payload = JSON.stringify({'forgetIgnore': youtube_id}); + sendPost(payload); + document.getElementById("dl-" + youtube_id).remove(); +} + +function addSingle(button) { + var youtube_id = button.getAttribute('data-id'); + var payload = JSON.stringify({'addSingle': youtube_id}); + sendPost(payload); + document.getElementById("dl-" + youtube_id).remove(); + setTimeout(function(){ + checkMessages(); + }, 500); +} + +function deleteQueue(button) { + var to_delete = button.getAttribute('data-id'); + var payload = JSON.stringify({'deleteQueue': to_delete}); + sendPost(payload); + setTimeout(function(){ + location.reload(); + return false; + }, 1000); +} + +function stopQueue() { + var payload = JSON.stringify({'queue': 'stop'}); + sendPost(payload); + document.getElementById('stop-icon').remove(); +} + +function killQueue() { + var payload = JSON.stringify({'queue': 'kill'}); + sendPost(payload); + document.getElementById('kill-icon').remove(); +} + +// settings page buttons +function manualImport() { + var payload = JSON.stringify({'manual-import': true}); + sendPost(payload); + // clear button + var message = document.createElement('p'); + message.innerText = 'processing import'; + var toReplace = document.getElementById('manual-import'); + toReplace.innerHTML = ''; + toReplace.appendChild(message); +} + +function reEmbed() { + var payload = JSON.stringify({'re-embed': true}); + sendPost(payload); + // clear button + var message = document.createElement('p'); + message.innerText = 'processing thumbnails'; + var toReplace = document.getElementById('re-embed'); + toReplace.innerHTML = ''; + toReplace.appendChild(message); +} + +function dbBackup() { + var payload = JSON.stringify({'db-backup': true}); + sendPost(payload); + // clear button + var message = document.createElement('p'); + message.innerText = 'backing up archive'; + var toReplace = document.getElementById('db-backup'); + toReplace.innerHTML = ''; + toReplace.appendChild(message); +} + +function dbRestore(button) { + var fileName = button.getAttribute("data-id"); + var payload = JSON.stringify({'db-restore': fileName}); + sendPost(payload); + // clear backup row + var message = document.createElement('p'); + message.innerText = 'restoring from backup'; + var toReplace = document.getElementById(fileName); + toReplace.innerHTML = ''; + toReplace.appendChild(message); +} + +function fsRescan() { + var payload = JSON.stringify({'fs-rescan': true}); + sendPost(payload); + // clear button + var message = document.createElement('p'); + message.innerText = 'File system scan in progress'; + var toReplace = document.getElementById('fs-rescan'); + toReplace.innerHTML = ''; + toReplace.appendChild(message); +} + +function resetToken() { + var payload = JSON.stringify({'reset-token': true}); + sendPost(payload); + var message = document.createElement("p"); + message.innerText = "Token revoked"; + document.getElementById("text-reveal").replaceWith(message); +} + +// delete from file system +function deleteConfirm() { + to_show = document.getElementById("delete-button"); + document.getElementById("delete-item").style.display = 'none'; + to_show.style.display = "block"; +} + +function deleteVideo(button) { + var to_delete = button.getAttribute("data-id"); + var to_redirect = button.getAttribute("data-redirect"); + var payload = JSON.stringify({"delete-video": to_delete}); + sendPost(payload); + setTimeout(function(){ + var redirect = "/channel/" + to_redirect; + window.location.replace(redirect); + return false; + }, 1000); +} + +function deleteChannel(button) { + var to_delete = button.getAttribute("data-id"); + var payload = JSON.stringify({"delete-channel": to_delete}); + sendPost(payload); + setTimeout(function(){ + window.location.replace("/channel/"); + return false; + }, 1000); +} + +function deletePlaylist(button) { + var playlist_id = button.getAttribute("data-id"); + var playlist_action = button.getAttribute("data-action"); + var payload = JSON.stringify({ + "delete-playlist": { + "playlist-id": playlist_id, + "playlist-action": playlist_action + } + }); + sendPost(payload); + setTimeout(function(){ + window.location.replace("/playlist/"); + return false; + }, 1000); +} + +function cancelDelete() { + document.getElementById("delete-button").style.display = 'none'; + document.getElementById("delete-item").style.display = 'block'; +} + +// player +function createPlayer(button) { + var videoId = button.getAttribute('data-id'); + var videoData = getVideoData(videoId); + var videoProgress = getVideoProgress(videoId).position; + var videoName = videoData.data.title; + + var videoTag = createVideoTag(videoData, videoProgress); + + var playlist = ''; + var videoPlaylists = videoData.data.playlist; // Array of playlists the video is in + if (typeof(videoPlaylists) != 'undefined') { + var subbedPlaylists = getSubbedPlaylists(videoPlaylists); // Array of playlist the video is in that are subscribed + if (subbedPlaylists.length != 0) { + var playlistData = getPlaylistData(subbedPlaylists[0]); // Playlist data for first subscribed playlist + var playlistId = playlistData.playlist_id; + var playlistName = playlistData.playlist_name; + var playlist = `
${playlistName}
`; + } + } + + var videoViews = formatNumbers(videoData.data.stats.view_count); + + var channelId = videoData.data.channel.channel_id; + var channelName = videoData.data.channel.channel_name; + + removePlayer(); + // document.getElementById(videoId).outerHTML = ''; // Remove watch indicator from video info + + // If cast integration is enabled create cast button + var castButton = ''; + if (videoData.config.application.enable_cast) { + var castButton = ``; + } + + // Watched indicator + if (videoData.data.player.watched) { + var watchStatusIndicator = createWatchStatusIndicator(videoId, "watched"); + } else { + var watchStatusIndicator = createWatchStatusIndicator(videoId, "unwatched"); + } + + + var playerStats = `
views icon${videoViews}`; + if (videoData.data.stats.like_count) { + var likes = formatNumbers(videoData.data.stats.like_count); + playerStats += `|thumbs-up${likes}`; + } + if (videoData.data.stats.dislike_count && videoData.config.downloads.integrate_ryd) { + var dislikes = formatNumbers(videoData.data.stats.dislike_count); + playerStats += `|thumbs-down${dislikes}`; + } + playerStats += "
"; + + const markup = ` +
+ ${videoTag} +
+ close-icon + ${watchStatusIndicator} + ${castButton} + ${playerStats} +
+

${channelName}

+ ${playlist} +
+

${videoName}

+
+
+ `; + const divPlayer = document.getElementById("player"); + divPlayer.innerHTML = markup; +} + +// Add video tag to video page when passed a video id, function loaded on page load `video.html (115-117)` +function insertVideoTag(videoData, videoProgress) { + var videoTag = createVideoTag(videoData, videoProgress); + var videoMain = document.getElementsByClassName("video-main"); + videoMain[0].innerHTML = videoTag; +} + +// Generates a video tag with subtitles when passed videoData and videoProgress. +function createVideoTag(videoData, videoProgress) { + var videoId = videoData.data.youtube_id; + var videoUrl = videoData.data.media_url; + var videoThumbUrl = videoData.data.vid_thumb_url; + var subtitles = ''; + var videoSubtitles = videoData.data.subtitles; // Array of subtitles + if (typeof(videoSubtitles) != 'undefined' && videoData.config.downloads.subtitle) { + for (var i = 0; i < videoSubtitles.length; i++) { + let label = videoSubtitles[i].name; + if (videoSubtitles[i].source == "auto") { + label += " - auto"; + } + subtitles += ``; + } + } + + var videoTag = ` + + `; + return videoTag; +} + +// Gets video tag +function getVideoPlayer() { + var videoElement = document.getElementById("video-item"); + return videoElement; +} + +// Gets the video source tag +function getVideoPlayerVideoSource() { + var videoPlayerVideoSource = document.getElementById("video-source"); + return videoPlayerVideoSource; +} + +// Gets the current progress of the video currently in the player +function getVideoPlayerCurrentTime() { + var videoElement = getVideoPlayer(); + if (videoElement != null) { + return videoElement.currentTime; + } +} + +// Gets the video id of the video currently in the player +function getVideoPlayerVideoId() { + var videoPlayerVideoSource = getVideoPlayerVideoSource(); + if (videoPlayerVideoSource != null) { + return videoPlayerVideoSource.getAttribute("videoid"); + } +} + +// Gets the duration of the video currently in the player +function getVideoPlayerDuration() { + var videoElement = getVideoPlayer(); + if (videoElement != null) { + return videoElement.duration; + } +} + +// Gets current watch status of video based on watch button +function getVideoPlayerWatchStatus() { + var videoId = getVideoPlayerVideoId(); + var watched = false; + + var watchButtons = document.getElementsByClassName("watch-button"); + for (let i = 0; i < watchButtons.length; i++) { + if (watchButtons[i].getAttribute("data-id") == videoId && watchButtons[i].getAttribute("data-status") == "watched") { + watched = true; + } + } + return watched; +} + +// Runs on video playback, marks video as watched if video gets to 90% or higher, sends position to api +function onVideoProgress() { + var videoId = getVideoPlayerVideoId(); + var currentTime = getVideoPlayerCurrentTime(); + var duration = getVideoPlayerDuration(); + if ((currentTime % 10).toFixed(1) <= 0.2) { // Check progress every 10 seconds or else progress is checked a few times a second + postVideoProgress(videoId, currentTime); + if (!getVideoPlayerWatchStatus()) { // Check if video is already marked as watched + if (watchedThreshold(currentTime, duration)) { + updateVideoWatchStatus(videoId, "unwatched"); + } + } + } +} + +// Runs on video end, marks video as watched +function onVideoEnded() { + var videoId = getVideoPlayerVideoId(); + if (!getVideoPlayerWatchStatus()) { // Check if video is already marked as watched + updateVideoWatchStatus(videoId, "unwatched"); + } +} + +function watchedThreshold(currentTime, duration) { + var watched = false; + if (duration <= 1800){ // If video is less than 30 min + if ((currentTime / duration) >= 0.90) { // Mark as watched at 90% + var watched = true; + } + } else { // If video is more than 30 min + if (currentTime >= (duration - 120)) { // Mark as watched if there is two minutes left + var watched = true; + } + } + return watched; +} + +// Runs on video pause. Sends current position. +function onVideoPause() { + var videoId = getVideoPlayerVideoId(); + var currentTime = getVideoPlayerCurrentTime(); + postVideoProgress(videoId, currentTime); +} + +// Format numbers for frontend +function formatNumbers(number) { + var numberUnformatted = parseFloat(number); + if (numberUnformatted > 999999999) { + var numberFormatted = (numberUnformatted / 1000000000).toFixed(1).toString() + "B"; + } else if (numberUnformatted > 999999) { + var numberFormatted = (numberUnformatted / 1000000).toFixed(1).toString() + "M"; + } else if (numberUnformatted > 999) { + var numberFormatted = (numberUnformatted / 1000).toFixed(1).toString() + "K"; + } else { + var numberFormatted = numberUnformatted; + } + return numberFormatted; +} + +// Gets video data when passed video ID +function getVideoData(videoId) { + var apiEndpoint = "/api/video/" + videoId + "/"; + var videoData = apiRequest(apiEndpoint, "GET"); + return videoData; +} + +// Gets channel data when passed channel ID +function getChannelData(channelId) { + var apiEndpoint = "/api/channel/" + channelId + "/"; + var channelData = apiRequest(apiEndpoint, "GET"); + return channelData.data; +} + +// Gets playlist data when passed playlist ID +function getPlaylistData(playlistId) { + var apiEndpoint = "/api/playlist/" + playlistId + "/"; + var playlistData = apiRequest(apiEndpoint, "GET"); + return playlistData.data; +} + +// Get video progress data when passed video ID +function getVideoProgress(videoId) { + var apiEndpoint = "/api/video/" + videoId + "/progress/"; + var videoProgress = apiRequest(apiEndpoint, "GET"); + return videoProgress; +} + +// Given an array of playlist ids it returns an array of subbed playlist ids from that list +function getSubbedPlaylists(videoPlaylists) { + var subbedPlaylists = []; + for (var i = 0; i < videoPlaylists.length; i++) { + if(getPlaylistData(videoPlaylists[i]).playlist_subscribed) { + subbedPlaylists.push(videoPlaylists[i]); + } + } + return subbedPlaylists; +} + +// Send video position when given video id and progress in seconds +function postVideoProgress(videoId, videoProgress) { + var apiEndpoint = "/api/video/" + videoId + "/progress/"; + var duartion = getVideoPlayerDuration(); + if (!isNaN(videoProgress) && duartion != 'undefined') { + var data = { + "position": videoProgress + }; + if (videoProgress == 0) { + apiRequest(apiEndpoint, "DELETE"); + // console.log("Deleting Video Progress for Video ID: " + videoId + ", Progress: " + videoProgress); + } else if (!getVideoPlayerWatchStatus()) { + apiRequest(apiEndpoint, "POST", data); + // console.log("Saving Video Progress for Video ID: " + videoId + ", Progress: " + videoProgress); + } + } +} + +// Makes api requests when passed an endpoint and method ("GET", "POST", "DELETE") +function apiRequest(apiEndpoint, method, data) { + const xhttp = new XMLHttpRequest(); + var sessionToken = getCookie("sessionid"); + xhttp.open(method, apiEndpoint, false); + xhttp.setRequestHeader("X-CSRFToken", getCookie("csrftoken")); // Used for video progress POST requests + xhttp.setRequestHeader("Authorization", "Token " + sessionToken); + xhttp.setRequestHeader("Content-Type", "application/json"); + xhttp.send(JSON.stringify(data)); + return JSON.parse(xhttp.responseText); +} + +// Gets origin URL +function getURL() { + return window.location.origin; +} + +function removePlayer() { + var currentTime = getVideoPlayerCurrentTime(); + var duration = getVideoPlayerDuration(); + var videoId = getVideoPlayerVideoId(); + postVideoProgress(videoId, currentTime); + setProgressBar(videoId, currentTime, duration); + var playerElement = document.getElementById('player'); + if (playerElement.hasChildNodes()) { + var youtubeId = playerElement.childNodes[1].getAttribute("data-id"); + var playedStatus = document.createDocumentFragment(); + var playedBox = document.getElementById(youtubeId); + if (playedBox) { + playedStatus.appendChild(playedBox); + } + playerElement.innerHTML = ''; + // append played status + var videoInfo = document.getElementById('video-info-' + youtubeId); + videoInfo.insertBefore(playedStatus, videoInfo.firstChild); + } +} + +// Sets the progress bar when passed a video id, video progress and video duration +function setProgressBar(videoId, currentTime, duration) { + var progressBarWidth = (currentTime / duration) * 100 + "%"; + var progressBars = document.getElementsByClassName("video-progress-bar"); + for (let i = 0; i < progressBars.length; i++) { + if (progressBars[i].id == "progress-" + videoId) { + if (!getVideoPlayerWatchStatus()) { + progressBars[i].style.width = progressBarWidth; + } else { + progressBars[i].style.width = "0%"; + } + } + } + + // progressBar = document.getElementById("progress-" + videoId); + + +} + +// multi search form +function searchMulti(query) { + if (query.length > 1) { + var payload = JSON.stringify({'multi_search': query}); + var http = new XMLHttpRequest(); + http.onreadystatechange = function() { + if (http.readyState === 4) { + allResults = JSON.parse(http.response).results; + populateMultiSearchResults(allResults); + } + }; + http.open("POST", "/process/", true); + http.setRequestHeader("X-CSRFToken", getCookie("csrftoken")); + http.setRequestHeader("Content-type", "application/json"); + http.send(payload); + } +} + +function getViewDefaults(view) { + var defaultView = document.getElementById("id_" + view).value; + return defaultView; +} + +function populateMultiSearchResults(allResults) { + // videos + var defaultVideo = getViewDefaults("home"); + var allVideos = allResults.video_results; + var videoBox = document.getElementById("video-results"); + videoBox.innerHTML = ""; + for (let index = 0; index < allVideos.length; index++) { + const video = allVideos[index].source; + const videoDiv = createVideo(video, defaultVideo); + videoBox.appendChild(videoDiv); + } + // channels + var defaultChannel = getViewDefaults("channel"); + var allChannels = allResults.channel_results; + var channelBox = document.getElementById("channel-results"); + channelBox.innerHTML = ""; + for (let index = 0; index < allChannels.length; index++) { + const channel = allChannels[index].source; + const channelDiv = createChannel(channel, defaultChannel); + channelBox.appendChild(channelDiv); + } + // playlists + var defaultPlaylist = getViewDefaults("playlist"); + var allPlaylists = allResults.playlist_results; + var playlistBox = document.getElementById("playlist-results"); + playlistBox.innerHTML = ""; + for (let index = 0; index < allPlaylists.length; index++) { + const playlist = allPlaylists[index].source; + const playlistDiv = createPlaylist(playlist, defaultPlaylist); + playlistBox.appendChild(playlistDiv); + } +} + + +function createVideo(video, viewStyle) { + // create video item div from template + const videoId = video.youtube_id; + const mediaUrl = video.media_url; + const thumbUrl = "/cache/" + video.vid_thumb_url; + const videoTitle = video.title; + const videoPublished = video.published; + const videoDuration = video.player.duration_str; + if (video.player.watched) { + var watchStatusIndicator = createWatchStatusIndicator(videoId, "watched"); + } else { + var watchStatusIndicator = createWatchStatusIndicator(videoId, "unwatched"); + }; + const channelId = video.channel.channel_id; + const channelName = video.channel.channel_name; + // build markup + const markup = ` + +
+
+ video-thumb +
+
+ play-icon +
+
+
+
+
+ ${watchStatusIndicator} + ${videoPublished} | ${videoDuration} +
+
+

${channelName}

+

${videoTitle}

+
+
+ `; + const videoDiv = document.createElement("div"); + videoDiv.setAttribute("class", "video-item " + viewStyle); + videoDiv.innerHTML = markup; + return videoDiv; +} + + +function createChannel(channel, viewStyle) { + // create channel item div from template + const channelId = channel.channel_id; + const channelName = channel.channel_name; + const channelSubs = channel.channel_subs; + const channelLastRefresh = channel.channel_last_refresh; + if (channel.channel_subscribed) { + var button = ``; + } else { + var button = ``; + } + // build markup + const markup = ` +
+ + ${channelId}-banner + +
+
+
+
+ + channel-thumb + +
+
+

${channelName}

+

Subscribers: ${channelSubs}

+
+
+
+
+

Last refreshed: ${channelLastRefresh}

+ ${button} +
+
+
+ `; + const channelDiv = document.createElement("div"); + channelDiv.setAttribute("class", "channel-item " + viewStyle); + channelDiv.innerHTML = markup; + return channelDiv; +} + +function createPlaylist(playlist, viewStyle) { + // create playlist item div from template + const playlistId = playlist.playlist_id; + const playlistName = playlist.playlist_name; + const playlistChannelId = playlist.playlist_channel_id; + const playlistChannel = playlist.playlist_channel; + const playlistLastRefresh = playlist.playlist_last_refresh; + if (playlist.playlist_subscribed) { + var button = ``; + } else { + var button = ``; + } + const markup = ` +
+ + ${playlistId}-thumbnail + +
+
+

${playlistChannel}

+

${playlistName}

+

Last refreshed: ${playlistLastRefresh}

+ ${button} +
+ `; + const playlistDiv = document.createElement("div"); + playlistDiv.setAttribute("class", "playlist-item " + viewStyle); + playlistDiv.innerHTML = markup; + return playlistDiv; +} + + +// generic +function sendPost(payload) { + var http = new XMLHttpRequest(); + http.open("POST", "/process/", true); + http.setRequestHeader("X-CSRFToken", getCookie("csrftoken")); + http.setRequestHeader("Content-type", "application/json"); + http.send(payload); +} + + +function getCookie(c_name) { + if (document.cookie.length > 0) { + c_start = document.cookie.indexOf(c_name + "="); + if (c_start != -1) { + c_start = c_start + c_name.length + 1; + c_end = document.cookie.indexOf(";", c_start); + if (c_end == -1) c_end = document.cookie.length; + return unescape(document.cookie.substring(c_start,c_end)); + } + } + return ""; +} + + +// animations +function textReveal() { + var textBox = document.getElementById('text-reveal'); + var button = document.getElementById('text-reveal-button'); + var textBoxHeight = textBox.style.height; + if (textBoxHeight === 'unset') { + textBox.style.height = '0px'; + button.innerText = 'Show'; + } else { + textBox.style.height = 'unset'; + button.innerText = 'Hide'; + } +} + +function showForm() { + var formElement = document.getElementById('hidden-form'); + var displayStyle = formElement.style.display; + if (displayStyle === "") { + formElement.style.display = 'block'; + } else { + formElement.style.display = ""; + } + animate('animate-icon', 'pulse-img'); +} + +function showOverwrite() { + var overwriteDiv = document.getElementById("overwrite-form"); + if (overwriteDiv.classList.contains("hidden-overwrite")) { + overwriteDiv.classList.remove("hidden-overwrite"); + } else { + overwriteDiv.classList.add("hidden-overwrite") + } +} + +function animate(elementId, animationClass) { + var toAnimate = document.getElementById(elementId); + if (toAnimate.className !== animationClass) { + toAnimate.className = animationClass; + } else { + toAnimate.classList.remove(animationClass); + } +}