Continued work on media scheduler
This commit is contained in:
parent
9d01b4c962
commit
d5a2a51be2
14 changed files with 415 additions and 54 deletions
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
21
src/views/partial/popup/scheduleMedia.ejs
Normal file
21
src/views/partial/popup/scheduleMedia.ejs
Normal 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>
|
||||
|
|
@ -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">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue