Continued work on media scheduler

This commit is contained in:
rainbow napkin 2025-01-28 09:43:39 -05:00
parent 9d01b4c962
commit d5a2a51be2
14 changed files with 415 additions and 54 deletions

View file

@ -44,16 +44,38 @@ module.exports = class{
defineListeners(socket){
socket.on("queue", (data) => {this.queueURL(socket, data)});
socket.on("delete", (data => {this.deleteMedia(socket, data)}));
socket.on("move", (data => {this.moveMedia(socket, data)}));
}
async queueURL(socket, data){
try{
//pull URL and start time from data
let {url, start} = data;
let {url, start, title} = data;
//Pull media list
const mediaList = await yanker.yankMedia(url);
const mediaList = await yanker.yankMedia(url, title);
//If we didn't find any media
if(mediaList == null || mediaList.length <= 0){
//Bitch, moan, complain...
loggerUtils.socketErrorHandler(socket, "No media found!", "queue");
//and ignore it!
return;
}
//If we have an invalid time
if(start == null || start < (new Date).getTime()){
//Get last item from schedule
const lastItem = (Array.from(this.schedule)[this.schedule.size - 1]);
//if we have a last item
if(lastItem != null){
//Throw it on five ms after the last item
start = lastItem[1].startTime + (lastItem[1].duration * 1000) + 5;
}
}
//Queue the first media object given
this.queueMedia(mediaList[0], start, socket);
@ -62,6 +84,24 @@ module.exports = class{
}
}
deleteMedia(socket, data){
try{
//Remove media by UUID
this.removeMedia(data.uuid, socket);
}catch(err){
return loggerUtils.socketExceptionHandler(socket, err);
}
}
moveMedia(socket, data){
try{
//Move media by UUID
this.rescheduleMedia(data.uuid, data.start, socket);
}catch(err){
return loggerUtils.socketExceptionHandler(socket, err);
}
}
//Default start time to now + half a second to give everyone time to process shit
queueMedia(inputMedia, start = new Date().getTime() + 50, socket){
//Create a new media queued object, set start time to now
@ -69,9 +109,6 @@ module.exports = class{
//schedule the media
this.scheduleMedia(mediaObj, socket);
//Refresh the next timer to ensure whatever comes on next is right
this.refreshNextTimer();
}
refreshNextTimer(){
@ -93,6 +130,62 @@ module.exports = class{
this.nextTimer = setTimeout(()=>{this.start(nextItem)}, startsIn);
}
rescheduleMedia(uuid, start = new Date().getTime() + 50, socket){
//Find and remove media from the schedule by UUID
const media = this.removeMedia(uuid);
//If we got a bad request
if(media == null){
//If an originating socket was provided for this request
if(socket != null){
//Yell at the user for being an asshole
loggerUtils.socketErrorHandler(socket, "Cannot move non-existant item!", "queue");
}
//Ignore it
return;
}
//Set media time
media.startTime = start;
//Re-schedule the media for the given time
this.scheduleMedia(media, socket);
}
removeMedia(uuid, socket){
//Get requested media
const media = this.getItemByUUID(uuid);
//If we got a bad request
if(media == null){
//If an originating socket was provided for this request
if(socket != null){
//Yell at the user for being an asshole
loggerUtils.socketErrorHandler(socket, "Cannot delete non-existant item!", "queue");
}
//Ignore it
return;
}
//If we're currently playing the requested item.
if(this.nowPlaying != null && this.nowPlaying.uuid == uuid){
//End playback
this.end();
}
//Take the item out of the schedule
this.schedule.delete(media.startTime);
//Refresh next timer
this.refreshNextTimer();
//Broadcast the channel queue
this.broadcastQueue();
//return found media in-case our calling function needs it :P
return media;
}
scheduleMedia(mediaObj, socket){
/* 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
@ -159,6 +252,9 @@ module.exports = class{
//Broadcast the channel queue
this.broadcastQueue();
//Refresh the next timer to ensure whatever comes on next is right
this.refreshNextTimer();
}
start(mediaObj){
@ -270,6 +366,17 @@ module.exports = class{
}
}
getItemByUUID(uuid){
//Iterate through the schedule
for(let item of this.schedule){
//If the uuid matches
if(item[1].uuid == uuid){
//return the found item
return item[1];
}
}
}
sendMedia(socket){
//Create data object
const data = {

View file

@ -21,7 +21,7 @@ const validator = require('validator');//No express here, so regular validator i
const iaUtil = require('./internetArchiveUtils');
const media = require('../../app/channel/media/media');
module.exports.yankMedia = async function(url){
module.exports.yankMedia = async function(url, title){
const pullType = await this.getMediaType(url);
if(pullType == 'ia'){
@ -34,13 +34,21 @@ module.exports.yankMedia = async function(url){
for(let file of mediaInfo.files){
//Split file path by directories
const path = file.name.split('/');
//pull filename from path
const name = path[path.length - 1];
//Construct link from pulled info
const link = `https://archive.org/download/${mediaInfo.metadata.identifier}/${file.name}`;
//Create new media object from file info
mediaList.push(new media(name, name, link, link, 'ia', Number(file.length)));
//if we where handed a null title
if(title == null || title == ''){
//Create new media object from file info substituting filename for title
mediaList.push(new media(name, name, link, link, 'ia', Number(file.length)));
}else{
//Create new media object from file info
mediaList.push(new media(title, name, link, link, 'ia', Number(file.length)));
}
}
//return media object list
@ -52,12 +60,16 @@ module.exports.yankMedia = async function(url){
}
module.exports.getMediaType = async function(url){
//Encode URI in-case we where handed something a little too humie friendly
url = encodeURI(url);
//Check if we have a valid url
if(!validator.isURL(url)){
//If not toss the fucker out
return null;
}
//If we have link to a resource from archive.org
if(url.match(/^https\:\/\/archive.org\//g)){
//return internet archive code

View file

@ -16,7 +16,16 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
<link rel="stylesheet" type="text/css" href="/css/panel/queue.css">
<div id="queue-panel-layout-controller">
<div id="queue-controls">
<span id="queue-media-prompts">
<div id="queue-control-buttons">
<button id="queue-add-media"><i class="bi-plus-lg"></i></button>
<button id="queue-search-media"><i class="bi-search"></i></button>
<button id="queue-playlists"><i class="bi-list"></i></button>
<button id="queue-scroll-lock" class="positive-button"><i class="bi-clock-fill"></i></button>
<button id="queue-export-clear"><i class="bi-trash-fill"></i></button>
<button id="queue-export-date"><i class="bi-calendar-fill"></i></button>
<button id="queue-export-lock" class="positive-button"><i class="bi-unlock-fill"></i></button>
</div>
<div id="queue-media-prompts" style="display: none;">
<div class="panel-control-prompt control-prompt">
<input placeholder="Media Link..." id="media-link-input" class="control-prompt">
</div>
@ -24,9 +33,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
<%# Probably not the cleanest way to do this but fuggit %>
<input placeholder="Media Name..." id="media-name-input" class="control-prompt">
<button id="queue-last-button" class="positive-button">Queue Last</button>
<button id="queue-at-button" class="positive-button">Queue At...</button>
</div>
</span>
</div>
</div>
<div id="queue-control-offset"></div>
<div id="queue">
<div id="queue-marker-container"></div>
<div id="queue-container"></div>

View file

@ -0,0 +1,21 @@
<%# 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/schedule.css"> %>
<h3 class="popup-title">Schedule Media</h3>
<div>
<input type="datetime-local" id="schedule-media-popup-time-prompt">
<button class="positive-button" id="schedule-media-popup-schedule-button">Schedule</button>
</div>

View file

@ -1,4 +1,4 @@
<%# Canopy - The next generation of stoner streaming software
<!-- 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
@ -12,7 +12,7 @@ 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/>. %>
along with this program. If not, see <https://www.gnu.org/licenses/>. -->
<%# Technically favicon has nothing to do with .css, but it's still looks related, uses a link tag, and globally used :P %>
<link rel="icon" type="image/x-icon" href="/img/sweet_leaf_simple.png">
<link rel="stylesheet" href="/lib/bootstrap-icons/font/bootstrap-icons.css">