From b5e54afe99156c3e448048b7272056680c876919 Mon Sep 17 00:00:00 2001 From: rainbow napkin Date: Mon, 19 May 2025 04:11:25 -0400 Subject: [PATCH] Pushback Schedule mode now works when ending livestreams. --- src/app/channel/media/queue.js | 251 +++++++++++++++++++++++++-------- 1 file changed, 191 insertions(+), 60 deletions(-) diff --git a/src/app/channel/media/queue.js b/src/app/channel/media/queue.js index 0609a6d..e97edd5 100644 --- a/src/app/channel/media/queue.js +++ b/src/app/channel/media/queue.js @@ -51,6 +51,8 @@ module.exports = class{ this.nowPlaying = null; //Create variable to hold item that was playing during the last liveStream (can't check against full duration since it might've been stopped for other reasons) this.liveRemainder = null; + //Create variable to hold current live mode + this.liveMode = null; //Create variable to lock standard queuing functions during livestreams this.streamLock = false; @@ -314,6 +316,8 @@ module.exports = class{ new Date().getTime() ); + //Validate mode input, and default to overwrite + this.liveMode = (data.mode == "pushback") ? "pushback" : "overwrite"; //Throw stream lock this.streamLock = true; @@ -463,7 +467,7 @@ module.exports = class{ } } - async rescheduleMedia(uuid, start = new Date().getTime(), socket){ + async rescheduleMedia(uuid, start = new Date().getTime(), socket, chanDB){ //If we're streamlocked if(this.streamLock){ //If an originating socket was provided for this request @@ -475,68 +479,92 @@ module.exports = class{ return; } - //Find our media, don't remove it yet since we want to do some more testing first - const media = this.getItemByUUID(uuid); - - //If we got a bad request - if(media == null){ - //If an originating socket was provided for this request - if(socket != null){ - //Yell at the user for being an asshole - loggerUtils.socketErrorHandler(socket, "Cannot move non-existant item!", "queue"); + try{ + //If we wheren't handed a channel + if(chanDB == null){ + //DO everything ourselves since we don't have a fance end() function to do it + chanDB = await channelModel.findOne({name:this.channel.name}); } - //Ignore it - return; - } - //If someone is trying to re-schedule something that starts in the past - if(media.startTime < new Date().getTime()){ - //If an originating socket was provided for this request - if(socket != null){ + //If we couldn't find the channel + if(chanDB == null){ + //FUCK + throw loggerUtils.exceptionSmith(`Unable to find channel document ${this.channel.name} while queue item!`, "queue"); + } - //If the item is currently playing - if(media.getEndTime() > new Date().getTime()){ + //Find our media, don't remove it yet since we want to do some more testing first + const media = this.getItemByUUID(uuid); + + //If we got a bad request + if(media == null){ + //If an originating socket was provided for this request + if(socket != null){ //Yell at the user for being an asshole - loggerUtils.socketErrorHandler(socket, "You cannot move an actively playing video!", "queue"); - //Otherwise, if it's already ended - }else{ - //Yell at the user for being an asshole - loggerUtils.socketErrorHandler(socket, "You cannot alter the past!", "queue"); + loggerUtils.socketErrorHandler(socket, "Cannot move non-existant item!", "queue"); } + //Ignore it + return; } + //If someone is trying to re-schedule something that starts in the past + if(media.startTime < new Date().getTime()){ + //If an originating socket was provided for this request + if(socket != null){ + //If the item is currently playing + if(media.getEndTime() > new Date().getTime()){ + //Yell at the user for being an asshole + loggerUtils.socketErrorHandler(socket, "You cannot move an actively playing video!", "queue"); + //Otherwise, if it's already ended + }else{ + //Yell at the user for being an asshole + loggerUtils.socketErrorHandler(socket, "You cannot alter the past!", "queue"); + } + } - //Ignore it - return; - } - //Remove the media from the schedule - await this.removeMedia(uuid); + //Ignore it + return; + } - //Grab the old start time for safe keeping - const oldStart = media.startTime; + //Remove the media from the schedule + await this.removeMedia(uuid, socket, chanDB); - //Set media time - media.startTime = start; + //Grab the old start time for safe keeping + const oldStart = media.startTime; - //Reset the start time stamp for re-calculation - media.startTimeStamp = 0; - - //Attempt to schedule media at given time - //Otherwise, if it returns false for fuckup - if(!(await this.scheduleMedia([media], socket))){ - //Reset start time - media.startTime = oldStart; + //Set media time + media.startTime = start; //Reset the start time stamp for re-calculation media.startTimeStamp = 0; - //Schedule in old slot - this.scheduleMedia([media], socket, null, true); + //Attempt to schedule media at given time + //Otherwise, if it returns false for fuckup, with noSave enabled + if(!(await this.scheduleMedia([media], socket, chanDB))){ + //Reset start time + media.startTime = oldStart; + + //Reset the start time stamp for re-calculation + media.startTimeStamp = 0; + + //Schedule in old slot with noSave enabled + await this.scheduleMedia([media], socket, chanDB, true); + } + + }catch(err){ + //If this was originated by someone + if(socket != null){ + //Bitch at them + loggerUtils.socketExceptionHandler(socket, err); + //If not + }else{ + //Bitch to the console + loggerUtils.localExceptionHandler(err); + } } } - async removeMedia(uuid, socket, chanDB){ + async removeMedia(uuid, socket, chanDB, noScheduling = false){ //If we're streamlocked if(this.streamLock){ //If an originating socket was provided for this request @@ -585,7 +613,6 @@ module.exports = class{ }else{ //Broadcast changes this.broadcastQueue(chanDB); - //Save changes to the DB await chanDB.save(); } @@ -608,13 +635,18 @@ module.exports = class{ //Take the item out of the schedule map this.schedule.delete(media.startTime); - //Refresh next timer - this.refreshNextTimer(); + if(!noScheduling){ + //Refresh next timer + this.refreshNextTimer(); + } //If we're currently playing the requested item. if(this.nowPlaying != null && this.nowPlaying.uuid == uuid){ - //End playback - this.end(false, true); + //If scheduling is enabled + if(!noScheduling){ + //End playback + this.end(false, true); + } //otherwise }else{ try{ @@ -635,7 +667,10 @@ module.exports = class{ return record.uuid != uuid; }); - await chanDB.save(); + //If saving is enabled (seperate from all DB transactions since caller function may want modifications but handle saving on its own) + if(!noScheduling){ + await chanDB.save(); + } //Broadcast the channel this.broadcastQueue(chanDB); @@ -660,7 +695,7 @@ module.exports = class{ return media; } - async scheduleMedia(media, socket, chanDB, force = false, volatile = false, startVolatile = false, saveLate = false){ + async scheduleMedia(media, socket, chanDB, force = false, volatile = false, startVolatile = false, saveLate = false, noSave = false){ /* This is a fun method and I think it deserves it's own little explination... Since we're working with a time based schedule, using start epochs as keys for our iterable seemed the best option I don't want to store everything in a sparse array because that *feels* icky, and would probably be a pain in the ass. @@ -811,8 +846,11 @@ module.exports = class{ //If we fucked with the DB during the main loop if(chanDB != null && !volatile){ try{ - //Save the database - chanDB.save(); + //If saving is enabled (seperate from all DB transactions since caller function may want modifications but handle saving on its own) + if(!noSave){ + //Save the database + await chanDB.save(); + } //If something fucked up }catch(err){ //If this was originated by someone @@ -877,7 +915,7 @@ module.exports = class{ const chanDB = await channelModel.findOne({name: this.channel.name}); //If nowPlaying isn't null and isn't what we're about to throw on - if(chanDB.media.nowPlaying != null && chanDB.media.nowPlaying.uuid.toString != mediaObj.uuid){ + if(chanDB.media.nowPlaying != null && chanDB.media.nowPlaying.uuid.toString() != mediaObj.uuid){ //Archive whats already in there since we're about to clobber the fuck out of it chanDB.media.archived.push(chanDB.media.nowPlaying); } @@ -1034,15 +1072,30 @@ module.exports = class{ //Disable stream lock this.streamLock = false; - //We don't have to here since someone else will do it for us :) + //We don't have to save here since someone else will do it for us :) chanDB.media.liveRemainder = null; - //This is where I'd stick the IF statetement I'd add to switch between overwrite - await this.livestreamOverwriteSchedule(wasPlaying, chanDB) + //Get current epoch + const now = new Date().getTime() + + //Set duration from start and end time + wasPlaying.duration = (now - wasPlaying.startTime) / 1000; + + //If we're in pushback mode + if(this.liveMode == "pushback"){ + await this.livestreamPushbackSchedule(wasPlaying, chanDB); + //Otherwise + }else{ + //This is where I'd stick the IF statetement I'd add to switch between overwrite + await this.livestreamOverwriteSchedule(wasPlaying, chanDB) + } //Refresh next timer this.refreshNextTimer(); + //Null out live mode + this.liveMode = null; + //Broadcast Queue this.broadcastQueue(); //ACK @@ -1075,9 +1128,6 @@ module.exports = class{ //while the other needs to run regardless of this.liveRemainders definition let finished = false; - //Set duration from start and end time - wasPlaying.duration = (now - wasPlaying.startTime) / 1000; - //Throw the livestream into the archive chanDB.media.archived.push(wasPlaying); @@ -1141,6 +1191,87 @@ module.exports = class{ } + async livestreamPushbackSchedule(wasPlaying, chanDB){ + try{ + //Get current epoch + const now = new Date().getTime() + + //If we wheren't handed a channel + if(chanDB == null){ + //Now that everything is clean, we can take our time with the DB :P + chanDB = await channelModel.findOne({name:this.channel.name}); + } + + //If we couldn't find the channel + if(chanDB == null){ + //FUCK + throw loggerUtils.exceptionSmith(`Unable to find channel document ${this.channel.name} while ending queue item!`, "queue"); + } + + //Throw the livestream into the archive + chanDB.media.archived.push(wasPlaying); + + //Set the current place to schedule items at 5ms after the end of the live stream + let curPlace = wasPlaying.getEndTime() + 5; + const newSched = []; + + //if we have a live remainder + if(this.liveRemainder != null){ + //Set item to continue where it left off + this.liveRemainder.startTimeStamp = this.liveRemainder.earlyEnd; + + //Rip out the early end so it finish up + this.liveRemainder.earlyEnd = null; + + //Generate new UUID for uniqueness + this.liveRemainder.genUUID(); + + //Set start time to the end of the stream + this.liveRemainder.startTime = curPlace; + + //Reset starter time to end of current item + 5ms + curPlace = this.liveRemainder.getEndTime(true) + 5; + + //Throw live remainder into the new schedule + newSched.push(this.liveRemainder); + + //Null out live remainder for the next stream + this.liveRemainder = null; + chanDB.liveRemainder = null; + } + + //Iterate through objects in schedule + for(const entry of this.schedule){ + //Pull media object from map entry + const mediaObj = entry[1]; + + //Remove media from queue without calling chanDB.save() to make room before we move everything + await this.removeMedia(mediaObj.uuid, null, chanDB, true); + + mediaObj.genUUID(); + + //Change start time to current starter place + mediaObj.startTime = curPlace; + + //Throw item into the temp sched + newSched.push(mediaObj); + + //Set cur place to 5ms after the item we just queued + curPlace = mediaObj.getEndTime() + 5; + } + + //Schedule the moved schedule, letting scheduleMedia save our changes for us, starting w/o saves to prevent over-saving + await this.scheduleMedia(newSched, null, chanDB); + }catch(err){ + //Null out live remainder for the next stream + this.liveRemainder = null; + + //Handle the error + loggerUtils.localExceptionHandler(err); + } + + } + stop(socket){ //If we're not currently playing anything if(this.nowPlaying == null){