'use strict'; /* globals checkMessages */ function sortChange(sortValue) { let payload = JSON.stringify({ sort_order: sortValue }); sendPost(payload); setTimeout(function () { location.reload(); }, 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) { let videoId; 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); let watchStatusIndicator, payload; if (videoCurrentWatchStatus === 'watched') { watchStatusIndicator = createWatchStatusIndicator(videoId, 'unwatched'); payload = JSON.stringify({ un_watched: videoId }); sendPost(payload); } else if (videoCurrentWatchStatus === 'unwatched') { watchStatusIndicator = createWatchStatusIndicator(videoId, 'watched'); payload = JSON.stringify({ watched: videoId }); sendPost(payload); } let 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) { let seen, title; if (videoWatchStatus === 'watched') { seen = 'seen'; title = 'Mark as unwatched'; } else if (videoWatchStatus === 'unwatched') { seen = 'unseen'; title = 'Mark as watched'; } let 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) { let youtube_id = button.getAttribute('data-id'); let payload = JSON.stringify({ watched: youtube_id }); button.remove(); sendPost(payload); setTimeout(function () { location.reload(); }, 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) { let payload = JSON.stringify({ unsubscribe: id_unsub }); sendPost(payload); let message = document.createElement('span'); message.innerText = 'You are unsubscribed.'; document.getElementById(id_unsub).replaceWith(message); } function subscribe(id_sub) { let payload = JSON.stringify({ subscribe: id_sub }); sendPost(payload); let message = document.createElement('span'); message.innerText = 'You are subscribed.'; document.getElementById(id_sub).replaceWith(message); } function changeView(image) { let sourcePage = image.getAttribute('data-origin'); let newView = image.getAttribute('data-value'); let payload = JSON.stringify({ change_view: sourcePage + ':' + newView }); sendPost(payload); setTimeout(function () { location.reload(); }, 500); } function changeGridItems(image) { let operator = image.getAttribute('data-value'); let payload = JSON.stringify({ change_grid: operator }); sendPost(payload); setTimeout(function () { location.reload(); }, 500); } function toggleCheckbox(checkbox) { // pass checkbox id as key and checkbox.checked as value let toggleId = checkbox.id; let toggleVal = checkbox.checked; let payloadDict = {}; payloadDict[toggleId] = toggleVal; let payload = JSON.stringify(payloadDict); sendPost(payload); setTimeout(function () { let currPage = window.location.pathname + window.location.search; window.location.replace(currPage); }, 500); } // download page buttons function rescanPending() { let payload = JSON.stringify({ rescan_pending: true }); animate('rescan-icon', 'rotate-img'); sendPost(payload); setTimeout(function () { checkMessages(); }, 500); } function dlPending() { let payload = JSON.stringify({ dl_pending: true }); animate('download-icon', 'bounce-img'); sendPost(payload); setTimeout(function () { checkMessages(); }, 500); } function toIgnore(button) { let youtube_id = button.getAttribute('data-id'); let payload = JSON.stringify({ ignore: youtube_id }); sendPost(payload); document.getElementById('dl-' + youtube_id).remove(); } function downloadNow(button) { let youtube_id = button.getAttribute('data-id'); let payload = JSON.stringify({ dlnow: youtube_id }); sendPost(payload); document.getElementById(youtube_id).remove(); setTimeout(function () { checkMessages(); }, 500); } function forgetIgnore(button) { let youtube_id = button.getAttribute('data-id'); let payload = JSON.stringify({ forgetIgnore: youtube_id }); sendPost(payload); document.getElementById('dl-' + youtube_id).remove(); } function addSingle(button) { let youtube_id = button.getAttribute('data-id'); let payload = JSON.stringify({ addSingle: youtube_id }); sendPost(payload); document.getElementById('dl-' + youtube_id).remove(); setTimeout(function () { checkMessages(); }, 500); } function deleteQueue(button) { let to_delete = button.getAttribute('data-id'); let payload = JSON.stringify({ deleteQueue: to_delete }); sendPost(payload); // clear button let message = document.createElement('p'); message.innerText = 'deleting download queue: ' + to_delete; document.getElementById(button.id).replaceWith(message); } function stopQueue() { let payload = JSON.stringify({ queue: 'stop' }); sendPost(payload); document.getElementById('stop-icon').remove(); } function killQueue() { let payload = JSON.stringify({ queue: 'kill' }); sendPost(payload); document.getElementById('kill-icon').remove(); } // settings page buttons function manualImport() { let payload = JSON.stringify({ 'manual-import': true }); sendPost(payload); // clear button let message = document.createElement('p'); message.innerText = 'processing import'; let toReplace = document.getElementById('manual-import'); toReplace.innerHTML = ''; toReplace.appendChild(message); } function reEmbed() { let payload = JSON.stringify({ 're-embed': true }); sendPost(payload); // clear button let message = document.createElement('p'); message.innerText = 'processing thumbnails'; let toReplace = document.getElementById('re-embed'); toReplace.innerHTML = ''; toReplace.appendChild(message); } function dbBackup() { let payload = JSON.stringify({ 'db-backup': true }); sendPost(payload); // clear button let message = document.createElement('p'); message.innerText = 'backing up archive'; let toReplace = document.getElementById('db-backup'); toReplace.innerHTML = ''; toReplace.appendChild(message); } function dbRestore(button) { let fileName = button.getAttribute('data-id'); let payload = JSON.stringify({ 'db-restore': fileName }); sendPost(payload); // clear backup row let message = document.createElement('p'); message.innerText = 'restoring from backup'; let toReplace = document.getElementById(fileName); toReplace.innerHTML = ''; toReplace.appendChild(message); } function fsRescan() { let payload = JSON.stringify({ 'fs-rescan': true }); sendPost(payload); // clear button let message = document.createElement('p'); message.innerText = 'File system scan in progress'; let toReplace = document.getElementById('fs-rescan'); toReplace.innerHTML = ''; toReplace.appendChild(message); } function resetToken() { let payload = JSON.stringify({ 'reset-token': true }); sendPost(payload); let message = document.createElement('p'); message.innerText = 'Token revoked'; document.getElementById('text-reveal').replaceWith(message); } // delete from file system function deleteConfirm() { let to_show = document.getElementById('delete-button'); document.getElementById('delete-item').style.display = 'none'; to_show.style.display = 'block'; } function deleteVideo(button) { let to_delete = button.getAttribute('data-id'); let to_redirect = button.getAttribute('data-redirect'); let payload = JSON.stringify({ 'delete-video': to_delete }); sendPost(payload); setTimeout(function () { let redirect = '/channel/' + to_redirect; window.location.replace(redirect); }, 1000); } function deleteChannel(button) { let to_delete = button.getAttribute('data-id'); let payload = JSON.stringify({ 'delete-channel': to_delete }); sendPost(payload); setTimeout(function () { window.location.replace('/channel/'); }, 1000); } function deletePlaylist(button) { let playlist_id = button.getAttribute('data-id'); let playlist_action = button.getAttribute('data-action'); let payload = JSON.stringify({ 'delete-playlist': { 'playlist-id': playlist_id, 'playlist-action': playlist_action, }, }); sendPost(payload); setTimeout(function () { window.location.replace('/playlist/'); }, 1000); } function cancelDelete() { document.getElementById('delete-button').style.display = 'none'; document.getElementById('delete-item').style.display = 'block'; } // get seconds from hh:mm:ss.ms timestamp function getSeconds(timestamp) { let elements = timestamp.split(':', 3); let secs = parseInt(elements[0]) * 60 * 60 + parseInt(elements[1]) * 60 + parseFloat(elements[2]); return secs; } // player let sponsorBlock = []; function createPlayer(button) { let videoId = button.getAttribute('data-id'); let videoPosition = button.getAttribute('data-position'); let videoData = getVideoData(videoId); let sponsorBlockElements = ''; if (videoData.data.sponsorblock && videoData.data.sponsorblock.is_enabled) { sponsorBlock = videoData.data.sponsorblock; if (sponsorBlock.segments.length === 0) { sponsorBlockElements = `

This video doesn't have any sponsor segments added. To add a segment go to this video on Youtube and add a segment using the SponsorBlock extension.

`; } else { if (sponsorBlock.has_unlocked) { sponsorBlockElements = `

This video has unlocked sponsor segments. Go to this video on YouTube and vote on the segments using the SponsorBlock extension.

`; } } } else { sponsorBlock = null; } let videoProgress; if (videoPosition) { videoProgress = getSeconds(videoPosition); } else { videoProgress = getVideoProgress(videoId).position; } let videoName = videoData.data.title; let videoTag = createVideoTag(videoData, videoProgress); let playlist = ''; let videoPlaylists = videoData.data.playlist; // Array of playlists the video is in if (typeof videoPlaylists !== 'undefined') { let subbedPlaylists = getSubbedPlaylists(videoPlaylists); // Array of playlist the video is in that are subscribed if (subbedPlaylists.length !== 0) { let playlistData = getPlaylistData(subbedPlaylists[0]); // Playlist data for first subscribed playlist let playlistId = playlistData.playlist_id; let playlistName = playlistData.playlist_name; playlist = `
${playlistName}
`; } } let videoViews = formatNumbers(videoData.data.stats.view_count); let channelId = videoData.data.channel.channel_id; let channelName = videoData.data.channel.channel_name; removePlayer(); // If cast integration is enabled create cast button let castButton = ''; if (videoData.config.application.enable_cast) { castButton = ``; } // Watched indicator let watchStatusIndicator; if (videoData.data.player.watched) { watchStatusIndicator = createWatchStatusIndicator(videoId, 'watched'); } else { watchStatusIndicator = createWatchStatusIndicator(videoId, 'unwatched'); } let playerStats = `
views icon${videoViews}`; if (videoData.data.stats.like_count) { let likes = formatNumbers(videoData.data.stats.like_count); playerStats += `|thumbs-up${likes}`; } if (videoData.data.stats.dislike_count && videoData.config.downloads.integrate_ryd) { let dislikes = formatNumbers(videoData.data.stats.dislike_count); playerStats += `|thumbs-down${dislikes}`; } playerStats += '
'; const markup = `
${videoTag}
${sponsorBlockElements}
close-icon ${watchStatusIndicator} ${castButton} ${playerStats}

${channelName}

${playlist}

${videoName}

`; const divPlayer = document.getElementById('player'); divPlayer.innerHTML = markup; recordTextTrackChanges(); } // Add video tag to video page when passed a video id, function loaded on page load `video.html (115-117)` function insertVideoTag(videoData, videoProgress) { let videoTag = createVideoTag(videoData, videoProgress); let videoMain = document.querySelector('.video-main'); videoMain.innerHTML += videoTag; } // Generates a video tag with subtitles when passed videoData and videoProgress. function createVideoTag(videoData, videoProgress) { let videoId = videoData.data.youtube_id; let videoUrl = videoData.data.media_url; let videoThumbUrl = videoData.data.vid_thumb_url; let subtitles = ''; let videoSubtitles = videoData.data.subtitles; // Array of subtitles if (typeof videoSubtitles !== 'undefined' && videoData.config.downloads.subtitle) { for (let i = 0; i < videoSubtitles.length; i++) { let label = videoSubtitles[i].name; if (videoSubtitles[i].source === 'auto') { label += ' - auto'; } subtitles += ``; } } let videoTag = ` `; return videoTag; } // Gets video tag function getVideoPlayer() { let videoElement = document.getElementById('video-item'); return videoElement; } // Gets the video source tag function getVideoPlayerVideoSource() { let videoPlayerVideoSource = document.getElementById('video-source'); return videoPlayerVideoSource; } // Gets the current progress of the video currently in the player function getVideoPlayerCurrentTime() { let videoElement = getVideoPlayer(); if (videoElement != null) { return videoElement.currentTime; } } // Gets the video id of the video currently in the player function getVideoPlayerVideoId() { let videoPlayerVideoSource = getVideoPlayerVideoSource(); if (videoPlayerVideoSource != null) { return videoPlayerVideoSource.getAttribute('videoid'); } } // Gets the duration of the video currently in the player function getVideoPlayerDuration() { let videoElement = getVideoPlayer(); if (videoElement != null) { return videoElement.duration; } } // Gets current watch status of video based on watch button function getVideoPlayerWatchStatus() { let videoId = getVideoPlayerVideoId(); let watched = false; let 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, SB skipping function onVideoProgress() { let videoId = getVideoPlayerVideoId(); let currentTime = getVideoPlayerCurrentTime(); let duration = getVideoPlayerDuration(); let videoElement = getVideoPlayer(); let notificationsElement = document.getElementById('notifications'); if (sponsorBlock && sponsorBlock.segments) { for (let i in sponsorBlock.segments) { if ( currentTime >= sponsorBlock.segments[i].segment[0] && currentTime <= sponsorBlock.segments[i].segment[0] + 0.3 ) { videoElement.currentTime = sponsorBlock.segments[i].segment[1]; let notificationElement = document.getElementById( 'notification-' + sponsorBlock.segments[i].UUID ); if (!notificationElement) { notificationsElement.innerHTML += `

Skipped sponsor segment from ${formatTime( sponsorBlock.segments[i].segment[0] )} to ${formatTime(sponsorBlock.segments[i].segment[1])}.

`; } } if (currentTime > sponsorBlock.segments[i].segment[1] + 10) { let notificationsElementUUID = document.getElementById( 'notification-' + sponsorBlock.segments[i].UUID ); if (notificationsElementUUID) { notificationsElementUUID.outerHTML = ''; } } } } 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() { let videoId = getVideoPlayerVideoId(); if (!getVideoPlayerWatchStatus()) { // Check if video is already marked as watched updateVideoWatchStatus(videoId, 'unwatched'); } for (let i in sponsorBlock.segments) { let notificationsElementUUID = document.getElementById( 'notification-' + sponsorBlock.segments[i].UUID ); if (notificationsElementUUID) { notificationsElementUUID.outerHTML = ''; } } } function watchedThreshold(currentTime, duration) { let watched = false; if (duration <= 1800) { // If video is less than 30 min if (currentTime / duration >= 0.9) { // Mark as watched at 90% watched = true; } } else { // If video is more than 30 min if (currentTime >= duration - 120) { // Mark as watched if there is two minutes left watched = true; } } return watched; } // Runs on video pause. Sends current position. function onVideoPause() { let videoId = getVideoPlayerVideoId(); let currentTime = getVideoPlayerCurrentTime(); postVideoProgress(videoId, currentTime); } // Format numbers for frontend function formatNumbers(number) { let numberUnformatted = parseFloat(number); let numberFormatted; if (numberUnformatted > 999999999) { numberFormatted = (numberUnformatted / 1000000000).toFixed(1).toString() + 'B'; } else if (numberUnformatted > 999999) { numberFormatted = (numberUnformatted / 1000000).toFixed(1).toString() + 'M'; } else if (numberUnformatted > 999) { numberFormatted = (numberUnformatted / 1000).toFixed(1).toString() + 'K'; } else { numberFormatted = numberUnformatted; } return numberFormatted; } // Formats times in seconds for frontend function formatTime(time) { let hoursUnformatted = time / 3600; let minutesUnformatted = (time % 3600) / 60; let secondsUnformatted = time % 60; let hoursFormatted = Math.trunc(hoursUnformatted); let minutesFormatted; if (minutesUnformatted < 10 && hoursFormatted > 0) { minutesFormatted = '0' + Math.trunc(minutesUnformatted); } else { minutesFormatted = Math.trunc(minutesUnformatted); } let secondsFormatted; if (secondsUnformatted < 10) { secondsFormatted = '0' + Math.trunc(secondsUnformatted); } else { secondsFormatted = Math.trunc(secondsUnformatted); } let timeUnformatted = ''; if (hoursFormatted > 0) { timeUnformatted = hoursFormatted + ':'; } let timeFormatted = timeUnformatted.concat(minutesFormatted, ':', secondsFormatted); return timeFormatted; } // Gets video data when passed video ID function getVideoData(videoId) { let apiEndpoint = '/api/video/' + videoId + '/'; let videoData = apiRequest(apiEndpoint, 'GET'); return videoData; } // Gets channel data when passed channel ID function getChannelData(channelId) { let apiEndpoint = '/api/channel/' + channelId + '/'; let channelData = apiRequest(apiEndpoint, 'GET'); return channelData.data; } // Gets playlist data when passed playlist ID function getPlaylistData(playlistId) { let apiEndpoint = '/api/playlist/' + playlistId + '/'; let playlistData = apiRequest(apiEndpoint, 'GET'); return playlistData.data; } // Get video progress data when passed video ID function getVideoProgress(videoId) { let apiEndpoint = '/api/video/' + videoId + '/progress/'; let 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) { let subbedPlaylists = []; for (let 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) { let apiEndpoint = '/api/video/' + videoId + '/progress/'; let duartion = getVideoPlayerDuration(); if (!isNaN(videoProgress) && duartion !== 'undefined') { let 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); } } } // Send sponsor segment when given video id and and timestamps function postSponsorSegment(videoId, startTime, endTime) { let apiEndpoint = '/api/video/' + videoId + '/sponsor/'; let data = { segment: { startTime: startTime, endTime: endTime, }, }; apiRequest(apiEndpoint, 'POST', data); } // Send sponsor segment when given video id and and timestamps function postSponsorSegmentVote(videoId, uuid, vote) { let apiEndpoint = '/api/video/' + videoId + '/sponsor/'; let data = { vote: { uuid: uuid, yourVote: vote, }, }; apiRequest(apiEndpoint, 'POST', data); } function handleCookieValidate() { document.getElementById('cookieButton').remove(); let cookieMessageElement = document.getElementById('cookieMessage'); cookieMessageElement.innerHTML = `Processing.`; let response = postCookieValidate(); if (response.cookie_validated === true) { cookieMessageElement.innerHTML = `The cookie file is valid.`; } else { cookieMessageElement.innerHTML = `Warning, the cookie file is invalid.`; } } // Check youtube cookie settings function postCookieValidate() { let apiEndpoint = '/api/cookie/'; return apiRequest(apiEndpoint, 'POST'); } // Makes api requests when passed an endpoint and method ("GET", "POST", "DELETE") function apiRequest(apiEndpoint, method, data) { const xhttp = new XMLHttpRequest(); let 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() { let currentTime = getVideoPlayerCurrentTime(); let duration = getVideoPlayerDuration(); let videoId = getVideoPlayerVideoId(); postVideoProgress(videoId, currentTime); setProgressBar(videoId, currentTime, duration); let playerElement = document.getElementById('player'); if (playerElement.hasChildNodes()) { let youtubeId = playerElement.childNodes[1].getAttribute('data-id'); let playedStatus = document.createDocumentFragment(); let playedBox = document.getElementById(youtubeId); if (playedBox) { playedStatus.appendChild(playedBox); } playerElement.innerHTML = ''; // append played status let videoInfo = document.getElementById('video-info-' + youtubeId); if (videoInfo) { videoInfo.insertBefore(playedStatus, videoInfo.firstChild); } } } // Sets the progress bar when passed a video id, video progress and video duration function setProgressBar(videoId, currentTime, duration) { let progressBarWidth = (currentTime / duration) * 100 + '%'; let 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 let searchTimeout = null; function searchMulti(query) { clearTimeout(searchTimeout); searchTimeout = setTimeout(function () { if (query.length > 1) { let http = new XMLHttpRequest(); http.onreadystatechange = function () { if (http.readyState === 4) { let response = JSON.parse(http.response); populateMultiSearchResults(response.results, response.queryType); } }; http.open('GET', `/api/search/?query=${query}`, true); http.setRequestHeader('X-CSRFToken', getCookie('csrftoken')); http.setRequestHeader('Content-type', 'application/json'); http.send(); } }, 500); } function getViewDefaults(view) { let defaultView = document.getElementById('id_' + view).value; return defaultView; } function populateMultiSearchResults(allResults, queryType) { // videos let defaultVideo = getViewDefaults('home'); let allVideos = allResults.video_results; let videoBox = document.getElementById('video-results'); videoBox.innerHTML = ''; videoBox.parentElement.style.display = 'block'; if (allVideos.length > 0) { for (let index = 0; index < allVideos.length; index++) { const video = allVideos[index].source; const videoDiv = createVideo(video, defaultVideo); videoBox.appendChild(videoDiv); } } else { if (queryType === 'simple' || queryType === 'video') { videoBox.innerHTML = '

No videos found.

'; } else { videoBox.parentElement.style.display = 'none'; } } // channels let defaultChannel = getViewDefaults('channel'); let allChannels = allResults.channel_results; let channelBox = document.getElementById('channel-results'); channelBox.innerHTML = ''; channelBox.parentElement.style.display = 'block'; if (allChannels.length > 0) { for (let index = 0; index < allChannels.length; index++) { const channel = allChannels[index].source; const channelDiv = createChannel(channel, defaultChannel); channelBox.appendChild(channelDiv); } } else { if (queryType === 'simple' || queryType === 'channel') { channelBox.innerHTML = '

No channels found.

'; } else { channelBox.parentElement.style.display = 'none'; } } // playlists let defaultPlaylist = getViewDefaults('playlist'); let allPlaylists = allResults.playlist_results; let playlistBox = document.getElementById('playlist-results'); playlistBox.innerHTML = ''; playlistBox.parentElement.style.display = 'block'; if (allPlaylists.length > 0) { for (let index = 0; index < allPlaylists.length; index++) { const playlist = allPlaylists[index].source; const playlistDiv = createPlaylist(playlist, defaultPlaylist); playlistBox.appendChild(playlistDiv); } } else { if (queryType === 'simple' || queryType === 'playlist') { playlistBox.innerHTML = '

No playlists found.

'; } else { playlistBox.parentElement.style.display = 'none'; } } // fulltext let allFullText = allResults.fulltext_results; let fullTextBox = document.getElementById('fulltext-results'); fullTextBox.innerHTML = ''; fullTextBox.parentElement.style.display = 'block'; if (allFullText.length > 0) { for (let i = 0; i < allFullText.length; i++) { const fullText = allFullText[i]; if ('highlight' in fullText) { const fullTextDiv = createFulltext(fullText); fullTextBox.appendChild(fullTextDiv); } } } else { if (queryType === 'simple' || queryType === 'full') { fullTextBox.innerHTML = '

No fulltext items found.

'; } else { fullTextBox.parentElement.style.display = 'none'; } } } 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; let watchStatusIndicator; if (video.player.watched) { watchStatusIndicator = createWatchStatusIndicator(videoId, 'watched'); } else { 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; let button; if (channel.channel_subscribed) { button = ``; } else { button = ``; } // build markup const markup = `
${channelId}-banner

${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; let button; if (playlist.playlist_subscribed) { button = ``; } else { 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; } function createFulltext(fullText) { const videoId = fullText.source.youtube_id; const videoTitle = fullText.source.title; const thumbUrl = fullText.source.vid_thumb_url; const channelId = fullText.source.subtitle_channel_id; const channelName = fullText.source.subtitle_channel; const subtitleLine = fullText.highlight.subtitle_line[0]; const subtitle_start = fullText.source.subtitle_start.split('.')[0]; const subtitle_end = fullText.source.subtitle_end.split('.')[0]; const markup = `
video-thumb
play-icon

${subtitle_start} - ${subtitle_end}

${subtitleLine}

${channelName}

${videoTitle}

`; const fullTextDiv = document.createElement('div'); fullTextDiv.setAttribute('class', 'video-item list'); fullTextDiv.innerHTML = markup; return fullTextDiv; } // generic function sendPost(payload) { let 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) { let c_start = document.cookie.indexOf(c_name + '='); if (c_start !== -1) { c_start = c_start + c_name.length + 1; let 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() { let textBox = document.getElementById('text-reveal'); let button = document.getElementById('text-reveal-button'); let textBoxHeight = textBox.style.height; if (textBoxHeight === 'unset') { textBox.style.height = '0px'; button.innerText = 'Show'; } else { textBox.style.height = 'unset'; button.innerText = 'Hide'; } } function textExpand() { let textBox = document.getElementById('text-expand'); let button = document.getElementById('text-expand-button'); let style = window.getComputedStyle(textBox); if (style.webkitLineClamp === 'none') { textBox.style['-webkit-line-clamp'] = '4'; button.innerText = 'Show more'; } else { textBox.style['-webkit-line-clamp'] = 'unset'; button.innerText = 'Show less'; } } // hide "show more" button if all text is already visible function textExpandButtonVisibilityUpdate() { let textBox = document.getElementById('text-expand'); let button = document.getElementById('text-expand-button'); if (!textBox || !button) return; let styles = window.getComputedStyle(textBox); let textBoxLineClamp = styles.webkitLineClamp; if (textBoxLineClamp === 'unset') return; // text box is in revealed state if (textBox.offsetHeight < textBox.scrollHeight || textBox.offsetWidth < textBox.scrollWidth) { // the element has an overflow, show read more button button.style.display = 'inline-block'; } else { // the element doesn't have overflow button.style.display = 'none'; } } document.addEventListener('readystatechange', textExpandButtonVisibilityUpdate); window.addEventListener('resize', textExpandButtonVisibilityUpdate); function showForm() { let formElement = document.getElementById('hidden-form'); let displayStyle = formElement.style.display; if (displayStyle === '') { formElement.style.display = 'block'; } else { formElement.style.display = ''; } animate('animate-icon', 'pulse-img'); } function channelFilterDownload(value) { if (value === 'all') { window.location = '/downloads/'; } else { window.location.search = '?channel=' + value; } } function showOverwrite() { let 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) { let toAnimate = document.getElementById(elementId); if (toAnimate.className !== animationClass) { toAnimate.className = animationClass; } else { toAnimate.classList.remove(animationClass); } } // keep track of changes to the subtitles list made with the native UI // needed so that when toggling subtitles with the shortcut we go to the last selected one, not the first one addEventListener('DOMContentLoaded', recordTextTrackChanges); let lastSeenTextTrack = 0; function recordTextTrackChanges() { let player = getVideoPlayer(); if (player == null) { return; } player.textTracks.addEventListener('change', () => { let active = [...player.textTracks].findIndex(x => x.mode === 'showing'); if (active !== -1) { lastSeenTextTrack = active; } }); } // keyboard shortcuts for the video player document.addEventListener('keydown', doShortcut); let modalHideTimeout = -1; function showModal(html, duration) { let modal = document.querySelector('.video-modal-text'); modal.innerHTML = html; modal.style.display = 'initial'; clearTimeout(modalHideTimeout); modalHideTimeout = setTimeout(() => { modal.style.display = 'none'; }, duration); } let videoSpeeds = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.25, 2.5, 2.75, 3]; function doShortcut(e) { if (!(e.target instanceof HTMLElement)) { return; } let target = e.target; let targetName = target.nodeName.toLowerCase(); if ( targetName === 'textarea' || targetName === 'input' || targetName === 'select' || target.isContentEditable ) { return; } if (e.altKey || e.ctrlKey || e.metaKey) { return; } let player = getVideoPlayer(); if (player == null) { // not on the video page return; } switch (e.key) { case 'c': { // toggle captions let tracks = [...player.textTracks]; if (tracks.length === 0) { break; } let active = tracks.find(x => x.mode === 'showing'); if (active != null) { active.mode = 'disabled'; } else { tracks[lastSeenTextTrack].mode = 'showing'; } break; } case 'm': { player.muted = !player.muted; break; } case 'ArrowLeft': { if (targetName === 'video') { // hitting arrows while the video is focused will use the built-in skip break; } showModal('- 5 seconds', 500); player.currentTime -= 5; break; } case 'ArrowRight': { if (targetName === 'video') { // hitting space while the video is focused will use the built-in skip break; } showModal('+ 5 seconds', 500); player.currentTime += 5; break; } case '<': case '>': { // change speed let currentSpeedIdx = videoSpeeds.findIndex(s => s >= player.playbackRate); if (currentSpeedIdx === -1) { // handle the case where the user manually set the speed above our max speed currentSpeedIdx = videoSpeeds.length - 1; } let newSpeedIdx = e.key === '<' ? Math.max(0, currentSpeedIdx - 1) : Math.min(videoSpeeds.length - 1, currentSpeedIdx + 1); let newSpeed = videoSpeeds[newSpeedIdx]; player.playbackRate = newSpeed; showModal(newSpeed + 'x', 500); break; } case ' ': { if (targetName === 'video') { // hitting space while the video is focused will toggle it anyway break; } e.preventDefault(); if (player.paused) { player.play(); } else { player.pause(); } break; } case '?': { showModal( ` `, 3000 ); break; } } }
Show help?
Toggle mutem
Toggle subtitles (if available)c
Increase speed>
Decrease speed<
Back 5 seconds
Forward 5 seconds