Added schedule clearing and scroll to drag to schedule panel.

This commit is contained in:
rainbow napkin 2025-02-07 06:01:20 -05:00
parent 56ab5a16ec
commit c04edb6691
8 changed files with 466 additions and 120 deletions

View file

@ -49,6 +49,7 @@ module.exports = class{
socket.on("queue", (data) => {this.queueURL(socket, data)});
socket.on("delete", (data => {this.deleteMedia(socket, data)}));
socket.on("move", (data => {this.moveMedia(socket, data)}));
socket.on("clear", (data => {this.deleteRange(socket, data)}));
}
@ -57,8 +58,6 @@ module.exports = class{
//Set url
var url = data.url;
//pull URL and start time from data
//let {url, start, title} = data;
//If we where given a bad URL
if(!validator.isURL(url)){
//Attempt to fix the situation by encoding it
@ -122,6 +121,30 @@ module.exports = class{
}
}
deleteRange(socket, data){
try{
//If start time isn't an integer
if(data.start != null && !validator.isInt(String(data.start))){
//Bitch, moan, complain...
loggerUtils.socketErrorHandler(socket, "Bad start date!", "queue");
//and ignore it!
return;
}
//If end time isn't an integer
if(data.end != null && !validator.isInt(String(data.end))){
//Bitch, moan, complain...
loggerUtils.socketErrorHandler(socket, "Bad end date!", "queue");
//and ignore it!
return;
}
this.removeRange(data.start, data.end, socket);
}catch(err){
return loggerUtils.socketExceptionHandler(socket, err);
}
}
deleteMedia(socket, data){
try{
//If we don't have a valid UUID
@ -150,9 +173,9 @@ module.exports = class{
}
//If start time isn't an integer after the current epoch
if(data.start != null && !validator.isInt(String(data.start), new Date().getTime())){
if(data.start != null && !validator.isInt(String(data.start))){
//Null out time to tell the later parts of the function to start it now
data.start = null;
data.start = undefined;
}
//Move media by UUID
@ -177,17 +200,35 @@ module.exports = class{
//If we have no next item
if(nextItem == null){
//Fuck off and die
return;
//Get current item
const currentItem = this.getItemAtEpoch()
//If we have a current item and it isn't currently playing
if(currentItem != null && (this.nowPlaying == null || currentItem.uuid != this.nowPlaying.uuid)){
//Start the found item at w/ a pre-calculated time stamp to reflect the given start time
this.start(currentItem, Math.round((new Date().getTime() - currentItem.startTime) / 1000));
}
//otherwise if we have an item
}else{
//Calculate the amount of time in ms that the next item will start in
const startsIn = nextItem.startTime - new Date().getTime();
//Clear out any item that might be up next
clearTimeout(this.nextTimer);
//Set the next timer
this.nextTimer = setTimeout(()=>{this.start(nextItem)}, startsIn);
}
}
//Calculate the amount of time in ms that the next item will start in
const startsIn = nextItem.startTime - new Date().getTime();
removeRange(start = new Date().getTime() - 60 * 1000, end = new Date().getTime(), socket){
//Find items within given range
const foundItems = this.getItemsBetweenEpochs(start, end);
//Clear out any item that might be up next
clearTimeout(this.nextTimer);
//Set the next timer
this.nextTimer = setTimeout(()=>{this.start(nextItem)}, startsIn);
//For each item
for(let item of foundItems){
//Remove media
this.removeMedia(item.uuid, socket);
}
}
rescheduleMedia(uuid, start = new Date().getTime() + 50, socket){
@ -205,11 +246,21 @@ module.exports = class{
return;
}
//Grab the old start time for safe keeping
const oldStart = media.startTime;
//Set media time
media.startTime = start;
//Re-schedule the media for the given time
this.scheduleMedia(media, socket);
//Attempt to schedule media at given time
//Otherwise, if it returns false for fuckup
if(!this.scheduleMedia(media, socket)){
//Reset start time
media.startTime = oldStart;
//Schedule in old slot
this.scheduleMedia(media, socket);
}
}
removeMedia(uuid, socket){
@ -224,7 +275,7 @@ module.exports = class{
loggerUtils.socketErrorHandler(socket, "Cannot delete non-existant item!", "queue");
}
//Ignore it
return;
return false;
}
//If we're currently playing the requested item.
@ -264,11 +315,11 @@ module.exports = class{
that, no matter what, re-ordering the schedule map would've required us to iterate through and convert it to an array and back anyways...
Also it looks like due to implementation limitations, epochs stored as MS are too large for array elements, so we store them as seconds.
Also it looks like due to implementation limitations, epochs stored as MS are too large for array elements, so we store them there as seconds.
This also means that our current implementation will break exactly on unix epoch 4294967295 (Feb 7, 2106 6:28:15 AM UTC)
Hopefully javascript arrays will allow for larger lengths by then. If not blame the W3C :P
If for some reason they haven't we could probably implement an object that wraps a 2d array and set/gets it using modulo/devision/multiplication
If for some reason they haven't and we're not dead, we could probably implement an object that wraps a 2d array and set/gets it using modulo/devision/multiplication
Further Reading:
https://stackoverflow.com/questions/59480871/foreach-vs-object-keys-foreach-performance-on-sparse-arrays
@ -276,14 +327,14 @@ module.exports = class{
*/
//If there's already something queued right now
if(this.getItemAtEpoch(mediaObj.startTime) != null){
if(this.getItemAtEpoch(mediaObj.startTime) != null || this.getItemAtEpoch(mediaObj.startTime + (mediaObj.duration * 1000))){
//If an originating socket was provided for this request
if(socket != null){
//Yell at the user for being an asshole
loggerUtils.socketErrorHandler(socket, "This time slot has already been taken in the queue!", "queue");
}
//Ignore it
return;
return false;
}
@ -315,14 +366,17 @@ module.exports = class{
//Refresh the next timer to ensure whatever comes on next is right
this.refreshNextTimer();
//return media object for use
return mediaObj;
}
start(mediaObj){
start(mediaObj, timestamp = 0){
//Silently end the media
this.end(true);
//reset current timestamp
this.timestamp = 0;
this.timestamp = timestamp;
//Set current playing media
this.nowPlaying = mediaObj;
@ -335,6 +389,9 @@ module.exports = class{
//Setup the next video
this.refreshNextTimer();
//return media object for use
return mediaObj;
}
sync(){
@ -377,6 +434,23 @@ module.exports = class{
}
}
getItemsBetweenEpochs(start, end){
//Create an empty array to hold found items
const foundItems = [];
//Loop through scheduled items
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]);
}
}
//Return any found items
return foundItems;
}
getItemAtEpoch(epoch = new Date().getTime()){
//Loop through scheduled items
for(let item of this.schedule){

View file

@ -0,0 +1,28 @@
<%# Canopy - The next generation of stoner streaming software
Copyright (C) 2024-2025 Rainbownapkin and the TTN Community
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. %>
<link rel="stylesheet" type="text/css" href="/css/popup/clearMedia.css">
<h3 class="popup-title">Clear Media</h3>
<div class="clear-media-popup-div">
<span class="clear-media-input-span">
<label for="clear-media-popup-start-time-prompt">Start:</label>
<input name="clear-media-popup-start-time-prompt" type="datetime-local" id="clear-media-popup-start-time-prompt">
</span>
<span class="clear-media-input-span">
<label for="clear-media-popup-end-time-prompt">End:</label>
<input name="clear-media-popup-end-time-prompt" type="datetime-local" id="clear-media-popup-end-time-prompt">
</span>
<button class="danger-button" id="clear-media-popup-clear-button">Clear</button>
</div>