Features: Video Duration, Double click to full screen and more.

Loading Subscriptions is MUCH faster.
This commit is contained in:
Preston 2018-03-15 10:59:50 -04:00
parent 5729aa2c8b
commit 1ac6ce7dce
15 changed files with 425 additions and 180 deletions

View File

@ -65,7 +65,6 @@
"jquery": "^3.3.1",
"mustache": "^2.3.0",
"nedb": "^1.8.0",
"node-async-loop": "^1.2.2",
"opml-to-json": "0.0.3",
"youtube-dl": "^1.12.2"
}

View File

Before

Width:  |  Height:  |  Size: 361 KiB

After

Width:  |  Height:  |  Size: 361 KiB

View File

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -80,8 +80,12 @@ function goToChannel(channelId) {
order: 'date',
}, function (data) {
// Display recent uploads to #main
data['items'].forEach((video) => {
displayVideo(video);
let grabDuration = getDuration(data.items);
grabDuration.then((videoList) => {
videoList.items.forEach((video) => {
displayVideo(video, 'history');
});
});
});
});

View File

@ -199,6 +199,10 @@ let videoShortcutHandler = function(event) {
}
};
let fullscreenVideo = function(event){
$('.videoPlayer').get(0).webkitRequestFullscreen()
}
/**
* ---------------------------
* Bind click events
@ -208,6 +212,8 @@ $(document).on('click', '#showComments', showComments);
$(document).on('click', '.videoPlayer', playPauseVideo);
$(document).on('dblclick', '.videoPlayer', fullscreenVideo);
$(document).on('keydown', videoShortcutHandler);
$(document).on('click', '#confirmNo', hideConfirmFunction);

View File

@ -81,8 +81,12 @@ function showHistory(){
maxResults: 50,
}, function (data) {
createVideoListContainer('Watch History:');
data['items'].forEach((video) => {
displayVideo(video, 'history');
let grabDuration = getDuration(data.items);
grabDuration.then((videoList) => {
videoList.items.forEach((video) => {
displayVideo(video, 'history');
});
});
stopLoadingAnimation()
});

View File

@ -69,17 +69,6 @@ function playVideo(videoId) {
return;
}
const checkSubscription = isSubscribed(channelId);
// Change the subscribe button text depending on if the user has subscribed to the channel or not.
checkSubscription.then((results) => {
if (results === false) {
subscribeText = 'SUBSCRIBE';
} else {
subscribeText = 'UNSUBSCRIBE';
}
});
const checkSavedVideo = videoIsSaved(videoId);
// Change the save button icon and text depending on if the user has saved the video or not.
@ -94,7 +83,7 @@ function playVideo(videoId) {
});
/*
* FreeTube calls youtube-dl server to grab the direct video URL.
* FreeTube calls youtube-dl to grab the direct video URL.
*/
youtubedlGetInfo(videoId, (info) => {
console.log(info);
@ -109,7 +98,6 @@ function playVideo(videoId) {
let dateString = info['upload_date'];
dateString = [dateString.slice(0, 4), '-', dateString.slice(4)].join('');
dateString = [dateString.slice(0, 7), '-', dateString.slice(7)].join('');
console.log(dateString);
const publishedDate = dateFormat(dateString, "mmm dS, yyyy");
// Figure out the width for the like/dislike bar.
@ -127,7 +115,6 @@ function playVideo(videoId) {
// Grab all subtitles
Object.keys(videoSubtitles).forEach((subtitle) => {
console.log(subtitle);
subtitleLabel = subtitle.toUpperCase();
subtitleUrl = videoSubtitles[subtitle]['url'];
@ -142,7 +129,6 @@ function playVideo(videoId) {
// Search through the returned object to get the 480p and 720p video URLs (If available)
Object.keys(videoUrls).forEach((key) => {
console.log(key);
switch (videoUrls[key]['format_id']) {
case '18':
video480p = videoUrls[key]['url'];
@ -173,11 +159,18 @@ function playVideo(videoId) {
if (!useEmbedPlayer) {
videoHtml = '<video class="videoPlayer" onmousemove="hideMouseTimeout()" onmouseleave="removeMouseTimeout()" controls="" src="' + defaultUrl + '" poster="' + videoThumbnail + '" autoplay>' + subtitleHtml + '</video>';
console.log(videoHtml);
}
console.log(defaultUrl);
const checkSubscription = isSubscribed(channelId);
// Change the subscribe button text depending on if the user has subscribed to the channel or not.
checkSubscription.then((results) => {
if (results === false) {
subscribeText = 'SUBSCRIBE';
} else {
subscribeText = 'UNSUBSCRIBE';
}
});
// API Request
youtubeAPI('channels', {

View File

@ -143,8 +143,11 @@ function showSavedVideos(){
}, (data) => {
// Render the videos to the screen
createVideoListContainer('Saved Videos:');
data.items.forEach((video) => {
displayVideo(video, 'saved');
let grabDuration = getDuration(data.items);
grabDuration.then((videoList) => {
videoList.items.forEach((video) => {
displayVideo(video, 'saved');
});
});
stopLoadingAnimation();
});

View File

@ -18,23 +18,23 @@ along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
/*
* File for all functions related to subscriptions.
*/
* File for all functions related to subscriptions.
*/
/**
* Add a channel to the user's subscription database.
*
* @param {string} channelId - The channel ID to add to the subscriptions database.
*
* @return {Void}
*/
* Add a channel to the user's subscription database.
*
* @param {string} channelId - The channel ID to add to the subscriptions database.
*
* @return {Void}
*/
function addSubscription(channelId, useToast = true) {
console.log(channelId);
// Request YouTube API
youtubeAPI('channels', {
part: 'snippet',
id: channelId,
}, function (data){
}, function(data) {
const channelInfo = data['items'][0]['snippet'];
const channelName = channelInfo['title'];
const thumbnail = channelInfo['thumbnails']['high']['url'];
@ -47,7 +47,7 @@ function addSubscription(channelId, useToast = true) {
// Refresh the list of subscriptions on the side navigation bar.
subDb.insert(channel, (err, newDoc) => {
if (useToast){
if (useToast) {
showToast('Added ' + channelName + ' to subscriptions.');
displaySubs();
}
@ -56,12 +56,12 @@ function addSubscription(channelId, useToast = true) {
}
/**
* Remove a channel from the subscriptions database.
*
* @param {string} channelId - The channel ID to be removed.
*
* @return {Void}
*/
* Remove a channel from the subscriptions database.
*
* @param {string} channelId - The channel ID to be removed.
*
* @return {Void}
*/
function removeSubscription(channelId) {
subDb.remove({
channelId: channelId
@ -73,10 +73,10 @@ function removeSubscription(channelId) {
}
/**
* Load the recent uploads of the user's subscriptions.
*
* @return {Void}
*/
* Load the recent uploads of the user's subscriptions.
*
* @return {Void}
*/
function loadSubscriptions() {
clearMainContainer();
const loading = document.getElementById('loading');
@ -89,8 +89,82 @@ function loadSubscriptions() {
// Welcome to callback hell, we hope you enjoy your stay.
subscriptions.then((results) => {
let channelId = '';
let videoList = [];
if (results.length > 0) {
let counter = 0;
for (let i = 0; i < results.length; i++) {
channelId = results[i]['channelId'];
youtubeAPI('search', {
part: 'snippet',
channelId: channelId,
type: 'video',
maxResults: 15,
order: 'date',
}, (data) => {
console.log(data);
videoList = videoList.concat(data.items);
counter++;
if (counter === results.length) {
videoList.sort((a, b) => {
const date1 = Date.parse(a.snippet.publishedAt);
const date2 = Date.parse(b.snippet.publishedAt);
return date2.valueOf() - date1.valueOf();
});
// Render the videos to the application.
createVideoListContainer('Latest Subscriptions:');
// The YouTube website limits the subscriptions to 100 before grabbing more so we only show 100
// to keep the app running at a good speed.
if (videoList.length < 50) {
let grabDuration = getDuration(videoList.slice(0, 49));
grabDuration.then((list) => {
list.items.forEach((video) => {
displayVideo(video);
});
});
} else {
console.log(videoList);
let finishedList = [];
let firstBatchDuration = getDuration(videoList.slice(0, 49));
firstBatchDuration.then((list1) => {
finishedList = finishedList.concat(list1.items);
let secondBatchDuration = getDuration(videoList.slice(50, 99));
secondBatchDuration.then((list2) => {
finishedList = finishedList.concat(list2.items);
finishedList.forEach((video) => {
displayVideo(video);
});
stopLoadingAnimation();
});
});
}
}
}
);
}
} else {
// User has no subscriptions. Display message.
const container = document.getElementById('main');
stopLoadingAnimation();
container.innerHTML = `<h2 class="message">Your Subscription list is currently empty. Start adding subscriptions
to see them here.<br /><br /><i class="far fa-frown" style="font-size: 200px"></i></h2>`;
}
// Yes, This function is the thing that needs to most improvment
/*
if (results.length > 0) {
showToast('Getting Subscriptions. This may take a while...');
@ -101,68 +175,84 @@ function loadSubscriptions() {
* while then sorting them afterwards, this was my best solution at the time. I'm sure someone more
* experienced in Node can help out with this.
*/
asyncLoop(results, (sub, next) => {
const channelId = sub['channelId'];
/*
asyncLoop(results, (sub, next) => {
const channelId = sub['channelId'];
/*
* Grab the channels 15 most recent uploads. Typically this should be enough.
* This number can be changed if we feel necessary.
*/
youtubeAPI('search', {
part: 'snippet', // Try getting content details for video duration in the near future.
channelId: channelId,
type: 'video',
maxResults: 15,
order: 'date',
}, function (data){
videoList = videoList.concat(data['items']);
// Iterate through the next object in the loop.
next();
});
}, (err) => {
// Sort the videos by date
videoList.sort((a, b) => {
const date1 = Date.parse(a.snippet.publishedAt);
const date2 = Date.parse(b.snippet.publishedAt);
/*
* Grab the channels 15 most recent uploads. Typically this should be enough.
* This number can be changed if we feel necessary.
*/
/*
youtubeAPI('search', {
part: 'snippet',
channelId: channelId,
type: 'video',
maxResults: 15,
order: 'date',
}, (data) => {
videoList = videoList.concat(data.items);
return date2.valueOf() - date1.valueOf();
});
next();
});
}, (err) => {
// Sort the videos by date
videoList.sort((a, b) => {
const date1 = Date.parse(a.snippet.publishedAt);
const date2 = Date.parse(b.snippet.publishedAt);
// Render the videos to the application.
createVideoListContainer('Latest Subscriptions:');
return date2.valueOf() - date1.valueOf();
});
// The YouTube website limits the subscriptions to 100 before grabbing more so we only show 100
// to keep the app running at a good speed.
if(videoList.length < 100){
videoList.forEach((video) => {
console.log('Getting all videos');
displayVideo(video);
// Render the videos to the application.
createVideoListContainer('Latest Subscriptions:');
// The YouTube website limits the subscriptions to 100 before grabbing more so we only show 100
// to keep the app running at a good speed.
if(videoList.length < 50){
let grabDuration = getDuration(videoList.slice(0,49));
grabDuration.then((list) => {
list.items.forEach((video) => {
displayVideo(video);
});
});
}
else{
let finishedList = []
let firstBatchDuration = getDuration(videoList.slice(0, 49));
firstBatchDuration.then((list1) => {
finishedList = finishedList.concat(list1.items);
let secondBatchDuration = getDuration(videoList.slice(50, 99));
secondBatchDuration.then((list2) => {
finishedList = finishedList.concat(list2.items);
finishedList.forEach((video) => {
displayVideo(video);
});
});
});
}
stopLoadingAnimation()
});
}
else{
console.log('Getting top 100 videos');
for(let i = 0; i < 100; i++){
displayVideo(videoList[i]);
}
}
stopLoadingAnimation()
});
} else {
// User has no subscriptions. Display message.
const container = document.getElementById('main');
stopLoadingAnimation()
} else {
// User has no subscriptions. Display message.
const container = document.getElementById('main');
stopLoadingAnimation()
container.innerHTML = `<h2 class="message">Your Subscription list is currently empty. Start adding subscriptions
to see them here.<br /><br /><i class="far fa-frown" style="font-size: 200px"></i></h2>`;
}
container.innerHTML = `<h2 class="message">Your Subscription list is currently empty. Start adding subscriptions
to see them here.<br /><br /><i class="far fa-frown" style="font-size: 200px"></i></h2>`;
}
});*/
});
}
/**
* Get the list of subscriptions from the user's subscription database.
*
* @return {promise} The list of subscriptions.
*/
* Get the list of subscriptions from the user's subscription database.
*
* @return {promise} The list of subscriptions.
*/
function returnSubscriptions() {
return new Promise((resolve, reject) => {
subDb.find({}, (err, subs) => {
@ -172,10 +262,10 @@ function returnSubscriptions() {
}
/**
* Display the list of subscriptions on the side navigation bar.
*
* @return {Void}
*/
* Display the list of subscriptions on the side navigation bar.
*
* @return {Void}
*/
function displaySubs() {
const subList = document.getElementById('subscriptions');
@ -205,11 +295,11 @@ function displaySubs() {
}
/**
* Adds / Removes a subscription based on if the channel is in the database or not.
* @param {string} channelId - The channel ID to check
*
* @return {Void}
*/
* Adds / Removes a subscription based on if the channel is in the database or not.
* @param {string} channelId - The channel ID to check
*
* @return {Void}
*/
function toggleSubscription(channelId) {
event.stopPropagation();
@ -219,12 +309,12 @@ function toggleSubscription(channelId) {
checkIfSubscribed.then((results) => {
if (results === false) {
if(subscribeButton != null){
if (subscribeButton != null) {
subscribeButton.innerHTML = 'UNSUBSCRIBE';
}
addSubscription(channelId);
} else {
if(subscribeButton != null){
if (subscribeButton != null) {
subscribeButton.innerHTML = 'SUBSCRIBE';
}
removeSubscription(channelId);
@ -233,15 +323,17 @@ function toggleSubscription(channelId) {
}
/**
* Check if the user is subscribed to a channel or not.
*
* @param {string} channelId - The channel ID to check
*
* @return {promise} - A boolean value if the channel is currently subscribed or not.
*/
* Check if the user is subscribed to a channel or not.
*
* @param {string} channelId - The channel ID to check
*
* @return {promise} - A boolean value if the channel is currently subscribed or not.
*/
function isSubscribed(channelId) {
return new Promise((resolve, reject) => {
subDb.find({channelId: channelId}, (err, docs) => {
subDb.find({
channelId: channelId
}, (err, docs) => {
if (jQuery.isEmptyObject(docs)) {
resolve(false);
} else {

View File

@ -41,20 +41,64 @@ function search(nextPageToken = '') {
youtubeAPI('search', {
q: query,
part: 'id, snippet',
part: 'id',
type: 'video',
pageToken: nextPageToken,
maxResults: 25,
}, function (data){
let grabDuration = getDuration(data.items);
grabDuration.then((videoList) => {
videoList.items.forEach(displayVideo);
});
if (nextPageToken === '') {
createVideoListContainer('Search results:');
stopLoadingAnimation();
}
data.items.forEach(displayVideo);
addNextPage(data.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){
return new Promise((resolve, reject) => {
let videoIdList = '';
for(let i = 0; i < data.length; i++){
if (videoIdList === ''){
if(typeof(data[i]['id']) === 'string'){
videoIdList = data[i]['id'];
}
else{
videoIdList = data[i]['id']['videoId'];
}
}
else{
if(typeof(data[i]['id']) === 'string'){
videoIdList = videoIdList + ', ' + data[i]['id'];
}
else{
videoIdList = videoIdList + ', ' + data[i]['id']['videoId'];
}
}
}
youtubeAPI('videos', {
part: 'snippet, contentDetails',
id: videoIdList
}, (data) => {
resolve(data);
});
});
}
/**
* Display a video on the page. Function is typically contained in a loop.
*
@ -66,9 +110,14 @@ function search(nextPageToken = '') {
*/
function displayVideo(video, listType = '') {
const videoSnippet = video.snippet;
const videoDuration = parseVideoDuration(video.contentDetails.duration);
//const videoDuration = '00:00';
// Grab the published date for the video and convert to a user readable state.
const dateString = new Date(videoSnippet.publishedAt);
const publishedDate = dateFormat(dateString, "mmm dS, yyyy");
const searchMenu = $('#videoListContainer').html();
const videoId = video.id;
@ -94,6 +143,7 @@ function displayVideo(video, listType = '') {
channelName: videoSnippet.channelTitle,
videoDescription: videoSnippet.description,
channelId: videoSnippet.channelId,
videoDuration: videoDuration,
publishedDate: publishedDate,
liveText: liveText,
deleteHtml: deleteHtml,
@ -143,26 +193,30 @@ function addNextPage(nextPageToken) {
*/
function showVideoRecommendations(videoId) {
youtubeAPI('search', {
part: 'snippet',
part: 'id',
type: 'video',
relatedToVideoId: videoId,
maxResults: 15,
}, function (data){
const recommendations = data.items;
recommendations.forEach((data) => {
const snippet = data.snippet;
let grabDuration = getDuration(data.items);
grabDuration.then((videoList) => {
videoList.items.forEach((video) => {
const snippet = video.snippet;
const videoDuration = parseVideoDuration(video.contentDetails.duration);
const recommTemplate = require('./templates/recommendations.html')
mustache.parse(recommTemplate);
const rendered = mustache.render(recommTemplate, {
videoId: data.id.videoId,
videoTitle: snippet.title,
channelName: snippet.channelTitle,
videoThumbnail: snippet.thumbnails.medium.url,
publishedDate: dateFormat(snippet.publishedAt, "mmm dS, yyyy")
const recommTemplate = require('./templates/recommendations.html')
mustache.parse(recommTemplate);
const rendered = mustache.render(recommTemplate, {
videoId: video.id,
videoTitle: snippet.title,
channelName: snippet.channelTitle,
videoThumbnail: snippet.thumbnails.medium.url,
videoDuration: videoDuration,
publishedDate: dateFormat(snippet.publishedAt, "mmm dS, yyyy")
});
const recommendationHtml = $('#recommendations').html();
$('#recommendations').html(recommendationHtml + rendered);
});
const recommendationHtml = $('#recommendations').html();
$('#recommendations').html(recommendationHtml + rendered);
});
});
}
@ -195,6 +249,57 @@ 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){
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/, '');
}
});
let hours = (parseInt(match[0]) || 0);
let minutes = (parseInt(match[1]) || 0);
let seconds = (parseInt(match[2]) || 0);
if (hours != 0){
duration = hours + ':';
}
else{
duration = minutes + ':';
}
if (hours != 0 && minutes < 10){
duration = duration + '0' + minutes + ':';
}
else if (hours != 0 && minutes > 10){
duration = duration + minutes + ':';
}
else if (hours != 0 && minutes == 0){
duration = duration + '00:';
}
if (seconds == 0){
duration = duration + '00';
}
else if (seconds < 10){
duration = duration + '0' + seconds;
}
else{
duration = duration + seconds;
}
return duration;
}
/**
* Grab the most popular videos over the last couple of days and display them.
*
@ -213,15 +318,21 @@ function showMostPopular() {
// Applications grab these. Videos in the 'Trending' tab on YouTube will be different.
// And there is no way to grab those videos.
youtubeAPI('search', {
part: 'snippet',
part: 'id',
order: 'viewCount',
type: 'video',
publishedAfter: d.toISOString(),
maxResults: 50,
}, function (data){
createVideoListContainer('Most Popular:');
console.log(data);
let grabDuration = getDuration(data.items);
grabDuration.then((videoList) => {
console.log(videoList);
videoList.items.forEach(displayVideo);
});
stopLoadingAnimation();
data.items.forEach(displayVideo);
});
}

View File

@ -1,14 +1,14 @@
iframe{
iframe {
width: 100%;
height: 41.25vw;
}
#main{
margin-top: 80px;
margin-left: 250px;
#main {
margin-top: 80px;
margin-left: 250px;
}
.video{
.video {
width: 95%;
max-width: 1000px;
height: 145px;
@ -16,68 +16,90 @@ iframe{
overflow: hidden;
}
.videoThumbnail{
.videoThumbnail {
width: 275px;
height: 160px;
float: left;
cursor: pointer;
}
.videoTitle{
.videoThumbnail img{
width: 100%;
}
.videoTitle {
font-weight: bold;
margin-left: 285px;
margin-top: 5px;
cursor: pointer;
}
.channelName{
.channelName {
margin-left: 285px;
font-size: 14px;
cursor: pointer;
}
.videoDescription{
.videoDescription {
margin-left: 285px;
font-size: 13px;
cursor: pointer;
height: 45px;
overflow: hidden;
}
.videoPlayer{width: 100%;}
.statistics{
padding: 20px;
padding-bottom: 45px;
.videoDuration {
display: inline-block;
float: right;
position: relative;
bottom: 36px;
color: white;
background-color: black;
opacity: 0.7;
padding: 2px;
font-size: 13px;
text-align: right;
}
.title{
.videoPlayer {
width: 100%;
}
.statistics {
padding: 20px;
padding-bottom: 45px;
}
.title {
font-weight: bold;
font-size: 25px;
}
.views{
.views {
margin-top: -10px;
float: left;
}
.details{
.details {
padding: 15px;
}
.likeContainer{
.likeContainer {
width: 300px;
float: right;
}
.likes{
.likes {
float: left;
font-size: 12px;
}
.dislikes{
.dislikes {
float: right;
font-size: 12px;
}
.dislikeBar{
.dislikeBar {
background-color: #9E9E9E;
width: 300px;
height: 5px;
@ -86,14 +108,14 @@ iframe{
-webkit-border-radius: 200px 200px 200px 200px;
}
.likeBar{
.likeBar {
height: 5px;
background-color: #2196F3;
border-radius: 200px 200px 200px 200px;
-webkit-border-radius: 200px 200px 200px 200px;
}
#channelIcon{
#channelIcon {
float: left;
width: 80px;
border-radius: 200px 200px 200px 200px;
@ -115,26 +137,26 @@ iframe{
cursor: pointer;
}
#channelName{
#channelName {
font-weight: bold;
margin-left: 95px;
font-size: 16px;
cursor: pointer;
}
#publishDate{
#publishDate {
margin-left: 95px;
font-size: 13px;
margin-top: -10px;
}
#description{
#description {
white-space: pre-line;
max-height: 200px;
overflow-y: auto;
}
.playerSubButton{
.playerSubButton {
float: right;
width: 125px;
height: 50px;
@ -144,7 +166,7 @@ iframe{
cursor: pointer;
}
.smallButton{
.smallButton {
float: right;
height: 30px;
font-size: 10px;
@ -156,15 +178,15 @@ iframe{
cursor: pointer;
}
.videoQuality{
.videoQuality {
width: 42px;
}
.videoQuality:hover .qualityTypes{
.videoQuality:hover .qualityTypes {
visibility: visible;
}
.qualityTypes{
.qualityTypes {
visibility: hidden;
width: 72px;
position: relative;
@ -172,27 +194,27 @@ iframe{
right: 15px;
}
.qualityTypes ul{
.qualityTypes ul {
list-style-type: none;
position: relative;
right: 24px;
}
.qualityTypes ul li{
.qualityTypes ul li {
width: 72px;
position: relative;
right: 15px;
}
.videoSpeed{
.videoSpeed {
width: 42px;
}
.videoSpeed:hover .speedTypes{
.videoSpeed:hover .speedTypes {
visibility: visible;
}
.speedTypes{
.speedTypes {
visibility: hidden;
width: 72px;
position: relative;
@ -200,19 +222,19 @@ iframe{
right: 15px;
}
.speedTypes ul{
.speedTypes ul {
list-style-type: none;
position: relative;
right: 24px;
}
.speedTypes ul li{
.speedTypes ul li {
width: 72px;
position: relative;
right: 15px;
}
#showComments{
#showComments {
text-align: center;
height: 40px;
line-height: 40px;
@ -220,33 +242,38 @@ iframe{
margin-bottom: 15px;
}
#recommendations{
#recommendations {
width: 100%;
}
.recommendVideo{
.recommendVideo {
cursor: pointer;
height: 150px;
}
.recommendThumbnail{
.recommendThumbnail {
width: 250px;
height: 145px;
float: left;
}
.recommendTitle{
.recommendThumbnail img{
width: 100%;
}
.recommendTitle {
font-size: 16px;
font-weight: bold;
margin-left: 260px;
}
.recommendChannel{
.recommendChannel {
margin-left: 260px;
font-size: 14px;
margin-top: -10px;
}
.recommendDate{
.recommendDate {
margin-left: 260px;
font-size: 11px;
}

View File

@ -1,5 +1,8 @@
<div class='recommendVideo' onclick='playVideo("{{videoId}}")'>
<image class='recommendThumbnail' src='{{videoThumbnail}}'></image>
<div class='recommendThumbnail'>
<img src='{{videoThumbnail}}'></img>
<p onclick='playVideo("{{videoId}}")' class='videoDuration'>{{videoDuration}}</p>
</div>
<p class='recommendTitle'>{{videoTitle}}</p>
<p class='recommendChannel'>{{channelName}}</p>
<p class='recommendDate'>{{publishedDate}}</p>

View File

@ -1,6 +1,9 @@
<div class='video'>
{{{deleteHtml}}}
<img onclick='playVideo("{{videoId}}")' src={{videoThumbnail}} class='videoThumbnail' />
<div class='videoThumbnail'>
<img onclick='playVideo("{{videoId}}")' src={{videoThumbnail}} />
<p onclick='playVideo("{{videoId}}")' class='videoDuration'>{{videoDuration}}</p>
</div>
<p onclick='playVideo("{{videoId}}")' class='videoTitle'>{{videoTitle}}</p>
<p onclick='goToChannel("{{channelId}}")' class='channelName'>{{channelName}} - {{publishedDate}}</p>
<p onclick='playVideo("{{videoId}}")' class='videoDescription'>{{videoDescription}}</p>