Livestream Database Handling for Overwrite mode complete. Schedule goes back to pre-stream state if server crashes/stops.
This commit is contained in:
parent
85c1258bb6
commit
8c8b2a6f0b
|
|
@ -49,6 +49,8 @@ module.exports = class{
|
|||
this.preSwitchTimer = null;
|
||||
//Create variable to hold currently playing media object
|
||||
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 lock standard queuing functions during livestreams
|
||||
this.streamLock = false;
|
||||
|
|
@ -267,8 +269,15 @@ module.exports = class{
|
|||
throw loggerUtils.exceptionSmith(`Unable to find channel document ${this.channel.name} while queue item!`, "queue");
|
||||
}
|
||||
|
||||
//If something is playing
|
||||
if(this.nowPlaying != null){
|
||||
//Capture currently playing object
|
||||
const wasPlaying = this.nowPlaying;
|
||||
this.liveRemainder = this.nowPlaying;
|
||||
chanDB.media.liveRemainder = this.nowPlaying.uuid;
|
||||
|
||||
//Save the chanDB
|
||||
await chanDB.save();
|
||||
}
|
||||
|
||||
//Kill schedule timers to prevent items from starting during the stream
|
||||
await this.stopScheduleTimers();
|
||||
|
|
@ -309,13 +318,6 @@ module.exports = class{
|
|||
//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){
|
||||
|
|
@ -326,7 +328,7 @@ module.exports = class{
|
|||
//--- INTERNAL USE ONLY QUEUEING FUNCTIONS ---
|
||||
async stopScheduleTimers(noArchive = true){
|
||||
//End any currently playing media media w/o archiving
|
||||
await this.end(false, noArchive);
|
||||
await this.stop();
|
||||
|
||||
//Clear sync timer
|
||||
clearTimeout(this.syncTimer);
|
||||
|
|
@ -417,7 +419,7 @@ module.exports = class{
|
|||
}
|
||||
}
|
||||
|
||||
async removeRange(start = new Date().getTime() - 60 * 1000, end = new Date().getTime(), socket){
|
||||
async removeRange(start = new Date().getTime() - 60 * 1000, end = new Date().getTime(), socket, noUnfinished = false){
|
||||
//If we're streamlocked
|
||||
if(this.streamLock){
|
||||
//If an originating socket was provided for this request
|
||||
|
|
@ -430,7 +432,7 @@ module.exports = class{
|
|||
}
|
||||
|
||||
//Find items within given range
|
||||
const foundItems = this.getItemsBetweenEpochs(start, end);
|
||||
const foundItems = this.getItemsBetweenEpochs(start, end, noUnfinished);
|
||||
|
||||
try{
|
||||
//DO everything ourselves since we don't have a fance end() function to do it
|
||||
|
|
@ -969,7 +971,7 @@ module.exports = class{
|
|||
//If we're ending an HLS Livestream
|
||||
if(wasPlaying.type == "livehls"){
|
||||
//Redirect to the endLivestream function
|
||||
return this.endLivestream(chanDB);
|
||||
return this.endLivestream(wasPlaying, chanDB)
|
||||
}
|
||||
|
||||
//If we're not in volatile mode and we're not ending a livestream
|
||||
|
|
@ -987,7 +989,7 @@ module.exports = class{
|
|||
}
|
||||
|
||||
//If we haven't changed 'nowPlaying' in the play list
|
||||
if(chanDB.media.nowPlaying.uuid == wasPlaying.uuid){
|
||||
if(chanDB.media.nowPlaying != null && chanDB.media.nowPlaying.uuid == wasPlaying.uuid){
|
||||
//Take it out
|
||||
await chanDB.media.nowPlaying.deleteOne();
|
||||
}
|
||||
|
|
@ -1016,29 +1018,7 @@ module.exports = class{
|
|||
}
|
||||
}
|
||||
|
||||
async endLivestream(chanDB){
|
||||
try{
|
||||
//Disable stream lock
|
||||
this.streamLock = false;
|
||||
|
||||
//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
|
||||
}catch(err){
|
||||
//Broadcast queue
|
||||
this.broadcastQueue();
|
||||
//Handle the error
|
||||
loggerUtils.localExceptionHandler(err);
|
||||
}
|
||||
}
|
||||
|
||||
async livestreamOverwriteSchedule(chanDB){
|
||||
async endLivestream(wasPlaying, chanDB){
|
||||
try{
|
||||
//If we wheren't handed a channel
|
||||
if(chanDB == null){
|
||||
|
|
@ -1051,14 +1031,117 @@ module.exports = class{
|
|||
//FUCK
|
||||
throw loggerUtils.exceptionSmith(`Unable to find channel document ${this.channel.name} while ending queue item!`, "queue");
|
||||
}
|
||||
//Disable stream lock
|
||||
this.streamLock = false;
|
||||
|
||||
//We don't have to 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)
|
||||
|
||||
//Refresh next timer
|
||||
this.refreshNextTimer();
|
||||
|
||||
//Broadcast Queue
|
||||
this.broadcastQueue();
|
||||
//ACK
|
||||
}catch(err){
|
||||
//Broadcast queue
|
||||
this.broadcastQueue();
|
||||
//Handle the error
|
||||
loggerUtils.localExceptionHandler(err);
|
||||
}
|
||||
}
|
||||
|
||||
async livestreamOverwriteSchedule(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");
|
||||
}
|
||||
|
||||
//mark overwrite job as finished so we don't run two sets of logic, as one needs to do a null check before it can run it's conditional
|
||||
//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);
|
||||
|
||||
//Save the DB
|
||||
await chanDB.save();
|
||||
|
||||
//If we have a live remainder
|
||||
if(this.liveRemainder != null){
|
||||
//If the item hasn't ended
|
||||
if(finished = (this.liveRemainder.getEndTime(true) > now)){
|
||||
//Rip out early end
|
||||
this.liveRemainder.earlyEnd = undefined;
|
||||
|
||||
//regenerate UUID to differentiate between this and the original item
|
||||
this.liveRemainder.genUUID();
|
||||
|
||||
//Re-schedule the remainder
|
||||
await this.scheduleMedia([this.liveRemainder], undefined, chanDB);
|
||||
}
|
||||
}
|
||||
|
||||
//if "THIS ISN'T OVER, PUNK!"
|
||||
if(!finished){
|
||||
//Pull item from end
|
||||
const wasPlayingDuringEnd = this.getItemAtEpoch(now);
|
||||
|
||||
//If we ended in the middle of something
|
||||
if(wasPlayingDuringEnd != null){
|
||||
const difference = (now - wasPlayingDuringEnd.startTime);
|
||||
|
||||
//Take item out
|
||||
await this.removeMedia(wasPlayingDuringEnd.uuid, null, chanDB);
|
||||
|
||||
//Push the item up to match the difference
|
||||
wasPlayingDuringEnd.startTime += difference;
|
||||
|
||||
//re-set start time stamp based on media start and stream end
|
||||
wasPlayingDuringEnd.startTimeStamp = Math.round(difference / 1000);
|
||||
|
||||
//Make unique, true
|
||||
wasPlayingDuringEnd.genUUID();
|
||||
|
||||
//Re-schedule media now that it's been cut
|
||||
await this.scheduleMedia([wasPlayingDuringEnd], null, chanDB);
|
||||
}
|
||||
|
||||
//Remove all the in-betweeners
|
||||
await this.removeRange(wasPlaying.startTime, now, null, true);
|
||||
}
|
||||
|
||||
|
||||
//Null out live remainder for the next stream
|
||||
this.liveRemainder = null;
|
||||
}catch(err){
|
||||
//Null out live remainder for the next stream
|
||||
this.liveRemainder = null;
|
||||
|
||||
//Handle the error
|
||||
loggerUtils.localExceptionHandler(err);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
stop(){
|
||||
stop(socket){
|
||||
//If we're not currently playing anything
|
||||
if(this.nowPlaying == null){
|
||||
//If an originating socket was provided for this request
|
||||
|
|
@ -1073,14 +1156,17 @@ module.exports = class{
|
|||
//Stop playing
|
||||
const stoppedMedia = this.nowPlaying;
|
||||
|
||||
//Ignore early end for livestreams
|
||||
if(this.nowPlaying.type != 'livehls'){
|
||||
//Get difference between current time and start time and set as early end
|
||||
stoppedMedia.earlyEnd = (new Date().getTime() - stoppedMedia.startTime) / 1000;
|
||||
}
|
||||
|
||||
//End the media
|
||||
this.end();
|
||||
}
|
||||
|
||||
getItemsBetweenEpochs(start, end){
|
||||
getItemsBetweenEpochs(start, end, noUnfinished = false){
|
||||
//Create an empty array to hold found items
|
||||
const foundItems = [];
|
||||
|
||||
|
|
@ -1088,10 +1174,13 @@ module.exports = class{
|
|||
for(let item of this.schedule){
|
||||
//If the item starts after our start date and before our end date
|
||||
if(item[0] >= start && item[0] <= end ){
|
||||
//If we're allowed to add unifnished items, or the item has finished
|
||||
if(!noUnfinished || item[1].getEndTime() <= end){
|
||||
//Add the current item to the list
|
||||
foundItems.push(item[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Return any found items
|
||||
return foundItems;
|
||||
|
|
@ -1207,6 +1296,12 @@ module.exports = class{
|
|||
|
||||
//If the media started within the last 24 hours
|
||||
if(media.startTime > yesterday){
|
||||
//If we're sending out the live remainder during a live stream
|
||||
if(this.liveRemainder != null && media.uuid.toString() == this.liveRemainder.uuid.toString()){
|
||||
//Throw out the early end before sending it off, so it looks like it hasn't been cut off yet (smoke n mirrors :P)
|
||||
media.earlyEnd = null;
|
||||
}
|
||||
|
||||
//Add it to the schedule array as if it where part of the actual schedule map
|
||||
schedule.push([media.startTime, media]);
|
||||
//Otherwise if it's older
|
||||
|
|
@ -1277,7 +1372,7 @@ module.exports = class{
|
|||
//Add record to new schedule
|
||||
newSched.push(record);
|
||||
|
||||
//Re-Schedule it in RAM
|
||||
//Re-Schedule it in RAM, with the start function running w/ DB transactions enabled, since it won't happen right away
|
||||
await this.scheduleMedia([mediaObj], null, chanDB, true, true, false);
|
||||
}else{
|
||||
//If the media should be playing now
|
||||
|
|
@ -1285,7 +1380,7 @@ module.exports = class{
|
|||
//Save record to nowPlaying in the DB
|
||||
chanDB.media.nowPlaying = record;
|
||||
|
||||
//Re-Schedule it in RAM
|
||||
//Schedule the fucker in RAM, w/ the start function also running in RAM-Only mode
|
||||
await this.scheduleMedia([mediaObj], null, chanDB, true, true, true);
|
||||
//If it's been ended
|
||||
}else{
|
||||
|
|
@ -1295,6 +1390,39 @@ module.exports = class{
|
|||
}
|
||||
}
|
||||
|
||||
//If we have a remainder from a livestream
|
||||
if(chanDB.media.liveRemainder){
|
||||
//Iterate backwards through the archive to pull the newest first, since that's probably where this fucker is
|
||||
for(let archiveIndex = (chanDB.media.archived.length - 1); archiveIndex > 0; archiveIndex--){
|
||||
//Grab the current media object
|
||||
const archivedMedia = chanDB.media.archived[archiveIndex];
|
||||
|
||||
//If the current object matches our remainder UUID
|
||||
if((archivedMedia.uuid.toString() == chanDB.media.liveRemainder.toString())){
|
||||
//Null out any early end
|
||||
archivedMedia.earlyEnd = null;
|
||||
|
||||
//Re-hydrate the item
|
||||
const archivedMediaObject = archivedMedia.rehydrate();
|
||||
|
||||
//if we still have a video to finish
|
||||
if(archivedMediaObject.getEndTime() > now){
|
||||
//Set the fucker as now playing
|
||||
chanDB.media.nowPlaying = archivedMediaObject;
|
||||
|
||||
//Schedule the fucker in RAM, w/ the start function also running in RAM-Only mode
|
||||
this.scheduleMedia([archivedMediaObject], null, chanDB, true, true, true);
|
||||
|
||||
//Splice the fucker out of the archive
|
||||
chanDB.media.archived.splice(archiveIndex, 1);
|
||||
}
|
||||
|
||||
//Break out of the loop
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Update schedule to only contain what hasn't been played yet
|
||||
chanDB.media.scheduled = newSched;
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
|||
const media = require('./media');
|
||||
|
||||
module.exports = class extends media{
|
||||
constructor(title, fileName, url, id, type, duration, rawLink, startTime, startTimeStamp, earlyEnd, uuid){
|
||||
constructor(title, fileName, url, id, type, duration, rawLink, startTime, startTimeStamp = 0, earlyEnd, uuid){
|
||||
//Call derived constructor
|
||||
super(title, fileName, url, id, type, duration, rawLink);
|
||||
//Set media start time
|
||||
|
|
@ -77,9 +77,9 @@ module.exports = class extends media{
|
|||
this.uuid = crypto.randomUUID();
|
||||
}
|
||||
|
||||
getEndTime(){
|
||||
getEndTime(fullTime = false){
|
||||
//If we have an early ending
|
||||
if(this.earlyEnd == null){
|
||||
if(this.earlyEnd == null || fullTime){
|
||||
//Calculate our ending
|
||||
return this.startTime + ((this.duration - this.startTimeStamp) * 1000);
|
||||
}else{
|
||||
|
|
|
|||
|
|
@ -111,7 +111,11 @@ const channelSchema = new mongoose.Schema({
|
|||
scheduled: [queuedMediaSchema],
|
||||
//We should consider moving archived media and channel playlists to their own collections/models for preformances sake
|
||||
archived: [queuedMediaSchema],
|
||||
playlists: [playlistSchema]
|
||||
playlists: [playlistSchema],
|
||||
liveRemainder: {
|
||||
type: mongoose.SchemaTypes.UUID,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
//Thankfully we don't have to keep track of alts, ips, or deleted users so this should be a lot easier than site-wide bans :P
|
||||
banList: [channelBanSchema]
|
||||
|
|
|
|||
|
|
@ -104,6 +104,11 @@ div.queue-entry{
|
|||
user-select: none;
|
||||
}
|
||||
|
||||
|
||||
div.queue-entry.live{
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
div.queue-entry p{
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
|||
--danger0-alt2: rgb(242, 189, 189);
|
||||
--danger-glow0: 2px 2px 3px var(--danger0), -2px 2px 3px var(--danger0), 2px -2px 3px var(--danger0), -2px -2px 3px var(--danger0);
|
||||
--danger-glow0-alt1: 2px 2px 3px var(--danger0-alt1), -2px 2px 3px var(--danger0-alt1), 2px -2px 3px var(--danger0-alt1), -2px -2px 3px var(--danger0-alt1);
|
||||
--danger-glow0-smol: 2px 2px -1px var(--danger0), -2px 2px -1px var(--danger0), 2px -2px -1px var(--danger0), -2px -2px -1px var(--danger0);
|
||||
|
||||
--timer-glow: -2px 1px 3px var(--danger0-alt1), 2px -1px 3px var(--danger0-alt1);
|
||||
|
||||
|
|
|
|||
|
|
@ -616,8 +616,8 @@ class queuePanel extends panelObj{
|
|||
menuMap.set("Play now", ()=>{this.client.socket.emit('move', {uuid: entry[1].uuid})});
|
||||
//Add 'Move To...' option to context menu
|
||||
menuMap.set("Move To...", (event)=>{new reschedulePopup(event, this.client, entry[1], null, this.ownerDoc)});
|
||||
//Otherwise, if the item is currently playing
|
||||
}else if(this.getMediaEnd(entry[1]) > now.getTime()){
|
||||
//Otherwise, if the item is currently playing (confirm with UUID since time might not always be reliable, such as during livestreams)
|
||||
}else if(entry[1].uuid == this.client.player.mediaHandler.nowPlaying.uuid){
|
||||
//Add 'Stop' option to context menu
|
||||
menuMap.set("Stop", ()=>{this.client.socket.emit('stop')});
|
||||
//Add the Now Playing glow, not the prettiest place to add this, but why let a good conditional go to waste?
|
||||
|
|
@ -1068,8 +1068,8 @@ class queuePanel extends panelObj{
|
|||
const entryTitle = document.createElement('p');
|
||||
entryTitle.textContent = utils.unescapeEntities(nowPlaying.title);
|
||||
|
||||
//Set entry div bottom-border location based on current time
|
||||
entryDiv.style.bottom = `${this.offsetByDate(date, true)}px`
|
||||
//Set entry div bottom-border location based on current time, round to match time marker
|
||||
entryDiv.style.bottom = `${Math.round(this.offsetByDate(date, true))}px`
|
||||
|
||||
//Assembly entryDiv
|
||||
entryDiv.appendChild(entryTitle);
|
||||
|
|
@ -1100,8 +1100,8 @@ class queuePanel extends panelObj{
|
|||
//Append entry div to queue container
|
||||
this.queueContainer.appendChild(entryDiv);
|
||||
}else{
|
||||
//Update existing entry
|
||||
staleEntry.style.bottom = `${this.offsetByDate(date, true)}px`
|
||||
//Update existing entry, round offset to match time marker
|
||||
staleEntry.style.bottom = `${Math.round(this.offsetByDate(date, true))}px`
|
||||
}
|
||||
|
||||
//Keep tooltip date seperate so it re-calculates live duration properly
|
||||
|
|
@ -1206,6 +1206,7 @@ class queuePanel extends panelObj{
|
|||
}
|
||||
|
||||
getMediaEnd(media){
|
||||
console.log(media);
|
||||
//If we have an early end
|
||||
if(media.earlyEnd != null){
|
||||
return media.startTime + (media.earlyEnd * 1000);
|
||||
|
|
|
|||
Loading…
Reference in a new issue