From 60cd21d9381efc9e7a4bb12442a6e171d82d4d86 Mon Sep 17 00:00:00 2001 From: rainbow napkin Date: Tue, 6 May 2025 21:13:54 -0400 Subject: [PATCH] Youtube videos now refresh metadata 10 seconds before playback starts. --- src/app/channel/media/queue.js | 37 +++++++++++++++++-- .../channel/media/queuedMediaSchema.js | 2 +- src/utils/media/yanker.js | 16 ++++++++ www/js/channel/player.js | 12 ++++++ 4 files changed, 62 insertions(+), 5 deletions(-) diff --git a/src/app/channel/media/queue.js b/src/app/channel/media/queue.js index 8f9c469..3adcaf6 100644 --- a/src/app/channel/media/queue.js +++ b/src/app/channel/media/queue.js @@ -42,6 +42,8 @@ module.exports = class{ this.syncTimer = null; //Create variable to hold next playing item timer this.nextTimer = null; + //Create vairable to hold pre-switch timer + this.preSwitchTimer = null; //Create variable to hold currently playing media object this.nowPlaying = null; @@ -97,12 +99,12 @@ module.exports = class{ //Set title const title = validator.escape(validator.trim(data.title)); - //set start - let start = this.getStart(data.start); - //Pull media list const mediaList = await yanker.yankMedia(url, title); + //set start + let start = this.getStart(data.start); + //If we didn't find any media if(mediaList == null || mediaList.length <= 0){ //Bitch, moan, complain... @@ -286,6 +288,7 @@ module.exports = class{ //Clear out any stale timers to prevent ghost queueing clearTimeout(this.nextTimer); + clearTimeout(this.preSwitchTimer); //If we have a current item and it isn't currently playing if(currentItem != null && (this.nowPlaying == null || currentItem.uuid != this.nowPlaying.uuid)){ @@ -293,8 +296,23 @@ module.exports = class{ this.start(currentItem, Math.round((new Date().getTime() - currentItem.startTime) / 1000) + currentItem.startTimeStamp, volatile); //If we have a next item }else if(nextItem != null){ + //Get current time as epoch + const now = new Date().getTime(); //Calculate the amount of time in ms that the next item will start in - const startsIn = nextItem.startTime - new Date().getTime(); + const startsIn = nextItem.startTime - now; + //Delay between pre-switch function call and start of media + //This should be enough time to do things like pre-fetch updated raw links from youtube + const preSwitchDelta = 10 * 1000; + //Calculate when the pre-switch timer would be called + const preSwitchTime = nextItem.startTime - preSwitchDelta; + //Calculate how long the pre-switch timer will be called in + const preSwitchIn = preSwitchTime - now; + + //If we have enough time to call the pre-switch timer + if(preSwitchIn > preSwitchDelta){ + //Set the pre-switch timer + this.preSwitchTimer = setTimeout(()=>{this.preSwitch(nextItem)}, preSwitchIn); + } //Set the next timer this.nextTimer = setTimeout(()=>{this.start(nextItem, nextItem.startTimeStamp, volatile)}, startsIn); @@ -655,6 +673,17 @@ module.exports = class{ return true; } + async preSwitch(mediaObj){ + //Check if media needs a new raw link and update if it does + if(await yanker.refreshRawLink(mediaObj)){ + //If the fetch took so god damned long we've already started the video (isn't 10 seconds enough?) + if(this.nowPlaying != null && this.nowPlaying.uuid == mediaObj.uuid){ + //Tell the clients to update the raw file for the current item fore.st-style, as it probably got sent out with a stale link + this.server.io.in(this.channel.name).emit("updateCurrentRawFile", {file: mediaObj.rawLink}); + } + } + } + async start(mediaObj, timestamp = mediaObj.startTimeStamp, volatile = false){ //If something is already playing if(this.nowPlaying != null){ diff --git a/src/schemas/channel/media/queuedMediaSchema.js b/src/schemas/channel/media/queuedMediaSchema.js index ba92208..e5fbfc2 100644 --- a/src/schemas/channel/media/queuedMediaSchema.js +++ b/src/schemas/channel/media/queuedMediaSchema.js @@ -54,7 +54,7 @@ queuedProperties.methods.rehydrate = function(){ this.type, this.duration, //We don't save raw links that are stored seperate from the standard URL as they tend to expire. - null, + undefined, this.startTime, this.startTimeStamp, this.earlyEnd, diff --git a/src/utils/media/yanker.js b/src/utils/media/yanker.js index ccdf84a..2c3c166 100644 --- a/src/utils/media/yanker.js +++ b/src/utils/media/yanker.js @@ -38,6 +38,22 @@ module.exports.yankMedia = async function(url, title){ } } +module.exports.refreshRawLink = async function(mediaObj){ + switch(mediaObj.type){ + case 'yt': + //Re-fetch media metadata + metadata = await ytdlpUtil.fetchYoutubeVideoMetadata(mediaObj.id); + //Refresh media rawlink from metadata + mediaObj.rawLink = metadata[0].rawLink; + + //return media object + return mediaObj; + } + + //Return null to tell the calling function there is no refresh required for this media type + return null; +} + //I'd be lying if this didn't take at least some inspiration/regex patterns from extractQueryParam() in cytube/forest's browser-side 'util.js' //Still this has some improvements like url pre-checks and the fact that it's handled serverside, recuing possibility of bad requests. //Some of the regex expressions for certain services have also been improved, such as youtube, and the fore.st-unique archive.org diff --git a/www/js/channel/player.js b/www/js/channel/player.js index dbf1d70..eef1d0a 100644 --- a/www/js/channel/player.js +++ b/www/js/channel/player.js @@ -71,6 +71,7 @@ class player{ this.client.socket.on("start", this.start.bind(this)); this.client.socket.on("sync", this.sync.bind(this)); this.client.socket.on("end", this.end.bind(this)); + this.client.socket.on("updateCurrentRawFile", this.updateCurrentRawFile.bind(this)); } start(data){ @@ -144,6 +145,17 @@ class player{ this.lockSync(); } + updateCurrentRawFile(data){ + //Grab current item from media handler + const currentItem = this.mediaHandler.nowPlaying; + + //Update raw link + currentItem.rawLink = data.file; + + //Re-start the item + this.start({media: currentItem}); + } + lockSync(){ //Enable syncing this.syncLock = true;