From 9eeed591addcfde2793f89deb3a222dac464fd10 Mon Sep 17 00:00:00 2001 From: rainbow napkin Date: Tue, 9 Sep 2025 08:19:46 -0400 Subject: [PATCH] Re-scheduling currently playing item now stops currnet playback, and re-schedules a clone. Allowing for DB-Friendly schedule2scrub functionality. --- src/app/channel/media/queue.js | 41 +++++---- src/app/channel/media/queuedMedia.js | 19 ++++ .../channel/panels/queuePanel/queuePanel.js | 19 +++- www/js/utils.js | 91 ++++++++++++------- 4 files changed, 112 insertions(+), 58 deletions(-) diff --git a/src/app/channel/media/queue.js b/src/app/channel/media/queue.js index f4cfe3e..83eb236 100644 --- a/src/app/channel/media/queue.js +++ b/src/app/channel/media/queue.js @@ -596,7 +596,7 @@ class queue{ } //Find our media, don't remove it yet since we want to do some more testing first - const media = this.getItemByUUID(uuid); + let media = this.getItemByUUID(uuid); //If we got a bad request if(media == null){ @@ -611,27 +611,31 @@ class queue{ //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{ + //If the item is currently playing + if(media.getEndTime() > new Date().getTime()){ + //Dupe media for the rest of the function + media = media.clone(); + + //Stop current item + await this.stop(socket, chanDB); + + //Otherwise, if it's already ended + }else{ + //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 alter the past!", "queue"); } + + //Ignore it + return; } - - - //Ignore it - return; + //If the item has yet to be played + }else{ + //Remove the media from the schedule + await this.removeMedia(uuid, socket, chanDB); } - //Remove the media from the schedule - await this.removeMedia(uuid, socket, chanDB); - //Grab the old start time for safe keeping const oldStart = media.startTime; @@ -1438,9 +1442,10 @@ class queue{ /** * Stops currently playing media item * @param {Socket} socket - Requesting Socket + * @param {Mongoose.Document} chanDB - Pass through Channel Document to save on DB Transactions * @returns returns false if there is nothing to stop */ - stop(socket){ + async stop(socket, chanDB){ //If we're not currently playing anything if(this.nowPlaying == null){ //If an originating socket was provided for this request @@ -1462,7 +1467,7 @@ class queue{ } //End the media - this.end(); + await this.end(false, false, false, chanDB); } /** diff --git a/src/app/channel/media/queuedMedia.js b/src/app/channel/media/queuedMedia.js index c92d7d0..863c770 100644 --- a/src/app/channel/media/queuedMedia.js +++ b/src/app/channel/media/queuedMedia.js @@ -119,6 +119,25 @@ class queuedMedia extends media{ this.uuid = crypto.randomUUID(); } + /** + * Generates a unique clone of a given media object + * @returns unique clone of media object + */ + clone(){ + return new queuedMedia( + this.title, + this.fileName, + this.url, + this.id, + this.type, + this.duration, + this.rawLink, + this.startTime, + this.startTimeStamp, + this.earlyEnd + ); + } + /** * return the end time of a given queuedMedia object * @param {boolean} fullTime - Overrides early ends diff --git a/www/js/channel/panels/queuePanel/queuePanel.js b/www/js/channel/panels/queuePanel/queuePanel.js index 9bb91f4..5fb159e 100644 --- a/www/js/channel/panels/queuePanel/queuePanel.js +++ b/www/js/channel/panels/queuePanel/queuePanel.js @@ -956,16 +956,25 @@ class queuePanel extends panelObj{ //Get current start time const start = this.dateByOffset(target.offsetTop); + const end = new Date(start.getTime() + (target.dataset['duration'] * 1000)); //Position timetip timetip.moveToMouse(event); - //Inject timetip label - //Normally wouldn't do innerHTML but these values are calculated serverside and it saves us making a
element - timetip.tooltip.innerHTML = [ + //Normally wouldn't do innerHTML but these values are calculated serverside and it saves us making a
dom node + let timetipContents = [ `Start Time: ${utils.ux.timeStringFromDate(start, true)}`, - `End Time: ${utils.ux.timeStringFromDate(new Date(start.getTime() + (target.dataset['duration'] * 1000)), true)}` - ].join('
'); + `End Time: ${utils.ux.timeStringFromDate(end, true)}` + ]; + + //If the current time is after the start date, but before the end (we're scheduling to start now) + if(start.getTime() < date.getTime() && end.getTime() > date.getTime()){ + //Add start timestamp to item + timetipContents.push(`Start Timestamp: ${utils.ux.humieFriendlyDuration((date.getTime() - start.getTime()) / 1000, true)}`); + } + + //Inject timetip label + timetip.tooltip.innerHTML = timetipContents.join('
') //Calculate offset from rest of window const windowOffset = this.queueContainer.offsetTop + this.ownerDoc.defaultView.scrollY; diff --git a/www/js/utils.js b/www/js/utils.js index e7bfce2..c8d7508 100644 --- a/www/js/utils.js +++ b/www/js/utils.js @@ -99,7 +99,7 @@ class canopyUXUtils{ return outString; } - humieFriendlyDuration(seconds){ + humieFriendlyDuration(seconds, compact = false){ //If we have an invalid duration if(seconds <= 0){ //bitch, moan, and complain! @@ -119,41 +119,62 @@ class canopyUXUtils{ //Remove recorded minutes seconds -= minutes * 60; - //If we have an hour - if(hours == 1){ - //Add the string - timeStrings.push('1 Hour'); - //If we have hours - }else if(hours > 0){ - //Add the string - timeStrings.push(`${hours} Hours`); + //If we're rendering compact, alarm-clock style duration + if(compact){ + if(hours > 0){ + //Push hours, pad start with 0 + timeStrings.push(String(hours).padStart(2, '0')); + } + + if(hours > 0 || minutes > 0){ + //Push minutes, pad start with 0 + timeStrings.push(String(minutes).padStart(2, '0')); + } + + if(hours > 0 || minutes > 0 || seconds > 0){ + //Push seconds, pre-fix a 00: if hours and minutes are empty, round to nearest int and pad start with 0 + timeStrings.push(`${(hours == 0 && minutes == 0) ? '00:' : ''}${String(Math.round(seconds)).padStart(2, '0')}`); + } + + return timeStrings.join(':'); + //If we're rendering out using our words + }else{ + //If we have an hour + if(hours == 1){ + //Add the string + timeStrings.push('1 Hour'); + //If we have hours + }else if(hours > 0){ + //Add the string + timeStrings.push(`${hours} Hours`); + } + + //If we have a minute + if(minutes == 1){ + //Add the string + timeStrings.push('1 Minute'); + //If we have minutes + }else if(minutes > 0){ + //Add the string + timeStrings.push(`${minutes} Minutes`); + } + + //Add the 'and ' if we need it + const secondsPrefix = timeStrings.length > 0 ? 'and ' : ''; + + //If we have a second + if(seconds == 1){ + //Add the string + timeStrings.push(`${secondsPrefix}1 Second`); + //If we have more than a second + }else if(seconds > 1){ + //Add the string + timeStrings.push(`${secondsPrefix}${Math.round(seconds)} Seconds`); + } + + //Join the time strings together + return timeStrings.join(', '); } - - //If we have a minute - if(minutes == 1){ - //Add the string - timeStrings.push('1 Minute'); - //If we have minutes - }else if(minutes > 0){ - //Add the string - timeStrings.push(`${minutes} Minutes`); - } - - //Add the 'and ' if we need it - const secondsPrefix = timeStrings.length > 0 ? 'and ' : ''; - - //If we have a second - if(seconds == 1){ - //Add the string - timeStrings.push(`${secondsPrefix}1 Second`); - //If we have more than a second - }else if(seconds > 1){ - //Add the string - timeStrings.push(`${secondsPrefix}${Math.round(seconds)} Seconds`); - } - - //Join the time strings together - return timeStrings.join(', '); } //Update this and popup class to use nodes