[Feature] Playlist Support mostly finished

This commit is contained in:
PrestonN 2018-09-28 15:48:42 -04:00
parent a2cf6851b8
commit c18046406e
11 changed files with 272 additions and 99 deletions

View File

@ -28,7 +28,7 @@
*
* @return {Void}
*/
function playVideo(videoId) {
function playVideo(videoId, playlistId = '') {
hideViews();
playerView.playerSeen = true;
@ -120,7 +120,7 @@ function playVideo(videoId) {
if (typeof (playerView.video720p) === 'undefined' && typeof (playerView.video480p) === 'undefined') {
//useEmbedPlayer = true;
playerView.currentQuality = 'EMBED';
//playerView.playerSeen = false;
playerView.playerSeen = false;
//useEmbedPlayer = true;
showToast('Unable to get video file. Reverting to embeded player.');
}
@ -196,6 +196,49 @@ function playVideo(videoId) {
playerView.recommendedVideoList = playerView.recommendedVideoList.concat(data);
});
if (playlistId != '') {
playerView.playlistSeen = true;
playerView.playlistShowList = true;
playerView.playlistId = playlistId;
playerView.playlistVideoList = [];
invidiousAPI('playlists', playlistId, {}, (data) => {
playerView.playlistTitle = data.title;
playerView.playlistChannelName = data.author;
playerView.playlistChannelId = data.authorId;
playerView.playlistTotal = data.videoCount;
let amountOfPages = Math.ceil(data.videoCount / 100);
console.log(amountOfPages);
for (let i = 1; i <= amountOfPages; i++) {
invidiousAPI('playlists', playlistId, {page: i}, (data) => {
data.videos.forEach((video) => {
let data = {};
if (video.videoId == videoId){
playerView.playlistIndex = video.index + 1;
}
data.title = video.title;
data.videoId = video.videoId;
data.channelName = video.author;
data.index = video.index + 1;
data.thumbnail = video.videoThumbnails[4].url;
playerView.playlistVideoList[video.index] = data;
});
});
}
});
}
else{
playerView.playlistSeen = false;
playerView.playlistShowList = false;
playerView.playlistId = '';
}
loadingView.seen = false;
if (subscriptionView.seen === false && aboutView.seen === false && headerView.seen === false && searchView.seen === false && settingsView.seen === false && popularView.seen === false && savedView.seen === false && historyView.seen === false && channelView.seen === false && channelVideosView.seen === false) {
@ -272,16 +315,22 @@ function checkVideoSettings() {
switch (defaultQuality) {
case '480':
playerView.videoUrl = playerView.video480p;
playerView.currentQuality = '480p';
if (typeof(playerView.video480p) !== 'undefined') {
playerView.videoUrl = playerView.video480p;
playerView.currentQuality = '480p';
}
break;
case '720':
playerView.videoUrl = playerView.video720p;
playerView.currentQuality = '720p';
if (typeof(playerView.video720p) !== 'undefined') {
playerView.videoUrl = playerView.video720p;
playerView.currentQuality = '720p';
}
break;
default:
playerView.videoUrl = playerView.video720p;
playerView.currentQuality = '720p';
if (typeof(playerView.video720p) !== 'undefined') {
playerView.videoUrl = playerView.video720p;
playerView.currentQuality = '720p';
}
break;
}
@ -292,63 +341,32 @@ function checkVideoSettings() {
player.volume = currentVolume;
}
/**
* Change the quality of the current video.
*
* @param {string} videoHtml - The HTML of the video player to be set.
* @param {string} qualityType - The Quality Type of the video. Ex: 720p, 480p
* @param {boolean} isEmbed - Optional: Value on if the videoHtml is the embeded player.
*
* @return {Void}
*/
function changeQuality(url, qualityText, isEmbed = false) {
if (videoHtml == '') {
showToast('Video quality type is not available. Unable to change quality.')
return;
}
function playNextVideo() {
let player = document.getElementById('videoPlayer');
videoHtml = videoHtml.replace(/\&quot\;/g, '"');
if (player.loop !== false || playerView.playlistSeen === false) {
return;
}
ft.log('HTML Video: ', videoHtml);
ft.log('(Is the video embeded?) isEmbed: ', isEmbed);
if (playerView.playlistShuffle === true) {
let randomVideo = Math.floor(Math.random() * playerView.playlistTotal);
// The YouTube API creates 2 more iFrames. This is why a boolean value is sent
// with the function.
const embedPlayer = document.getElementsByTagName('IFRAME')[0];
loadingView.seen = true;
playVideo(playerView.playlistVideoList[randomVideo].videoId, playerView.playlistId);
return;
}
const html5Player = document.getElementsByClassName('videoPlayer');
if (playerView.playlistLoop === true && playerView.playlistIndex == playerView.playlistTotal) {
loadingView.seen = true;
playVideo(playerView.playlistVideoList[0].videoId, playerView.playlistId);
return;
}
ft.log('Embeded Player Element: ', embedPlayer);
ft.log('HTML5 Player Element: ', html5Player);
if (isEmbed && html5Player.length == 0) {
// The embeded player is already playing. Return.
showToast('You are already using the embeded player.')
return;
} else if (isEmbed) {
// Switch from HTML 5 player to embeded Player
html5Player[0].remove();
const mainHtml = $('#main').html();
$('#main').html(videoHtml + mainHtml);
$('#currentQuality').html(qualityType);
} else if (html5Player.length == 0) {
// Switch from embeded player to HTML 5 player
embedPlayer.remove();
let videoPlayer = document.createElement('video');
videoPlayer.className = 'videoPlayer';
videoPlayer.src = videoHtml;
videoPlayer.controls = true;
videoPlayer.autoplay = true;
$('#main').prepend(videoPlayer);
$('#currentQuality').html(qualityType);
} else {
// Switch src on HTML 5 player
const currentPlayBackTime = $('.videoPlayer').get(0).currentTime;
html5Player[0].src = videoHtml;
html5Player[0].load();
$('.videoPlayer').get(0).currentTime = currentPlayBackTime;
$('#currentQuality').html(qualityType);
}
if (playerView.playlistIndex != playerView.playlistTotal) {
loadingView.seen = true;
playVideo(playerView.playlistVideoList[playerView.playlistIndex].videoId, playerView.playlistId);
return;
}
}
/**

View File

@ -19,9 +19,12 @@ function showPlaylist(playlistId) {
hideViews();
loadingView.seen = true;
playlistView.videoList = [];
invidiousAPI('playlists', playlistId, {}, (data) => {
console.log(data);
playlistView.playlistId = playlistId;
playlistView.channelName = data.author;
playlistView.channelId = data.authorId;
playlistView.channelThumbnail = data.authorThumbnails[3].url;
@ -34,43 +37,66 @@ function showPlaylist(playlistId) {
dateString.setDate(dateString.getDate() + 1);
playlistView.lastUpdated = dateFormat(dateString, "mmm dS, yyyy");
data.videos.forEach((video) => {
let videoData = {};
let amountOfPages = Math.ceil(data.videoCount / 100);
let time = video.lengthSeconds;
let hours = 0;
console.log(amountOfPages);
if (time >= 3600) {
hours = Math.floor(time / 3600);
time = time - hours * 3600;
}
for (let i = 1; i <= amountOfPages; i++) {
invidiousAPI('playlists', playlistId, {page: i}, (data) => {
console.log(data);
data.videos.forEach((video) => {
let videoData = {};
let minutes = Math.floor(time / 60);
let seconds = time - minutes * 60;
let time = video.lengthSeconds;
let hours = 0;
if (seconds < 10) {
seconds = '0' + seconds;
}
if (time >= 3600) {
hours = Math.floor(time / 3600);
time = time - hours * 3600;
}
if (minutes < 10 && hours > 0) {
minutes = '0' + minutes;
}
let minutes = Math.floor(time / 60);
let seconds = time - minutes * 60;
if (hours > 0) {
videoData.duration = hours + ":" + minutes + ":" + seconds;
} else {
videoData.duration = minutes + ":" + seconds;
}
if (seconds < 10) {
seconds = '0' + seconds;
}
videoData.id = video.videoId;
videoData.title = video.title;
videoData.channelName = video.author;
videoData.channelId = video.authorId;
videoData.thumbnail = video.videoThumbnails[4].url;
if (minutes < 10 && hours > 0) {
minutes = '0' + minutes;
}
playlistView.videoList[video.index] = videoData;
});
loadingView.seen = false;
playlistView.seen = true;
if (hours > 0) {
videoData.duration = hours + ":" + minutes + ":" + seconds;
} else {
videoData.duration = minutes + ":" + seconds;
}
videoData.id = video.videoId;
videoData.title = video.title;
videoData.channelName = video.author;
videoData.channelId = video.authorId;
videoData.thumbnail = video.videoThumbnails[4].url;
playlistView.videoList[video.index] = videoData;
});
if (playlistView.seen !== false) {
playlistView.seen = false;
playlistView.seen = true;
}
});
loadingView.seen = false;
playlistView.seen = true;
}
});
}
function togglePlaylist() {
if (playerView.playlistShowList !== false) {
playerView.playlistShowList = false;
}
else{
playerView.playlistShowList = true;
}
}

View File

@ -288,6 +288,7 @@ let playlistView = new Vue({
el: '#playlistView',
data: {
seen: false,
playlistId: '',
channelName: '',
channelId: '',
thumbnail: '',
@ -301,7 +302,7 @@ let playlistView = new Vue({
methods: {
play: (videoId) => {
loadingView.seen = true;
playVideo(videoId);
playVideo(videoId, playlistView.playlistId);
},
channel: (channelId) => {
goToChannel(channelId);
@ -433,6 +434,7 @@ let playerView = new Vue({
el: '#playerView',
data: {
seen: false,
playlistSeen: false,
firstLoad: true,
publishedDate: '',
videoUrl: '',
@ -461,6 +463,13 @@ let playerView = new Vue({
videoLikes: 0,
videoDislikes: 0,
playerSeen: true,
playlistTitle: '',
playlistChannelName: '',
playlistIndex: 1,
playlistTotal: 1,
playlistLoop: false,
playlistShuffle: false,
playlistShowList: true,
recommendedVideoList: [],
playlistVideoList: [],
},
@ -500,9 +509,9 @@ let playerView = new Vue({
save: (videoId) => {
toggleSavedVideo(videoId);
},
play: (videoId) => {
play: (videoId, playlistId = '') => {
loadingView.seen = true;
playVideo(videoId);
playVideo(videoId, playlistId);
},
loop: () => {
let player = document.getElementById('videoPlayer');
@ -519,6 +528,26 @@ let playerView = new Vue({
playlist: (playlistId) => {
showPlaylist(playlistId);
},
playlistLoopToggle: () => {
if (playerView.playlistLoop !== false) {
showToast('Playlist will no longer loop');
playerView.playlistLoop = false;
}
else {
showToast('Playlist will now loop');
playerView.playlistLoop = true;
}
},
playlistShuffleToggle: () => {
if (playerView.playlistShuffle !== false) {
showToast('Playlist will no longer shuffle');
playerView.playlistShuffle = false;
}
else{
showToast('Playlist will now shuffle');
playerView.playlistShuffle = true;
}
},
},
template: playerTemplate
});

View File

@ -459,7 +459,7 @@ function checkVideoUrls(video480p, video720p, videoAudio) {
default:
ft.log('480p is valid');
if (currentQuality === '720p' && typeof (video720p) === 'undefined') {
changeQuality(video480p);
playerView.currentQuality = '480p';
}
break;
}
@ -477,7 +477,7 @@ function checkVideoUrls(video480p, video720p, videoAudio) {
showToast('Found valid URL for 720p, but returned a 404. Video type might be available in the future.');
playerView.valid720p = false;
if (typeof (valid480) !== 'undefined') {
changeQuality(video480p, '480p');
playerView.currentQuality = '480p';
}
break;
case 403:

View File

@ -267,3 +267,7 @@ input[type=text] {
#comments {
background-color: #424242;
}
#miniPL {
background-color: #424242;
}

View File

@ -211,3 +211,7 @@ body {
#comments {
background: #eee;
}
#miniPL {
background-color: white;
}

View File

@ -46,7 +46,7 @@ a {
height: 3px;
background-color: #f44336;
display: block;
position: absolute;
position: fixed;
bottom: 0;
left: 0;
z-index: 1;

View File

@ -71,3 +71,78 @@
font-size: 12px;
cursor: pointer;
}
#miniPL {
width: 100%;
}
.miniPLVideo {
width: 100%;
cursor: pointer;
height: 70px;
}
.miniPLThumbnail {
width: 120px;
height: 80px;
margin-right: 5px;
float: left;
}
.miniPLIndex {
float: left;
position: relative;
width: 15px;
top: 30px;
margin-right: 35px;
}
.miniPLVideoTitle {
font-weight: bold;
}
.miniPLVideoChannelName {
font-size: 12px;
position: relative;
bottom: 10px;
}
#miniPLTitle {
font-weight: bold;
font-size: 20px;
margin-left: 20px;
padding-top: 25px;
cursor: pointer;
}
#miniPLChannelName {
font-size: 15px;
font-weight: normal;
}
#miniPLVideoList {
padding: 10px;
overflow: scroll;
height: 375px;
}
#miniPLLoop {
margin-left: 20px;
font-size: 20px;
cursor: pointer;
}
#miniPLShuffle {
margin-left: 20px;
font-size: 20px;
cursor: pointer;
}
#miniPLDropdown {
float: right;
cursor: pointer;
font-size: 20px;
position: relative;
top: 20px;
right: 15px;
}

View File

@ -1,6 +1,6 @@
<div v-if='seen'>
<div v-if='playerSeen'>
<video class="videoPlayer" id='videoPlayer' type="application/x-mpegURL" object-fit='cover' onmousemove="hideMouseTimeout()" onmouseleave="removeMouseTimeout()" onloadstart='checkVideoSettings()' onvolumechange='updateVolume()' controls="" :src='videoUrl' :poster="videoThumbnail" v-html="subtitleHtml">
<video class="videoPlayer" id='videoPlayer' type="application/x-mpegURL" object-fit='cover' onmousemove="hideMouseTimeout()" onmouseleave="removeMouseTimeout()" onloadstart='checkVideoSettings()' onvolumechange='updateVolume()' controls="" onended='playNextVideo()' :src='videoUrl' :poster="videoThumbnail" v-html="subtitleHtml">
</video>
</div>
<div v-else>
@ -79,6 +79,23 @@
<span v-html="description"></span>
</div>
</div>
<div v-if='playlistSeen' id='miniPL'>
<i id='miniPLDropdown' onclick='togglePlaylist()' class='fas fa-angle-down'></i>
<p id='miniPLTitle'><span v-on:click='playlist(playlistId)'>{{playlistTitle}}</span><br /><span id='miniPLChannelName' v-on:click='channel(playlistChannelId)'>{{playlistChannelName}} - {{playlistIndex}} / {{playlistTotal}}</span></p>
<i id='miniPLLoop' v-on:click='playlistLoopToggle' class='fas fa-redo'></i>
<i id='miniPLShuffle' v-on:click='playlistShuffleToggle' class='fas fa-random'></i>
<br /><br />
<div v-if='playlistShowList' id='miniPLVideoList'>
<div v-for='video in playlistVideoList'>
<div v-on:click='play(video.videoId, playlistId)' class='miniPLVideo'>
<span class='miniPLIndex'>{{video.index}}</span>
<img :src='video.thumbnail' class='miniPLThumbnail'></>
<p class='miniPLVideoTitle'>{{video.title}}</p>
<p class='miniPLVideoChannelName'>{{video.channelName}}</p>
</div>
</div>
</div>
</div>
<div id='showComments'>
Show Comments <i class="far fa-comments"></i> (Max of 100)
</div>

View File

@ -25,7 +25,7 @@
<p>{{videoCount}} videos - {{viewCount}} views - Last updated on {{lastUpdated}}</p>
<p v-html='description'></p>
<br />
<div class='playlistChannel'>
<div v-on:click='channel(channelId)' class='playlistChannel'>
<img :src='channelThumbnail' />
<h3>{{channelName}}</h3>
</div>

View File

@ -36,7 +36,7 @@
<div class='video'>
<div class='videoThumbnail'>
<img v-on:click='playlist(video.id)' :src='video.thumbnail' />
<div class='videoPlaylist'>
<div class='videoPlaylist' v-on:click='playlist(video.id)'>
<span class='videoPlaylistTotals'>{{video.videoCount}}</span>
<i class='fas fa-list-ol videoPlaylistIcon'></i>
</div>