mirror of https://github.com/FreeTubeApp/FreeTube
[WIP] Early Progress on Playlist support
This commit is contained in:
parent
a64ba1e2f0
commit
195eb20b03
|
@ -59,7 +59,6 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"autolinker": "^1.6.2",
|
||||
"dashjs": "^2.6.7",
|
||||
"dateformat": "^3.0.3",
|
||||
"electron-compile": "^6.4.2",
|
||||
"electron-squirrel-startup": "^1.0.0",
|
||||
|
|
|
@ -52,14 +52,15 @@
|
|||
<div id="sideNav">
|
||||
<div class="sideNavContainer">
|
||||
<ul>
|
||||
<li onclick='loadSubscriptions()'><i class="fas fa-users"></i> Subscriptions</li>
|
||||
<li onclick='showMostPopular()'><i class="fas fa-fire"></i> Most Popular</li>
|
||||
<li onclick='loadSubscriptions()'><i class="fas fa-rss"></i> Subscriptions</li>
|
||||
<li onclick='showMostPopular()'><i class="fas fa-users"></i> Most Popular</li>
|
||||
<li onclick='showSavedVideos()'><i class="fas fa-star"></i> Saved</li>
|
||||
<li onclick='showSavedVideos()'><i class="fas fa-list-ul"></i> Playlists</li>
|
||||
<li onclick='showHistory()'><i class="fas fa-history"></i> History</li>
|
||||
</ul>
|
||||
<hr />
|
||||
<ul>
|
||||
<li onclick='showSettings()'><i class="fas fa-cog"></i> Settings</li>
|
||||
<li onclick='showSettings()'><i class="fas fa-sliders-h"></i> Settings</li>
|
||||
<li onclick='showAbout()'><i class="far fa-question-circle"></i> About</li>
|
||||
</ul>
|
||||
<hr />
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/*
|
||||
/*
|
||||
This file is part of FreeTube.
|
||||
|
||||
FreeTube is free software: you can redistribute it and/or modify
|
||||
|
@ -200,7 +200,12 @@ let videoShortcutHandler = function(event) {
|
|||
};
|
||||
|
||||
let fullscreenVideo = function(event){
|
||||
$('.videoPlayer').get(0).webkitRequestFullscreen();
|
||||
if (document.webkitFullscreenElement !== null){
|
||||
document.webkitExitFullscreen();
|
||||
}
|
||||
else{
|
||||
$('.videoPlayer').get(0).webkitRequestFullscreen();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
148
src/js/layout.js
148
src/js/layout.js
|
@ -18,9 +18,9 @@ along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
|
|||
|
||||
|
||||
/*
|
||||
* File for main layout manipulation and general variable configuration.
|
||||
* There are some functions from other files that will probably need to be moved to here.
|
||||
*/
|
||||
* File for main layout manipulation and general variable configuration.
|
||||
* There are some functions from other files that will probably need to be moved to here.
|
||||
*/
|
||||
|
||||
// Add general variables. Please put all require statements here.
|
||||
const Datastore = require('nedb'); // database logic
|
||||
|
@ -28,6 +28,8 @@ window.$ = window.jQuery = require('jquery');
|
|||
const mustache = require('mustache'); // templating
|
||||
const dateFormat = require('dateformat'); // formatting dates
|
||||
|
||||
const RxPlayer = require('rx-player'); // formatting dates
|
||||
|
||||
// Used for finding links within text and making them clickable. Used mostly for video descriptions.
|
||||
const autolinker = require('autolinker');
|
||||
const electron = require('electron');
|
||||
|
@ -49,8 +51,8 @@ let dialog = require('electron').remote.dialog; // Used for opening file browser
|
|||
let toastTimeout; // Timeout for toast notifications.
|
||||
let mouseTimeout; // Timeout for hiding the mouse cursor on video playback
|
||||
|
||||
require.extensions['.html'] = function (module, filename) {
|
||||
module.exports = fs.readFileSync(filename, 'utf8');
|
||||
require.extensions['.html'] = function(module, filename) {
|
||||
module.exports = fs.readFileSync(filename, 'utf8');
|
||||
};
|
||||
|
||||
const subDb = new Datastore({
|
||||
|
@ -78,14 +80,14 @@ const settingsDb = new Datastore({
|
|||
checkDefaultSettings();
|
||||
|
||||
// Ppen links externally by default
|
||||
$(document).on('click', 'a[href^="http"]', (event) =>{
|
||||
$(document).on('click', 'a[href^="http"]', (event) => {
|
||||
let el = event.currentTarget;
|
||||
event.preventDefault();
|
||||
shell.openExternal(el.href);
|
||||
});
|
||||
|
||||
// Open links externally on middle click.
|
||||
$(document).on('auxclick', 'a[href^="http"]', (event) =>{
|
||||
$(document).on('auxclick', 'a[href^="http"]', (event) => {
|
||||
let el = event.currentTarget;
|
||||
event.preventDefault();
|
||||
shell.openExternal(el.href);
|
||||
|
@ -119,10 +121,10 @@ $(document).ready(() => {
|
|||
});
|
||||
|
||||
/**
|
||||
* Toggle the ability to view the side navigation bar.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
* Toggle the ability to view the side navigation bar.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function toggleSideNavigation() {
|
||||
const sideNav = document.getElementById('sideNav');
|
||||
const mainContainer = document.getElementById('main');
|
||||
|
@ -137,10 +139,10 @@ function toggleSideNavigation() {
|
|||
}
|
||||
|
||||
/**
|
||||
* Clears out the #main container to allow other information to be shown.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
* Clears out the #main container to allow other information to be shown.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function clearMainContainer() {
|
||||
const container = document.getElementById('main');
|
||||
container.innerHTML = '';
|
||||
|
@ -158,6 +160,7 @@ function startLoadingAnimation() {
|
|||
searchBar.disabled = true;
|
||||
goToVideoInput.disabled = true;
|
||||
}
|
||||
|
||||
function stopLoadingAnimation() {
|
||||
const loading = document.getElementById('loading');
|
||||
const sideNavDisabled = document.getElementById('sideNavDisabled');
|
||||
|
@ -171,12 +174,12 @@ function stopLoadingAnimation() {
|
|||
}
|
||||
|
||||
/**
|
||||
* Creates a div container in #main meant to be a container for video lists.
|
||||
*
|
||||
* @param {string} headerLabel - The header of the container. Not used for showing video recommendations.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
* Creates a div container in #main meant to be a container for video lists.
|
||||
*
|
||||
* @param {string} headerLabel - The header of the container. Not used for showing video recommendations.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function createVideoListContainer(headerLabel = '') {
|
||||
const videoListContainer = document.createElement("div");
|
||||
videoListContainer.id = 'videoListContainer';
|
||||
|
@ -192,11 +195,11 @@ function createVideoListContainer(headerLabel = '') {
|
|||
}
|
||||
|
||||
/**
|
||||
* Displays the about page to #main
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function showAbout(){
|
||||
* Displays the about page to #main
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function showAbout() {
|
||||
// Remove current information and display loading animation
|
||||
clearMainContainer();
|
||||
startLoadingAnimation();
|
||||
|
@ -212,14 +215,14 @@ function showAbout(){
|
|||
}
|
||||
|
||||
/**
|
||||
* Display a toast message in the bottom right corner of the page.
|
||||
* The toast automatically disappears after a timeout.
|
||||
*
|
||||
* @param {string} message - The toast message.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function showToast(message){
|
||||
* Display a toast message in the bottom right corner of the page.
|
||||
* The toast automatically disappears after a timeout.
|
||||
*
|
||||
* @param {string} message - The toast message.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function showToast(message) {
|
||||
let toast = document.getElementById('toast');
|
||||
let toastMessage = document.getElementById('toastMessage');
|
||||
|
||||
|
@ -235,27 +238,27 @@ function showToast(message){
|
|||
}
|
||||
|
||||
/**
|
||||
* Hide the toast notification from the page.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function hideToast(){
|
||||
* Hide the toast notification from the page.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function hideToast() {
|
||||
let toast = document.getElementById('toast');
|
||||
toast.style.opacity = 0;
|
||||
toast.style.visibility = 'hidden';
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a confirmation box before performing an action. The action will be performed
|
||||
* if the user clicks 'yes'.
|
||||
*
|
||||
* @param {string} message - The message to be displayed in the confirmation box
|
||||
* @param {function} performFunction - The function to be performed upon confirmation
|
||||
* @param {*} parameters - The parameters that will be sent to performFunction
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function confirmFunction(message, performFunction, parameters){
|
||||
* Displays a confirmation box before performing an action. The action will be performed
|
||||
* if the user clicks 'yes'.
|
||||
*
|
||||
* @param {string} message - The message to be displayed in the confirmation box
|
||||
* @param {function} performFunction - The function to be performed upon confirmation
|
||||
* @param {*} parameters - The parameters that will be sent to performFunction
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function confirmFunction(message, performFunction, parameters) {
|
||||
let confirmContainer = document.getElementById('confirmFunction');
|
||||
let confirmMessage = document.getElementById('confirmMessage');
|
||||
|
||||
|
@ -269,44 +272,43 @@ function confirmFunction(message, performFunction, parameters){
|
|||
}
|
||||
|
||||
/**
|
||||
* Hides the confirmation box. Happens when the user clicks on 'no'.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function hideConfirmFunction(){
|
||||
* Hides the confirmation box. Happens when the user clicks on 'no'.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function hideConfirmFunction() {
|
||||
let confirmContainer = document.getElementById('confirmFunction');
|
||||
confirmContainer.style.visibility = 'hidden';
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the mouse cursor after ~3 seconds. Used to hide the video when the user
|
||||
* hovers the mouse over the video player.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function hideMouseTimeout(){
|
||||
* Hide the mouse cursor after ~3 seconds. Used to hide the video when the user
|
||||
* hovers the mouse over the video player.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function hideMouseTimeout() {
|
||||
$('.videoPlayer')[0].style.cursor = 'default';
|
||||
clearTimeout(mouseTimeout);
|
||||
mouseTimeout = window.setTimeout(function(){
|
||||
mouseTimeout = window.setTimeout(function() {
|
||||
$('.videoPlayer')[0].style.cursor = 'none';
|
||||
}, 3150);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the timeout for the mouse cursor as a fallback.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function removeMouseTimeout(){
|
||||
* Remove the timeout for the mouse cursor as a fallback.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function removeMouseTimeout() {
|
||||
$('.videoPlayer')[0].style.cursor = 'default';
|
||||
clearTimeout(mouseTimeout);
|
||||
}
|
||||
|
||||
function showVideoOptions(element){
|
||||
if (element.nextElementSibling.style.display == 'none' || element.nextElementSibling.style.display == ''){
|
||||
element.nextElementSibling.style.display = 'inline-block'
|
||||
}
|
||||
else{
|
||||
element.nextElementSibling.style.display = 'none'
|
||||
}
|
||||
function showVideoOptions(element) {
|
||||
if (element.nextElementSibling.style.display == 'none' || element.nextElementSibling.style.display == '') {
|
||||
element.nextElementSibling.style.display = 'inline-block'
|
||||
} else {
|
||||
element.nextElementSibling.style.display = 'none'
|
||||
}
|
||||
}
|
||||
|
|
|
@ -143,7 +143,7 @@ function playVideo(videoId, videoThumbnail = '') {
|
|||
}
|
||||
|
||||
if (!useEmbedPlayer) {
|
||||
videoHtml = '<video data-dashjs-player class="videoPlayer" type="application/x-mpegURL" onmousemove="hideMouseTimeout()" onmouseleave="removeMouseTimeout()" controls="" src="' + defaultUrl + '" poster="' + videoThumbnail + '" autoplay>';
|
||||
videoHtml = '<video class="videoPlayer" type="application/x-mpegURL" onmousemove="hideMouseTimeout()" onmouseleave="removeMouseTimeout()" controls="" src="' + defaultUrl + '" poster="' + videoThumbnail + '" autoplay>';
|
||||
|
||||
|
||||
if(typeof(info.player_response.captions) === 'object'){
|
||||
|
|
332
src/js/videos.js
332
src/js/videos.js
|
@ -18,12 +18,12 @@ along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
|
|||
|
||||
|
||||
/**
|
||||
* Perform a search using the YouTube API. The search query is grabbed from the #search element.
|
||||
*
|
||||
* @param {string} nextPageToken - Optional: The page token to be inlcuded in the search.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
* Perform a search using the YouTube API. The search query is grabbed from the #search element.
|
||||
*
|
||||
* @param {string} nextPageToken - Optional: The page token to be inlcuded in the search.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function search(nextPageToken = '') {
|
||||
const query = document.getElementById('search').value;
|
||||
|
||||
|
@ -42,13 +42,45 @@ function search(nextPageToken = '') {
|
|||
youtubeAPI('search', {
|
||||
q: query,
|
||||
part: 'id',
|
||||
type: 'video',
|
||||
pageToken: nextPageToken,
|
||||
maxResults: 25,
|
||||
}, function (data){
|
||||
let grabDuration = getDuration(data.items);
|
||||
}, function(data) {
|
||||
console.log(data);
|
||||
|
||||
let channels = data.items.filter((item) => {
|
||||
if (item.id.kind === 'youtube#channel') {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
let playlists = data.items.filter((item) => {
|
||||
if (item.id.kind === 'youtube#playlist') {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
let videos = data.items.filter((item) => {
|
||||
if (item.id.kind === 'youtube#video') {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
console.log(channels);
|
||||
console.log(typeof(channels));
|
||||
console.log(playlists);
|
||||
|
||||
if(playlists.length > 0){
|
||||
displayPlaylists(playlists);
|
||||
}
|
||||
|
||||
if(channels.length > 0){
|
||||
displayChannels(channels);
|
||||
}
|
||||
|
||||
let grabDuration = getDuration(videos);
|
||||
|
||||
grabDuration.then((videoList) => {
|
||||
console.log(videoList);
|
||||
videoList.items.forEach(displayVideo);
|
||||
});
|
||||
|
||||
|
@ -61,30 +93,27 @@ function search(nextPageToken = '') {
|
|||
}
|
||||
|
||||
/**
|
||||
* Grab the duration of the videos
|
||||
*
|
||||
* @param {array} data - An array of videos to get the duration from
|
||||
*
|
||||
* @return {promise} - The list of videos with the duration included.
|
||||
*/
|
||||
function getDuration(data){
|
||||
* Grab the duration of the videos
|
||||
*
|
||||
* @param {array} data - An array of videos to get the duration from
|
||||
*
|
||||
* @return {promise} - The list of videos with the duration included.
|
||||
*/
|
||||
function getDuration(data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let videoIdList = '';
|
||||
|
||||
for(let i = 0; i < data.length; i++){
|
||||
if (videoIdList === ''){
|
||||
if(typeof(data[i]['id']) === 'string'){
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
if (videoIdList === '') {
|
||||
if (typeof(data[i]['id']) === 'string') {
|
||||
videoIdList = data[i]['id'];
|
||||
}
|
||||
else{
|
||||
} else {
|
||||
videoIdList = data[i]['id']['videoId'];
|
||||
}
|
||||
}
|
||||
else{
|
||||
if(typeof(data[i]['id']) === 'string'){
|
||||
} else {
|
||||
if (typeof(data[i]['id']) === 'string') {
|
||||
videoIdList = videoIdList + ', ' + data[i]['id'];
|
||||
}
|
||||
else{
|
||||
} else {
|
||||
videoIdList = videoIdList + ', ' + data[i]['id']['videoId'];
|
||||
}
|
||||
}
|
||||
|
@ -100,14 +129,14 @@ function getDuration(data){
|
|||
}
|
||||
|
||||
/**
|
||||
* Display a video on the page. Function is typically contained in a loop.
|
||||
*
|
||||
* @param {video} video - The video ID of the video to be displayed.
|
||||
* @param {string} listType - Optional: Specifies the list type of the video
|
||||
* Used for displaying the remove icon for history and saved videos.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
* Display a video on the page. Function is typically contained in a loop.
|
||||
*
|
||||
* @param {video} video - The video ID of the video to be displayed.
|
||||
* @param {string} listType - Optional: Specifies the list type of the video
|
||||
* Used for displaying the remove icon for history and saved videos.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function displayVideo(video, listType = '') {
|
||||
const videoSnippet = video.snippet;
|
||||
|
||||
|
@ -132,18 +161,18 @@ function displayVideo(video, listType = '') {
|
|||
};
|
||||
|
||||
// Includes text if the video is live.
|
||||
const liveText = (videoSnippet.liveBroadcastContent === 'live')? 'LIVE NOW' : '';
|
||||
const liveText = (videoSnippet.liveBroadcastContent === 'live') ? 'LIVE NOW' : '';
|
||||
const videoListTemplate = require('./templates/videoList.html');
|
||||
|
||||
mustache.parse(videoListTemplate);
|
||||
const rendered = mustache.render(videoListTemplate, {
|
||||
videoId: videoId,
|
||||
videoThumbnail: videoSnippet.thumbnails.medium.url,
|
||||
videoTitle: videoSnippet.title,
|
||||
channelName: videoSnippet.channelTitle,
|
||||
videoThumbnail: videoSnippet.thumbnails.medium.url,
|
||||
videoTitle: videoSnippet.title,
|
||||
channelName: videoSnippet.channelTitle,
|
||||
videoDescription: videoSnippet.description,
|
||||
channelId: videoSnippet.channelId,
|
||||
videoDuration: videoDuration,
|
||||
channelId: videoSnippet.channelId,
|
||||
videoDuration: videoDuration,
|
||||
publishedDate: publishedDate,
|
||||
liveText: liveText,
|
||||
deleteHtml: deleteHtml,
|
||||
|
@ -158,13 +187,95 @@ function displayVideo(video, listType = '') {
|
|||
}
|
||||
}
|
||||
|
||||
function displayChannels(channels) {
|
||||
let channelIds;
|
||||
|
||||
channels.forEach((channel) => {
|
||||
if (typeof(channelIds) === 'undefined') {
|
||||
channelIds = channel.id.channelId;
|
||||
} else {
|
||||
channelIds = channelIds + ',' + channel.id.channelId;
|
||||
}
|
||||
});
|
||||
|
||||
console.log(channelIds);
|
||||
|
||||
youtubeAPI('channels', {
|
||||
part: 'snippet,statistics',
|
||||
id: channelIds,
|
||||
}, function(data) {
|
||||
console.log(data);
|
||||
let items = data['items'].reverse();
|
||||
const videoListTemplate = require('./templates/channelList.html');
|
||||
|
||||
console.log(items);
|
||||
|
||||
items.forEach((item) => {
|
||||
mustache.parse(videoListTemplate);
|
||||
let rendered = mustache.render(videoListTemplate, {
|
||||
channelId: item.id,
|
||||
channelThumbnail: item.snippet.thumbnails.medium.url,
|
||||
channelName: item.snippet.title,
|
||||
channelDescription: item.snippet.description,
|
||||
subscriberCount: item.statistics.subscriberCount,
|
||||
videoCount: item.statistics.videoCount,
|
||||
});
|
||||
|
||||
$(rendered).insertBefore('#getNextPage');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function displayPlaylists(playlists) {
|
||||
let playlistIds;
|
||||
|
||||
playlists.forEach((playlist) => {
|
||||
if (typeof(playlistIds) === 'undefined') {
|
||||
playlistIds = playlist.id.playlistId;
|
||||
} else {
|
||||
playlistIds = playlistIds + ',' + playlist.id.playlistId;
|
||||
}
|
||||
});
|
||||
|
||||
console.log(playlistIds);
|
||||
|
||||
youtubeAPI('playlists', {
|
||||
part: 'snippet,contentDetails',
|
||||
id: playlistIds,
|
||||
}, function(data) {
|
||||
console.log(data);
|
||||
let items = data['items'].reverse();
|
||||
const playlistListTemplate = require('./templates/playlistList.html');
|
||||
|
||||
console.log(items);
|
||||
|
||||
items.forEach((item) => {
|
||||
let dateString = new Date(item.snippet.publishedAt);
|
||||
let publishedDate = dateFormat(dateString, "mmm dS, yyyy");
|
||||
|
||||
mustache.parse(playlistListTemplate);
|
||||
let rendered = mustache.render(playlistListTemplate, {
|
||||
channelId: item.snippet.channelId,
|
||||
channelName: item.snippet.channelTitle,
|
||||
playlistThumbnail: item.snippet.thumbnails.medium.url,
|
||||
playlistTitle: item.snippet.title,
|
||||
playlistDescription: item.snippet.description,
|
||||
videoCount: item.contentDetails.itemCount,
|
||||
publishedDate: publishedDate,
|
||||
});
|
||||
|
||||
$(rendered).insertBefore('#getNextPage');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the page token to the next page button during a video search.
|
||||
*
|
||||
* @param {string} nextPageToken - The page token to replace the button function.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
* Changes the page token to the next page button during a video search.
|
||||
*
|
||||
* @param {string} nextPageToken - The page token to replace the button function.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function addNextPage(nextPageToken) {
|
||||
let oldFetchButton = document.getElementById('getNextPage');
|
||||
|
||||
|
@ -185,19 +296,19 @@ function addNextPage(nextPageToken) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Grab the video recommendations for a video. This does not get recommendations based on what you watch,
|
||||
* as that would defeat the main purpose of using FreeTube. At any time you can check the video on HookTube
|
||||
* and compare the recommendations there. They should be nearly identical.
|
||||
*
|
||||
* @param {string} videoId - The video ID of the video to get recommendations from.
|
||||
*/
|
||||
* Grab the video recommendations for a video. This does not get recommendations based on what you watch,
|
||||
* as that would defeat the main purpose of using FreeTube. At any time you can check the video on HookTube
|
||||
* and compare the recommendations there. They should be nearly identical.
|
||||
*
|
||||
* @param {string} videoId - The video ID of the video to get recommendations from.
|
||||
*/
|
||||
function showVideoRecommendations(videoId) {
|
||||
youtubeAPI('search', {
|
||||
part: 'id',
|
||||
type: 'video',
|
||||
relatedToVideoId: videoId,
|
||||
maxResults: 15,
|
||||
}, function (data){
|
||||
}, function(data) {
|
||||
let grabDuration = getDuration(data.items);
|
||||
grabDuration.then((videoList) => {
|
||||
videoList.items.forEach((video) => {
|
||||
|
@ -208,10 +319,10 @@ function showVideoRecommendations(videoId) {
|
|||
mustache.parse(recommTemplate);
|
||||
const rendered = mustache.render(recommTemplate, {
|
||||
videoId: video.id,
|
||||
videoTitle: snippet.title,
|
||||
channelName: snippet.channelTitle,
|
||||
videoTitle: snippet.title,
|
||||
channelName: snippet.channelTitle,
|
||||
videoThumbnail: snippet.thumbnails.medium.url,
|
||||
videoDuration: videoDuration,
|
||||
videoDuration: videoDuration,
|
||||
publishedDate: dateFormat(snippet.publishedAt, "mmm dS, yyyy")
|
||||
});
|
||||
const recommendationHtml = $('#recommendations').html();
|
||||
|
@ -222,11 +333,11 @@ function showVideoRecommendations(videoId) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Check if a link is a valid YouTube/HookTube link and play that video. Gets input
|
||||
* from the #jumpToInput element.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
* Check if a link is a valid YouTube/HookTube link and play that video. Gets input
|
||||
* from the #jumpToInput element.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function parseVideoLink() {
|
||||
let input = document.getElementById('jumpToInput').value;
|
||||
|
||||
|
@ -250,19 +361,19 @@ function parseVideoLink() {
|
|||
}
|
||||
|
||||
/**
|
||||
* Convert duration into a more readable format
|
||||
*
|
||||
* @param {string} durationString - The string containing the video duration. Formated as 'PT12H34M56S'
|
||||
*
|
||||
* @return {string} - The formated string. Ex: 12:34:56
|
||||
*/
|
||||
function parseVideoDuration(durationString){
|
||||
* Convert duration into a more readable format
|
||||
*
|
||||
* @param {string} durationString - The string containing the video duration. Formated as 'PT12H34M56S'
|
||||
*
|
||||
* @return {string} - The formated string. Ex: 12:34:56
|
||||
*/
|
||||
function parseVideoDuration(durationString) {
|
||||
let match = durationString.match(/PT(\d+H)?(\d+M)?(\d+S)?/);
|
||||
let duration = '';
|
||||
|
||||
match = match.slice(1).map(function(x) {
|
||||
if (x != null) {
|
||||
return x.replace(/\D/, '');
|
||||
return x.replace(/\D/, '');
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -270,30 +381,25 @@ function parseVideoDuration(durationString){
|
|||
let minutes = (parseInt(match[1]) || 0);
|
||||
let seconds = (parseInt(match[2]) || 0);
|
||||
|
||||
if (hours != 0){
|
||||
if (hours != 0) {
|
||||
duration = hours + ':';
|
||||
}
|
||||
else{
|
||||
} else {
|
||||
duration = minutes + ':';
|
||||
}
|
||||
|
||||
if (hours != 0 && minutes < 10){
|
||||
if (hours != 0 && minutes < 10) {
|
||||
duration = duration + '0' + minutes + ':';
|
||||
}
|
||||
else if (hours != 0 && minutes > 10){
|
||||
} else if (hours != 0 && minutes > 10) {
|
||||
duration = duration + minutes + ':';
|
||||
}
|
||||
else if (hours != 0 && minutes == 0){
|
||||
} else if (hours != 0 && minutes == 0) {
|
||||
duration = duration + '00:';
|
||||
}
|
||||
|
||||
if (seconds == 0){
|
||||
if (seconds == 0) {
|
||||
duration = duration + '00';
|
||||
}
|
||||
else if (seconds < 10){
|
||||
} else if (seconds < 10) {
|
||||
duration = duration + '0' + seconds;
|
||||
}
|
||||
else{
|
||||
} else {
|
||||
duration = duration + seconds;
|
||||
}
|
||||
|
||||
|
@ -301,10 +407,10 @@ function parseVideoDuration(durationString){
|
|||
}
|
||||
|
||||
/**
|
||||
* Grab the most popular videos over the last couple of days and display them.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
* Grab the most popular videos over the last couple of days and display them.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function showMostPopular() {
|
||||
clearMainContainer();
|
||||
startLoadingAnimation();
|
||||
|
@ -323,7 +429,7 @@ function showMostPopular() {
|
|||
type: 'video',
|
||||
publishedAfter: d.toISOString(),
|
||||
maxResults: 50,
|
||||
}, function (data){
|
||||
}, function(data) {
|
||||
createVideoListContainer('Most Popular:');
|
||||
console.log(data);
|
||||
let grabDuration = getDuration(data.items);
|
||||
|
@ -337,13 +443,13 @@ function showMostPopular() {
|
|||
}
|
||||
|
||||
/**
|
||||
* Create a link of the video to HookTube or YouTube and copy it to the user's clipboard.
|
||||
*
|
||||
* @param {string} website - The website to watch the video on.
|
||||
* @param {string} videoId - The video ID of the video to add to the URL
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
* Create a link of the video to HookTube or YouTube and copy it to the user's clipboard.
|
||||
*
|
||||
* @param {string} website - The website to watch the video on.
|
||||
* @param {string} videoId - The video ID of the video to add to the URL
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function copyLink(website, videoId) {
|
||||
// Create the URL and copy to the clipboard.
|
||||
const url = 'https://' + website + '.com/watch?v=' + videoId;
|
||||
|
@ -352,19 +458,19 @@ function copyLink(website, videoId) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get the YouTube embeded player of a video as well as channel information..
|
||||
*
|
||||
* @param {string} videoId - The video ID of the video to get.
|
||||
*
|
||||
* @return {promise} - The HTML of the embeded player
|
||||
*/
|
||||
* Get the YouTube embeded player of a video as well as channel information..
|
||||
*
|
||||
* @param {string} videoId - The video ID of the video to get.
|
||||
*
|
||||
* @return {promise} - The HTML of the embeded player
|
||||
*/
|
||||
function getChannelAndPlayer(videoId) {
|
||||
console.log(videoId);
|
||||
return new Promise((resolve, reject) => {
|
||||
youtubeAPI('videos', {
|
||||
part: 'snippet,player',
|
||||
id: videoId,
|
||||
}, function (data){
|
||||
}, function(data) {
|
||||
let embedHtml = data.items[0].player.embedHtml;
|
||||
embedHtml = embedHtml.replace('src="', 'src="https:');
|
||||
embedHtml = embedHtml.replace('width="480px"', '');
|
||||
|
@ -376,20 +482,20 @@ function getChannelAndPlayer(videoId) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Check to see if the video URLs are valid. Change the video quality if one is not.
|
||||
* The API will grab video URLs, but they will sometimes return a 404. This
|
||||
* is why this check is needed. The video URL will typically be resolved over time.
|
||||
*
|
||||
* @param {string} video480p - The URL to the 480p video.
|
||||
* @param {string} video720p - The URL to the 720p video.
|
||||
*/
|
||||
* Check to see if the video URLs are valid. Change the video quality if one is not.
|
||||
* The API will grab video URLs, but they will sometimes return a 404. This
|
||||
* is why this check is needed. The video URL will typically be resolved over time.
|
||||
*
|
||||
* @param {string} video480p - The URL to the 480p video.
|
||||
* @param {string} video720p - The URL to the 720p video.
|
||||
*/
|
||||
function checkVideoUrls(video480p, video720p) {
|
||||
const currentQuality = $('#currentQuality').html();
|
||||
let buttonEmbed = document.getElementById('qualityEmbed');
|
||||
|
||||
let valid480 = false;
|
||||
|
||||
if (typeof(video480p) !== 'undefined'){
|
||||
if (typeof(video480p) !== 'undefined') {
|
||||
let get480pUrl = fetch(video480p);
|
||||
get480pUrl.then((status) => {
|
||||
switch (status.status) {
|
||||
|
@ -412,7 +518,7 @@ function checkVideoUrls(video480p, video720p) {
|
|||
break;
|
||||
default:
|
||||
console.log('480p is valid');
|
||||
if (currentQuality === '720p' && typeof(video720p) === 'undefined'){
|
||||
if (currentQuality === '720p' && typeof(video720p) === 'undefined') {
|
||||
changeQuality(video480p);
|
||||
}
|
||||
break;
|
||||
|
@ -420,7 +526,7 @@ function checkVideoUrls(video480p, video720p) {
|
|||
});
|
||||
}
|
||||
|
||||
if (typeof(video720p) !== 'undefined'){
|
||||
if (typeof(video720p) !== 'undefined') {
|
||||
let get720pUrl = fetch(video720p);
|
||||
get720pUrl.then((status) => {
|
||||
switch (status.status) {
|
||||
|
@ -430,7 +536,7 @@ function checkVideoUrls(video480p, video720p) {
|
|||
$(document).on('click', '#quality720p', (event) => {
|
||||
changeQuality('');
|
||||
});
|
||||
if (typeof(valid480) !== 'undefined'){
|
||||
if (typeof(valid480) !== 'undefined') {
|
||||
changeQuality(video480p, '480p');
|
||||
}
|
||||
break;
|
||||
|
@ -444,7 +550,7 @@ function checkVideoUrls(video480p, video720p) {
|
|||
break;
|
||||
default:
|
||||
console.log('720p is valid');
|
||||
if (currentQuality === '720p'){
|
||||
if (currentQuality === '720p') {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -27,6 +27,21 @@ iframe {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.channelThumbnail {
|
||||
width: 140px;
|
||||
height: 140px;
|
||||
float: left;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
left: 60px;
|
||||
}
|
||||
|
||||
.channelThumbnail img{
|
||||
width: 100%;
|
||||
border-radius: 200px 200px 200px 200px;
|
||||
-webkit-border-radius: 200px 200px 200px 200px;
|
||||
}
|
||||
|
||||
.videoOptions {
|
||||
float: right;
|
||||
width: 50px;
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
<div class='video'>
|
||||
<div class='channelThumbnail'>
|
||||
<img onclick='goToChannel("{{channelId}}")' src={{channelThumbnail}} />
|
||||
</div>
|
||||
<p onclick='goToChannel("{{channelId}}")' class='videoTitle'>{{channelName}}</p>
|
||||
<p onclick='goToChannel("{{channelId}}")' class='channelName'>{{subscriberCount}} subscribers - {{videoCount}} videos</p>
|
||||
<p onclick='goToChannel("{{channelId}}")' class='videoDescription'>{{channelDescription}}</p>
|
||||
</div>
|
||||
<hr />
|
|
@ -0,0 +1,10 @@
|
|||
<div class='video'>
|
||||
<div class='videoThumbnail'>
|
||||
<img onclick='console.log("Not Implemented")' src={{playlistThumbnail}} />
|
||||
</div>
|
||||
<p onclick='goToChannel("{{channelId}}")' class='videoTitle'>{{playlistTitle}}</p>
|
||||
<p onclick='goToChannel("{{channelId}}")' class='channelName'>{{channelName}} - {{publishedDate}}</p>
|
||||
<p onclick='goToChannel("{{channelId}}")' class='videoDescription'>{{playlistDescription}}</p>
|
||||
<p>VIEW FULL PLAYLIST ({{videoCount}} videos)</p>
|
||||
</div>
|
||||
<hr />
|
|
@ -0,0 +1,4 @@
|
|||
<video class="videoPlayer" onmousemove="hideMouseTimeout()" onmouseleave="removeMouseTimeout()" controls="" poster="{{videoThumbnail}}" autoplay>
|
||||
<source src="{{videoUrl}}" type="video/mp4">
|
||||
{{subtitleElements}}
|
||||
</video>
|
Loading…
Reference in New Issue