diff --git a/tubearchivist/home/templates/home/video.html b/tubearchivist/home/templates/home/video.html
index a220fa8..1b2e03d 100644
--- a/tubearchivist/home/templates/home/video.html
+++ b/tubearchivist/home/templates/home/video.html
@@ -6,7 +6,7 @@
+ type='video/mp4' width="100%" playsinline id="video-item" ontimeupdate="onVideoProgress('{{ video.youtube_id }}')" onloadedmetadata="setVideoProgress(0)">
diff --git a/tubearchivist/static/.jshintrc b/tubearchivist/static/.jshintrc
new file mode 100644
index 0000000..8ab3485
--- /dev/null
+++ b/tubearchivist/static/.jshintrc
@@ -0,0 +1,3 @@
+{
+ "esversion": 6
+}
\ No newline at end of file
diff --git a/tubearchivist/static/css/style.css b/tubearchivist/static/css/style.css
index 40b845e..53a8882 100644
--- a/tubearchivist/static/css/style.css
+++ b/tubearchivist/static/css/style.css
@@ -456,6 +456,17 @@ button:hover {
text-transform: uppercase;
}
+.player-stats {
+ float: right;
+ display: flex;
+ align-items: center;
+ margin-top: 10px;
+}
+
+.player-stats span {
+ margin: 0 5px;
+}
+
.video-desc-player {
margin-bottom: 8px;
display: flex;
@@ -639,7 +650,7 @@ button:hover {
filter: var(--img-filter);
}
-.thumb-icon.dislike img {
+.dislike {
transform: rotate(180deg);
}
diff --git a/tubearchivist/static/img/icon-eye.svg b/tubearchivist/static/img/icon-eye.svg
new file mode 100644
index 0000000..cc2d959
--- /dev/null
+++ b/tubearchivist/static/img/icon-eye.svg
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
diff --git a/tubearchivist/static/img/icon-thumb.svg b/tubearchivist/static/img/icon-thumb.svg
index a3e21f3..55a85ff 100644
--- a/tubearchivist/static/img/icon-thumb.svg
+++ b/tubearchivist/static/img/icon-thumb.svg
@@ -15,7 +15,7 @@
version="1.1"
id="svg1303"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
- sodipodi:docname="Icons_thumbs.svg">
+ sodipodi:docname="icon_thumb.svg">
+ inkscape:window-maximized="1"
+ showguides="true"
+ inkscape:guide-bbox="true">
+
+
+
@@ -55,8 +68,8 @@
id="layer1"
transform="translate(0,-164.70764)">
diff --git a/tubearchivist/static/script.js b/tubearchivist/static/script.js
index a7c6b17..854e601 100644
--- a/tubearchivist/static/script.js
+++ b/tubearchivist/static/script.js
@@ -9,6 +9,7 @@ function sortChange(sortValue) {
}
function isWatched(youtube_id) {
+ // sendVideoProgress(youtube_id, 0); // Reset video progress on watched;
var payload = JSON.stringify({'watched': youtube_id});
sendPost(payload);
var seenIcon = document.createElement('img');
@@ -33,6 +34,7 @@ function isWatchedButton(button) {
}
function isUnwatched(youtube_id) {
+ // sendVideoProgress(youtube_id, 0); // Reset video progress on unwatched;
var payload = JSON.stringify({'un_watched': youtube_id});
sendPost(payload);
var unseenIcon = document.createElement('img');
@@ -188,7 +190,7 @@ function reEmbed() {
function dbBackup() {
var payload = JSON.stringify({'db-backup': true});
- sendPost(payload)
+ sendPost(payload);
// clear button
var message = document.createElement('p');
message.innerText = 'backing up archive';
@@ -286,161 +288,266 @@ function cancelDelete() {
// player
function createPlayer(button) {
- var mediaUrl = button.getAttribute('data-src');
- var mediaThumb = button.getAttribute('data-thumb');
- var mediaTitle = button.getAttribute('data-title');
- var mediaChannel = button.getAttribute('data-channel');
- var mediaChannelId = button.getAttribute('data-channel-id');
- var dataId = button.getAttribute('data-id');
- // get watched status
- var playedStatus = document.createDocumentFragment();
- playedStatus.appendChild(document.getElementById(dataId));
- // create player
- removePlayer();
- var playerElement = document.createElement('div');
- playerElement.classList.add("video-player");
- // var playerElement = document.getElementById('player');
- playerElement.setAttribute('data-id', dataId);
- // playerElement.innerHTML = '';
- var videoPlayer = document.createElement('video');
- videoPlayer.setAttribute('src', mediaUrl);
- videoPlayer.setAttribute('controls', true);
- videoPlayer.setAttribute('autoplay', true);
- videoPlayer.setAttribute('width', '100%');
- videoPlayer.setAttribute('playsinline', true);
- videoPlayer.setAttribute('poster', mediaThumb);
- videoPlayer.setAttribute('id', 'video-item'); // Set ID to get URL for casting
- playerElement.appendChild(videoPlayer);
- // title bar
- var titleBar = document.createElement('div');
- titleBar.classList.add('player-title', 'boxed-content');
- // close
- var closeButton = document.createElement('img');
- closeButton.className = 'close-button';
- closeButton.setAttribute('src', "/static/img/icon-close.svg");
- closeButton.setAttribute('alt', 'close-icon');
- closeButton.setAttribute('data', dataId);
- closeButton.setAttribute('onclick', "removePlayer()");
- closeButton.setAttribute('title', 'Close player');
- titleBar.appendChild(closeButton);
- // played
- titleBar.appendChild(playedStatus);
- // channel title
- var channelTitleLink = document.createElement('a');
- channelTitleLink.setAttribute('href', '/channel/' + mediaChannelId + '/');
- var channelTitle = document.createElement('h3');
- channelTitle.innerText = mediaChannel;
- channelTitleLink.appendChild(channelTitle);
- titleBar.appendChild(channelTitleLink);
- // video title
- var videoTitleLink = document.createElement('a');
- videoTitleLink.setAttribute('href', '/video/' + dataId + '/');
- var videoTitle = document.createElement('h2');
- videoTitle.setAttribute('id', "video-title"); // Set ID to get title for casting
- videoTitle.innerText = mediaTitle;
- var castScript = document.getElementById('cast-script'); // Get cast-script
- if(typeof(castScript) != 'undefined' && castScript != null) { // Check if cast integration is enabled
- var castButton = document.createElement("google-cast-launcher"); // Create cast button
- castButton.setAttribute('id', "castbutton"); // Set ID to apply theme
- titleBar.appendChild(castButton); // Add cast button to title
+ 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 playlist = '';
+ var videoPlaylists = videoData.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 = ``;
+ }
}
- videoTitleLink.appendChild(videoTitle);
- titleBar.appendChild(videoTitleLink);
- // add titlebar
- playerElement.appendChild(titleBar);
- // add whole
- document.getElementById("player").appendChild(playerElement);
+
+ 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 channelId = videoData.channel.channel_id;
+ var channelName = videoData.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 = ` `;
+ }
+
+ // Watched indicator
+ if (videoData.player.is_watched) {
+ var playerState = "seen";
+ var watchedFunction = "Unwatched";
+ } else {
+ var playerState = "unseen";
+ var watchedFunction = "Watched";
+ }
+
+ var playerStats = `${videoViews} `;
+ if (videoData.stats.like_count) {
+ var likes = formatNumbers(videoData.stats.like_count);
+ playerStats += `
| ${likes} `;
+ }
+ if (videoData.stats.dislike_count) {
+ var dislikes = formatNumbers(videoData.stats.dislike_count);
+ playerStats += `
| ${dislikes} `;
+ }
+ playerStats += "
";
+
+ const markup = `
+
+
+
+
+
+ ${castButton}
+ ${playerStats}
+
+
${videoName}
+
+
+ `;
+ 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;
+}
+
+// Runs on video playback, marks video as watched if video gets to 90% or higher, WIP sends position to api
+function onVideoProgress(videoId) {
+ var videoElement = document.getElementById("video-item");
+ 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) {
+ isWatched(videoId);
+ }
+ }
+ }
+}
+
+// Groundwork for saving video position
+function sendVideoProgress(videoId, videoProgress) {
+ var apiEndpoint = "/api/video/";
+ if (isNaN(videoProgress)) {
+ videoProgress = 0;
+ }
+ var data = {
+ "data": [{
+ "youtube_id": videoId,
+ "player": {
+ "progress": videoProgress
+ }
+ }]
+ };
+ videoData = apiRequest(apiEndpoint, "POST", data);
+}
+
+// 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 in JSON format when passed video ID
+function getVideoData(videoId) {
+ var apiEndpoint = "/api/video/" + videoId + "/";
+ videoData = apiRequest(apiEndpoint, "GET");
+ return videoData.data;
+}
+
+// Gets channel data in JSON format when passed channel ID
+function getChannelData(channelId) {
+ var apiEndpoint = "/api/channel/" + channelId + "/";
+ channelData = apiRequest(apiEndpoint, "GET");
+ return channelData.data;
+}
+
+// Gets playlist data in JSON format when passed playlist ID
+function getPlaylistData(playlistId) {
+ var apiEndpoint = "/api/playlist/" + playlistId + "/";
+ playlistData = apiRequest(apiEndpoint, "GET");
+ return playlistData.data;
+}
+
+// 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;
+}
+
+// Makes api requests when passed an endpoint and method ("GET" or "POST")
+function apiRequest(apiEndpoint, method, data) {
+ const xhttp = new XMLHttpRequest();
+ var sessionToken = getCookie("sessionid");
+ xhttp.open(method, apiEndpoint, false);
+ xhttp.setRequestHeader("Authorization", "Token " + sessionToken);
+ xhttp.setRequestHeader("Content-Type", "application/json");
+ xhttp.send(JSON.stringify(data));
+ return JSON.parse(xhttp.responseText);
}
function removePlayer() {
var playerElement = document.getElementById('player');
if (playerElement.hasChildNodes()) {
- var youtubeId = playerElement.childNodes[0].getAttribute("data-id");
+ 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);
- };
+ }
}
// multi search form
function searchMulti(query) {
if (query.length > 1) {
- var payload = JSON.stringify({'multi_search': query})
+ var payload = JSON.stringify({'multi_search': query});
var http = new XMLHttpRequest();
http.onreadystatechange = function() {
if (http.readyState === 4) {
- allResults = JSON.parse(http.response)['results'];
+ 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
+ return defaultView;
}
function populateMultiSearchResults(allResults) {
// videos
var defaultVideo = getViewDefaults("home");
- var allVideos = allResults["video_results"];
+ 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 video = allVideos[index].source;
const videoDiv = createVideo(video, defaultVideo);
videoBox.appendChild(videoDiv);
- };
+ }
// channels
var defaultChannel = getViewDefaults("channel");
- var allChannels = allResults["channel_results"];
+ 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 channel = allChannels[index].source;
const channelDiv = createChannel(channel, defaultChannel);
channelBox.appendChild(channelDiv);
- };
+ }
// playlists
var defaultPlaylist = getViewDefaults("playlist");
- var allPlaylists = allResults["playlist_results"];
+ 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 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"]) {
+ 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 playerState = "seen";
} else {
var playerState = "unseen";
};
- const channelId = video["channel"]["channel_id"];
- const channelName = video["channel"]["channel_name"];
+ const channelId = video.channel.channel_id;
+ const channelName = video.channel.channel_name;
// build markup
const markup = `
@@ -463,25 +570,25 @@ function createVideo(video, viewStyle) {
${videoTitle}