diff --git a/docker-compose.yml b/docker-compose.yml index b300949..65fee96 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,7 +23,7 @@ services: - archivist-es - archivist-redis archivist-redis: - image: redislabs/rejson:latest + image: redislabs/rejson:latest # For arm64 just update this line with bbilly1/rejson:latest container_name: archivist-redis restart: always expose: diff --git a/tubearchivist/home/templates/home/video.html b/tubearchivist/home/templates/home/video.html index cb583cf..90e986d 100644 --- a/tubearchivist/home/templates/home/video.html +++ b/tubearchivist/home/templates/home/video.html @@ -2,17 +2,7 @@ {% block content %} {% load static %} {% load humanize %} -
- -
+
{% if cast %} @@ -122,4 +112,7 @@ {% endfor %} {% endif %}
+ {% endblock content %} diff --git a/tubearchivist/static/cast-videos.js b/tubearchivist/static/cast-videos.js index 6b98058..0a30af4 100644 --- a/tubearchivist/static/cast-videos.js +++ b/tubearchivist/static/cast-videos.js @@ -13,6 +13,16 @@ function initializeCastApi() { castConnectionChange(player) } ); + playerController.addEventListener( + cast.framework.RemotePlayerEventType.CURRENT_TIME_CHANGED, function() { + castVideoProgress(player) + } + ); + playerController.addEventListener( + cast.framework.RemotePlayerEventType.IS_PAUSED_CHANGED, function() { + castVideoPaused(player) + } + ); } @@ -26,32 +36,64 @@ function castConnectionChange(player) { } } +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); + 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()) { - contentId = document.getElementById("video-source").src; // Get video URL - contentTitle = document.getElementById('video-title').innerHTML; // Get video title - contentImage = document.getElementById("video-item").poster; // Get video thumbnail URL + 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 = document.getElementById("video-item").currentTime; // Get video's current position + contentCurrentTime = getVideoPlayerCurrentTime(); // Get video's current position contentActiveSubtitle = []; // Check if a subtitle is turned on. - for (var i = 0; i < document.getElementById("video-item").textTracks.length; i++) { - if (document.getElementById("video-item").textTracks[i].mode == "showing") { + for (var i = 0; i < getVideoPlayer().textTracks.length; i++) { + if (getVideoPlayer().textTracks[i].mode == "showing") { contentActiveSubtitle =[i + 1]; } } contentSubtitles = []; - for (var i = 0; i < document.getElementById("video-item").children.length; i++) { - if (document.getElementById("video-item").children[i].tagName == "TRACK") { + 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 = document.getElementById("video-item").children[i].src; + subtitle.trackContentId = videoSubtitles[i].media_url; subtitle.trackContentType = 'text/vtt'; subtitle.subtype = chrome.cast.media.TextTrackType.SUBTITLES; - subtitle.name = document.getElementById("video-item").children[i].label; - subtitle.language = document.getElementById("video-item").children[i].srclang; + subtitle.name = videoSubtitles[i].name; + subtitle.language = videoSubtitles[i].lang; subtitle.customData = null; contentSubtitles.push(subtitle); } @@ -91,7 +133,7 @@ function shiftCurrentTime(contentCurrentTime) { // Shift media back 3 seconds to function castSuccessful() { // console.log('Cast Successful.'); - document.getElementById("video-item").pause(); // Pause browser video on successful cast + getVideoPlayer().pause(); // Pause browser video on successful cast } function castFailed(errorCode) { diff --git a/tubearchivist/static/script.js b/tubearchivist/static/script.js index ec3aa40..162e5ab 100644 --- a/tubearchivist/static/script.js +++ b/tubearchivist/static/script.js @@ -9,7 +9,7 @@ function sortChange(sortValue) { } function isWatched(youtube_id) { - // sendVideoProgress(youtube_id, 0); // Reset video progress on watched; + postVideoProgress(youtube_id, 0); // Reset video progress on watched; var payload = JSON.stringify({'watched': youtube_id}); sendPost(payload); var seenIcon = document.createElement('img'); @@ -34,7 +34,7 @@ function isWatchedButton(button) { } function isUnwatched(youtube_id) { - // sendVideoProgress(youtube_id, 0); // Reset video progress on unwatched; + postVideoProgress(youtube_id, 0); // Reset video progress on unwatched; var payload = JSON.stringify({'un_watched': youtube_id}); sendPost(payload); var unseenIcon = document.createElement('img'); @@ -298,20 +298,12 @@ function cancelDelete() { function createPlayer(button) { var videoId = button.getAttribute('data-id'); var videoData = getVideoData(videoId); - var videoUrl = videoData.media_url; - var videoThumbUrl = videoData.vid_thumb_url; - var videoName = videoData.title; + var videoName = videoData.data.title; - var subtitles = ''; - var videoSubtitles = videoData.subtitles; // Array of subtitles - if (typeof(videoSubtitles) != 'undefined') { - for (var i = 0; i < videoSubtitles.length; i++) { - subtitles += ``; - } - } + var videoTag = createVideoTag(videoId); var playlist = ''; - var videoPlaylists = videoData.playlist; // Array of playlists the video is in + 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) { @@ -322,24 +314,22 @@ function createPlayer(button) { } } - var videoProgress = videoData.player.progress; // Groundwork for saving video position, change once progress variable is added to API - var videoViews = formatNumbers(videoData.stats.view_count); + var videoViews = formatNumbers(videoData.data.stats.view_count); - var channelId = videoData.channel.channel_id; - var channelName = videoData.channel.channel_name; + 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 = ``; - var castScript = document.getElementById('cast-script'); - if (typeof(castScript) != 'undefined' && castScript != null) { + var castButton = ''; + if (videoData.config.application.enable_cast) { var castButton = ``; } // Watched indicator - if (videoData.player.watched) { + if (videoData.data.player.watched) { var playerState = "seen"; var watchedFunction = "Unwatched"; } else { @@ -348,22 +338,19 @@ function createPlayer(button) { } var playerStats = `
views icon${videoViews}`; - if (videoData.stats.like_count) { - var likes = formatNumbers(videoData.stats.like_count); + if (videoData.data.stats.like_count) { + var likes = formatNumbers(videoData.data.stats.like_count); playerStats += `|thumbs-up${likes}`; } - if (videoData.stats.dislike_count) { - var dislikes = formatNumbers(videoData.stats.dislike_count); + 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 ${playerState}-icon @@ -377,47 +364,121 @@ function createPlayer(button) {
`; - const divPlayer = document.getElementById("player"); + const divPlayer = document.getElementById("player"); divPlayer.innerHTML = markup; } -// Set video progress in seconds -function setVideoProgress(videoProgress) { - if (isNaN(videoProgress)) { - videoProgress = 0; - } - var videoElement = document.getElementById("video-item"); - videoElement.currentTime = videoProgress; +// Add video tag to video page when passed a video id, function loaded on page load `video.html (115-117)` +function insertVideoTag(videoId) { + var videoTag = createVideoTag(videoId); + var videoMain = document.getElementsByClassName("video-main"); + videoMain[0].innerHTML = videoTag; } -// Runs on video playback, marks video as watched if video gets to 90% or higher, WIP sends position to api -function onVideoProgress(videoId) { +// Generates a video tag with subtitles when passed a video id. +function createVideoTag(videoId) { + var videoData = getVideoData(videoId); + var videoProgress = getVideoProgress(videoId).position; + 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++) { + 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) { - if ((videoElement.currentTime % 10).toFixed(1) <= 0.2) { // Check progress every 10 seconds or else progress is checked a few times a second - // sendVideoProgress(videoId, videoElement.currentTime); // Groundwork for saving video position - if (((videoElement.currentTime / videoElement.duration) >= 0.90) && document.getElementById(videoId).className == "unseen-icon") { + 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; + if(document.getElementById(videoId).className != "unseen-icon") { + 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)) { isWatched(videoId); } } } } -// Groundwork for saving video position -function sendVideoProgress(videoId, videoProgress) { - var apiEndpoint = "/api/video/"; - if (isNaN(videoProgress)) { - videoProgress = 0; +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; + } } - var data = { - "data": [{ - "youtube_id": videoId, - "player": { - "progress": videoProgress - } - }] - }; - videoData = apiRequest(apiEndpoint, "POST", data); + return watched; +} + +// Runs on video pause. Sends current position. +function onVideoPause() { + var videoId = getVideoPlayerVideoId(); + var currentTime = getVideoPlayerCurrentTime(); + postVideoProgress(videoId, currentTime); } // Format numbers for frontend @@ -435,27 +496,34 @@ function formatNumbers(number) { return numberFormatted; } -// Gets video data in JSON format when passed video ID +// Gets video data when passed video ID function getVideoData(videoId) { var apiEndpoint = "/api/video/" + videoId + "/"; - videoData = apiRequest(apiEndpoint, "GET"); - return videoData.data; + var videoData = apiRequest(apiEndpoint, "GET"); + return videoData; } -// Gets channel data in JSON format when passed channel ID +// Gets channel data when passed channel ID function getChannelData(channelId) { var apiEndpoint = "/api/channel/" + channelId + "/"; - channelData = apiRequest(apiEndpoint, "GET"); + var channelData = apiRequest(apiEndpoint, "GET"); return channelData.data; } -// Gets playlist data in JSON format when passed playlist ID +// Gets playlist data when passed playlist ID function getPlaylistData(playlistId) { var apiEndpoint = "/api/playlist/" + playlistId + "/"; - playlistData = apiRequest(apiEndpoint, "GET"); + 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 = []; @@ -467,18 +535,43 @@ function getSubbedPlaylists(videoPlaylists) { return subbedPlaylists; } -// Makes api requests when passed an endpoint and method ("GET" or "POST") +// Send video position when given video id and progress in seconds +function postVideoProgress(videoId, videoProgress) { + var apiEndpoint = "/api/video/" + videoId + "/progress/"; + if (!isNaN(videoProgress)) { + 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); } +function getURL() { + return window.location.href.replace(window.location.pathname, ""); +} + function removePlayer() { + var currentTime = getVideoPlayerCurrentTime(); + var videoId = getVideoPlayerVideoId(); + postVideoProgress(videoId, currentTime); var playerElement = document.getElementById('player'); if (playerElement.hasChildNodes()) { var youtubeId = playerElement.childNodes[1].getAttribute("data-id");