From 85c1258bb6f3128a8e64608b41a4a6d934597bdd Mon Sep 17 00:00:00 2001 From: rainbow napkin Date: Sat, 17 May 2025 10:03:32 -0400 Subject: [PATCH] Started work on livestream end handling, and live stream server crash handling. --- src/app/channel/media/queue.js | 69 +++++++++++++++++++++++++++------- 1 file changed, 55 insertions(+), 14 deletions(-) diff --git a/src/app/channel/media/queue.js b/src/app/channel/media/queue.js index 67bc710..096a823 100644 --- a/src/app/channel/media/queue.js +++ b/src/app/channel/media/queue.js @@ -238,9 +238,6 @@ module.exports = class{ async goLive(socket, data){ try{ - //Grab the channel from DB - const chanDB = await channelModel.findOne({name:this.channel.name}); - let title = "Livestream"; if(data != null && data.title != null){ @@ -261,12 +258,18 @@ module.exports = class{ } } + //Grab the channel from DB + const 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 queue item!`, "queue"); } + //Capture currently playing object + const wasPlaying = this.nowPlaying; + //Kill schedule timers to prevent items from starting during the stream await this.stopScheduleTimers(); @@ -302,20 +305,28 @@ module.exports = class{ new Date().getTime() ); - //Broadcast new media object to users - this.sendMedia(); //Throw stream lock this.streamLock = true; + + //If something was playing + if(wasPlaying != null){ + //Force it back into the schedule w/ saveLate enabled from nowPlaying, since it was ended with noArchive, effectively deleting it + //This is also the easiest way to bring nowPlaying media back into the schedule, since end wants to archive it :P + await this.scheduleMedia([wasPlaying], socket, chanDB, true, false, false, true); + } + + //Broadcast new media object to users + this.sendMedia(); }catch(err){ return loggerUtils.socketExceptionHandler(socket, err); } } //--- INTERNAL USE ONLY QUEUEING FUNCTIONS --- - async stopScheduleTimers(){ - //End any currently playing media media - await this.end(); + async stopScheduleTimers(noArchive = true){ + //End any currently playing media media w/o archiving + await this.end(false, noArchive); //Clear sync timer clearTimeout(this.syncTimer); @@ -647,7 +658,7 @@ module.exports = class{ return media; } - async scheduleMedia(media, socket, chanDB, force = false, volatile = false, startVolatile = false){ + async scheduleMedia(media, socket, chanDB, force = false, volatile = false, startVolatile = false, saveLate = 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. @@ -676,8 +687,8 @@ module.exports = class{ https://community.appsmith.com/content/blog/dark-side-foreach-why-you-should-think-twice-using-it */ - //If we're streamlocked - if(this.streamLock){ + //If we're streamlocked and this isn't being forced + if(this.streamLock && !force){ //If an originating socket was provided for this request if(socket != null){ //Yell at the user for being an asshole @@ -761,8 +772,8 @@ module.exports = class{ //Refresh the next timer to ensure whatever comes on next is right this.refreshNextTimer(startVolatile); - //If media has more than a minute before starting and DB transactions are enabled - if(mediaObj.startTime - new Date().getTime() > 1000 && !volatile){ + //If media has more than a minute before starting OR saveLate is enabled and DB transactions are enabled + if((mediaObj.startTime - new Date().getTime() > 1000 || saveLate) && !volatile){ //fuck you yoda you fucking nerd try{ //If we didn't get handed a freebie @@ -850,6 +861,13 @@ module.exports = class{ //Set current playing media this.nowPlaying = mediaObj; + //You might think it'd make sense to remove this item from the schedule like we will in the DB to keep it in nowPlaying only + //That however, is not how this was originally made, and updating it would take more work and break more shit than is worth + //Especially since one could argue that not deleting now-playing items in the RAM schedule makes it easier to iterate through + //We can get away with doing so in the DB since it only needs to be iterated once, + //If I was to re-do this from scratch I'd probably at least try it out with it deleting them here + //But agin, this is months in, not really worth it since it doesn't really cause any issues... + //if DB transactions are enabled if(!volatile){ try{ @@ -1006,6 +1024,9 @@ module.exports = class{ //Refresh next timer this.refreshNextTimer(); + //This is where I'd stick the IF statetement I'd add to switch between overwrite + await this.livestreamOverwriteSchedule(chanDB) + //Broadcast Queue this.broadcastQueue(); //ACK @@ -1017,6 +1038,26 @@ module.exports = class{ } } + async livestreamOverwriteSchedule(chanDB){ + try{ + //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"); + } + }catch(err){ + //Handle the error + loggerUtils.localExceptionHandler(err); + } + + } + stop(){ //If we're not currently playing anything if(this.nowPlaying == null){ @@ -1205,7 +1246,7 @@ module.exports = class{ const now = new Date().getTime(); //If something was playing - if(chanDB.media.nowPlaying != null){ + if(chanDB.media.nowPlaying != null && chanDB.media.nowPlaying.type != 'livehls'){ //Rehydrate the currently playing item int oa queued media object const wasPlaying = chanDB.media.nowPlaying.rehydrate();