diff --git a/src/app/channel/media/queue.js b/src/app/channel/media/queue.js
index 096a823..0609a6d 100644
--- a/src/app/channel/media/queue.js
+++ b/src/app/channel/media/queue.js
@@ -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");
}
- //Capture currently playing object
- const wasPlaying = this.nowPlaying;
+ //If something is playing
+ if(this.nowPlaying != null){
+ //Capture currently playing object
+ 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;
- //Get difference between current time and start time and set as early end
- stoppedMedia.earlyEnd = (new Date().getTime() - stoppedMedia.startTime) / 1000;
+ //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,8 +1174,11 @@ 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 ){
- //Add the current item to the list
- foundItems.push(item[1]);
+ //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]);
+ }
}
}
@@ -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;
diff --git a/src/app/channel/media/queuedMedia.js b/src/app/channel/media/queuedMedia.js
index 85fc365..fe4c68e 100644
--- a/src/app/channel/media/queuedMedia.js
+++ b/src/app/channel/media/queuedMedia.js
@@ -18,7 +18,7 @@ along with this program. If not, see .*/
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{
diff --git a/src/schemas/channel/channelSchema.js b/src/schemas/channel/channelSchema.js
index 235023f..fd2d4ad 100644
--- a/src/schemas/channel/channelSchema.js
+++ b/src/schemas/channel/channelSchema.js
@@ -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]
diff --git a/www/css/panel/queue.css b/www/css/panel/queue.css
index d3907ca..378b3fc 100644
--- a/www/css/panel/queue.css
+++ b/www/css/panel/queue.css
@@ -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;
diff --git a/www/css/theme/movie-night.css b/www/css/theme/movie-night.css
index 8e17813..de764eb 100644
--- a/www/css/theme/movie-night.css
+++ b/www/css/theme/movie-night.css
@@ -47,6 +47,7 @@ along with this program. If not, see .*/
--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);
diff --git a/www/js/channel/panels/queuePanel/queuePanel.js b/www/js/channel/panels/queuePanel/queuePanel.js
index 1ef06c9..e2c7be4 100644
--- a/www/js/channel/panels/queuePanel/queuePanel.js
+++ b/www/js/channel/panels/queuePanel/queuePanel.js
@@ -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);