Continued work on channel-wide playlists.
This commit is contained in:
parent
72a89ae5ff
commit
70a68d9336
|
|
@ -18,6 +18,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
|||
const validator = require('validator');
|
||||
|
||||
//Local imports
|
||||
const queuedMedia = require('./queuedMedia');
|
||||
const loggerUtils = require('../../../utils/loggerUtils');
|
||||
const yanker = require('../../../utils/media/yanker');
|
||||
const channelModel = require('../../../schemas/channel/channelSchema');
|
||||
|
|
@ -35,8 +36,10 @@ module.exports = class{
|
|||
socket.on("getChannelPlaylists", () => {this.getChannelPlaylists(socket)});
|
||||
socket.on("createChannelPlaylist", (data) => {this.createChannelPlaylist(socket, data)});
|
||||
socket.on("addToChannelPlaylist", (data) => {this.addToChannelPlaylist(socket, data)});
|
||||
socket.on("queueChannelPlaylist", (data) => {this.queueChannelPlaylist(socket, data)});
|
||||
}
|
||||
|
||||
//--- USER-FACING PLAYLIST FUNCTIONS ---
|
||||
async getChannelPlaylists(socket, chanDB){
|
||||
//if we wherent handed a channel document
|
||||
if(chanDB == null){
|
||||
|
|
@ -84,4 +87,30 @@ module.exports = class{
|
|||
//Return playlists from channel doc
|
||||
socket.emit('chanPlaylists', chanDB.getPlaylists());
|
||||
}
|
||||
|
||||
async queueChannelPlaylist(socket, data, chanDB){
|
||||
//if we wherent handed a channel document
|
||||
if(chanDB == null){
|
||||
//Pull it based on channel name
|
||||
chanDB = await channelModel.findOne({name: this.channel.name});
|
||||
}
|
||||
|
||||
//Pull a valid start time from input, or make one up if we can't
|
||||
let start = this.channel.queue.getStart(data.start);
|
||||
|
||||
//Grab playlist from the DB
|
||||
let playlist = chanDB.getPlaylistByName(data.playlist);
|
||||
|
||||
//Create an empty array to hold our media list
|
||||
const mediaList = [];
|
||||
|
||||
//Iterate through playlist media
|
||||
for(let item of playlist.media){
|
||||
//Rehydrate playlist item and push it into the media list
|
||||
mediaList.push(item.rehydrate());
|
||||
}
|
||||
|
||||
//Convert array of standard media objects to queued media objects, and push to schedule
|
||||
this.channel.queue.scheduleMedia(queuedMedia.fromMediaArray(mediaList, start), socket, chanDB);
|
||||
}
|
||||
}
|
||||
|
|
@ -56,11 +56,12 @@ module.exports = class{
|
|||
socket.on("queue", (data) => {this.queueURL(socket, data)});
|
||||
socket.on("stop", (data) => {this.stopMedia(socket)});
|
||||
socket.on("delete", (data) => {this.deleteMedia(socket, data)});
|
||||
socket.on("move", (data) => {this.moveMedia(socket, data)});
|
||||
socket.on("clear", (data) => {this.deleteRange(socket, data)});
|
||||
socket.on("lock", (data) => {this.toggleLock(socket)});
|
||||
socket.on("move", (data) => {this.moveMedia(socket, data)});
|
||||
socket.on("lock", () => {this.toggleLock(socket)});
|
||||
}
|
||||
|
||||
//--- USER FACING QUEUEING FUNCTIONS ---
|
||||
async queueURL(socket, data){
|
||||
//Get the current channel from the database
|
||||
const chanDB = await channelModel.findOne({name: socket.chan});
|
||||
|
|
@ -68,7 +69,7 @@ module.exports = class{
|
|||
if((!this.locked && await chanDB.permCheck(socket.user, 'scheduleMedia')) || await chanDB.permCheck(socket.user, 'scheduleAdmin')){
|
||||
try{
|
||||
//Set url
|
||||
var url = data.url;
|
||||
let url = data.url;
|
||||
|
||||
//If we where given a bad URL
|
||||
if(!validator.isURL(url)){
|
||||
|
|
@ -94,14 +95,9 @@ module.exports = class{
|
|||
|
||||
//Set title
|
||||
const title = validator.escape(validator.trim(data.title));
|
||||
//set start
|
||||
var start = data.start;
|
||||
|
||||
//If start time isn't an integer after the current epoch
|
||||
if(start != null &&!validator.isInt(String(start), (new Date().getTime()))){
|
||||
//Null out time to tell the later parts of the function to start it now
|
||||
start = null;
|
||||
}
|
||||
//set start
|
||||
let start = this.getStart(data.start);
|
||||
|
||||
//Pull media list
|
||||
const mediaList = await yanker.yankMedia(url, title);
|
||||
|
|
@ -114,8 +110,56 @@ module.exports = class{
|
|||
return;
|
||||
}
|
||||
|
||||
//Queue the first media object given
|
||||
this.queueMedia(mediaList[0], start, socket);
|
||||
//Convert media list
|
||||
let queuedMediaList = queuedMedia.fromMediaArray(mediaList, start);
|
||||
|
||||
//schedule the media
|
||||
this.scheduleMedia(queuedMediaList, socket);
|
||||
}catch(err){
|
||||
return loggerUtils.socketExceptionHandler(socket, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stopMedia(socket){
|
||||
//If we're not currently playing anything
|
||||
if(this.nowPlaying == null){
|
||||
//If an originating socket was provided for this request
|
||||
if(socket != null){
|
||||
//Yell at the user for being an asshole
|
||||
loggerUtils.socketErrorHandler(socket, "No media playing!", "queue");
|
||||
}
|
||||
|
||||
//Ignore it
|
||||
return false;
|
||||
}
|
||||
|
||||
//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;
|
||||
|
||||
//End the media
|
||||
this.end();
|
||||
}
|
||||
|
||||
async deleteMedia(socket, data){
|
||||
//Get the current channel from the database
|
||||
const chanDB = await channelModel.findOne({name: socket.chan});
|
||||
|
||||
if((!this.locked && await chanDB.permCheck(socket.user, 'scheduleMedia')) || await chanDB.permCheck(socket.user, 'scheduleAdmin')){
|
||||
try{
|
||||
//If we don't have a valid UUID
|
||||
if(!validator.isUUID(data.uuid)){
|
||||
//Bitch, moan, complain...
|
||||
loggerUtils.socketErrorHandler(socket, "Bad UUID!", "queue");
|
||||
//and ignore it!
|
||||
return;
|
||||
}
|
||||
|
||||
//Remove media by UUID
|
||||
await this.removeMedia(data.uuid, socket);
|
||||
}catch(err){
|
||||
return loggerUtils.socketExceptionHandler(socket, err);
|
||||
}
|
||||
|
|
@ -150,28 +194,6 @@ module.exports = class{
|
|||
}
|
||||
}
|
||||
|
||||
async deleteMedia(socket, data){
|
||||
//Get the current channel from the database
|
||||
const chanDB = await channelModel.findOne({name: socket.chan});
|
||||
|
||||
if((!this.locked && await chanDB.permCheck(socket.user, 'scheduleMedia')) || await chanDB.permCheck(socket.user, 'scheduleAdmin')){
|
||||
try{
|
||||
//If we don't have a valid UUID
|
||||
if(!validator.isUUID(data.uuid)){
|
||||
//Bitch, moan, complain...
|
||||
loggerUtils.socketErrorHandler(socket, "Bad UUID!", "queue");
|
||||
//and ignore it!
|
||||
return;
|
||||
}
|
||||
|
||||
//Remove media by UUID
|
||||
await this.removeMedia(data.uuid, socket);
|
||||
}catch(err){
|
||||
return loggerUtils.socketExceptionHandler(socket, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async moveMedia(socket, data){
|
||||
//Get the current channel from the database
|
||||
const chanDB = await channelModel.findOne({name: socket.chan});
|
||||
|
|
@ -214,36 +236,33 @@ module.exports = class{
|
|||
}
|
||||
}
|
||||
|
||||
//Default start time to now + half a second to give everyone time to process shit
|
||||
queueMedia(inputMedia, start, socket){
|
||||
//If we have an invalid time
|
||||
if(start == null || start < (new Date).getTime()){
|
||||
//--- INTERNAL USE ONLY QUEUEING FUNCTIONS ---
|
||||
getStart(start){
|
||||
//Pull current time
|
||||
const now = new Date().getTime();
|
||||
|
||||
//If start time is null, or it isn't a valid integer after the current epoch
|
||||
if(start == null || !validator.isInt(String(start), {min: now})){
|
||||
//Get last item from schedule
|
||||
const lastItem = (Array.from(this.schedule)[this.schedule.size - 1]);
|
||||
|
||||
const now = new Date().getTime()
|
||||
|
||||
//if we have a last item
|
||||
if(lastItem != null){
|
||||
//If the last item has ended
|
||||
if(lastItem[1].getEndTime() < now){
|
||||
start = now + 5;
|
||||
//If it hasn't started yet
|
||||
//Throw it on in five ms
|
||||
return now;
|
||||
//If it hasn't ended yet
|
||||
}else{
|
||||
//Throw it on five ms after the last item
|
||||
start = lastItem[1].getEndTime() + 5;
|
||||
return lastItem[1].getEndTime() + 5;
|
||||
}
|
||||
//If we don't have a last item
|
||||
}else{
|
||||
//Throw it on five ms after the last item
|
||||
start = now + 5;
|
||||
//Throw it on in five ms
|
||||
return now;
|
||||
}
|
||||
}
|
||||
|
||||
//Create a new media queued object, set start time to now
|
||||
const mediaObj = queuedMedia.fromMedia(inputMedia, start, 0);
|
||||
|
||||
//schedule the media
|
||||
this.scheduleMedia(mediaObj, socket);
|
||||
}
|
||||
|
||||
refreshNextTimer(volatile = false){
|
||||
|
|
@ -305,7 +324,7 @@ module.exports = class{
|
|||
}
|
||||
}
|
||||
|
||||
async rescheduleMedia(uuid, start = new Date().getTime() + 5, socket){
|
||||
async rescheduleMedia(uuid, start = new Date().getTime(), socket){
|
||||
//Find our media, don't remove it yet since we want to do some more testing first
|
||||
const media = this.getItemByUUID(uuid);
|
||||
|
||||
|
|
@ -355,7 +374,7 @@ module.exports = class{
|
|||
|
||||
//Attempt to schedule media at given time
|
||||
//Otherwise, if it returns false for fuckup
|
||||
if(!(await this.scheduleMedia(media, socket))){
|
||||
if(!(await this.scheduleMedia([media], socket))){
|
||||
//Reset start time
|
||||
media.startTime = oldStart;
|
||||
|
||||
|
|
@ -363,7 +382,7 @@ module.exports = class{
|
|||
media.startTimeStamp = 0;
|
||||
|
||||
//Schedule in old slot
|
||||
this.scheduleMedia(media, socket, null, true);
|
||||
this.scheduleMedia([media], socket, null, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -474,30 +493,7 @@ module.exports = class{
|
|||
return media;
|
||||
}
|
||||
|
||||
stopMedia(socket){
|
||||
//If we're not currently playing anything
|
||||
if(this.nowPlaying == null){
|
||||
//If an originating socket was provided for this request
|
||||
if(socket != null){
|
||||
//Yell at the user for being an asshole
|
||||
loggerUtils.socketErrorHandler(socket, "No media playing!", "queue");
|
||||
}
|
||||
|
||||
//Ignore it
|
||||
return false;
|
||||
}
|
||||
|
||||
//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;
|
||||
|
||||
//End the media
|
||||
this.end();
|
||||
}
|
||||
|
||||
async scheduleMedia(mediaObj, socket, chanDB, force = false, volatile = false, startVolatile = false){
|
||||
async scheduleMedia(media, socket, chanDB, force = false, volatile = false, startVolatile = false){
|
||||
/* 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
|
||||
I don't want to store everything in a sparse array because that *feels* icky, and would probably be a pain in the ass.
|
||||
|
|
@ -512,7 +508,7 @@ module.exports = class{
|
|||
since it ONLY loops through defiened items within the array. No skipped empties for your runtime to worry about.
|
||||
Even more preformance benefits can be had by using a real for loop on the arrays keys, skipping the overhead of forEach entirely.
|
||||
This might seem gross but it completely avoids the computational workload of a sorting algo, especially when you consider
|
||||
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...
|
||||
that, no matter what, re-ordering the schedule map would've required us to iterate through and rebuild the map anyways...
|
||||
|
||||
|
||||
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.
|
||||
|
|
@ -526,6 +522,8 @@ module.exports = class{
|
|||
https://community.appsmith.com/content/blog/dark-side-foreach-why-you-should-think-twice-using-it
|
||||
*/
|
||||
|
||||
let mediaObj = media[0];
|
||||
|
||||
//If someone is trying to schedule something that starts and ends in the past
|
||||
if((mediaObj.getEndTime() < new Date().getTime()) && !force){
|
||||
//If an originating socket was provided for this request
|
||||
|
|
@ -539,9 +537,16 @@ module.exports = class{
|
|||
//If the item has already started
|
||||
if((mediaObj.startTime < new Date().getTime()) && !force){
|
||||
//Set time stamp to existing timestamp plus the difference between the orginal start-date and now
|
||||
mediaObj.startTimeStamp = mediaObj.startTimeStamp + ((new Date().getTime() - mediaObj.startTime) / 1000)
|
||||
const calculatedTimeStamp = mediaObj.startTimeStamp + ((new Date().getTime() - mediaObj.startTime) / 1000)
|
||||
|
||||
//If the calculated time stamp is more than negligible, and therefore not simply caused by serverside processing time
|
||||
if(calculatedTimeStamp > 5){
|
||||
//Set the media timestamp
|
||||
mediaObj.startTimeStamp = calculatedTimeStamp;
|
||||
|
||||
//Start the item now
|
||||
mediaObj.startTime = new Date().getTime() + 5;
|
||||
mediaObj.startTime = new Date().getTime();
|
||||
}
|
||||
}
|
||||
|
||||
//If there's already something queued right now
|
||||
|
|
@ -935,7 +940,7 @@ module.exports = class{
|
|||
//If the media hasn't ended yet
|
||||
if(wasPlaying.getEndTime() > now){
|
||||
//Re-Schedule it in RAM
|
||||
await this.scheduleMedia(wasPlaying, null, chanDB, true, true, true);
|
||||
await this.scheduleMedia([wasPlaying], null, chanDB, true, true, true);
|
||||
//Otherwise, if it has
|
||||
}else{
|
||||
//Null out nowPlaying
|
||||
|
|
@ -961,7 +966,7 @@ module.exports = class{
|
|||
|
||||
|
||||
//Re-Schedule it in RAM
|
||||
await this.scheduleMedia(mediaObj, null, chanDB, true, true, false);
|
||||
await this.scheduleMedia([mediaObj], null, chanDB, true, true, false);
|
||||
}else{
|
||||
//If the media should be playing now
|
||||
if(mediaObj.getEndTime() > now){
|
||||
|
|
@ -969,7 +974,7 @@ module.exports = class{
|
|||
chanDB.media.nowPlaying = record;
|
||||
|
||||
//Re-Schedule it in RAM
|
||||
await this.scheduleMedia(mediaObj, null, chanDB, true, true, true);
|
||||
await this.scheduleMedia([mediaObj], null, chanDB, true, true, true);
|
||||
//If it's been ended
|
||||
}else{
|
||||
//Archive ended media
|
||||
|
|
|
|||
|
|
@ -54,6 +54,23 @@ module.exports = class extends media{
|
|||
startTimeStamp);
|
||||
}
|
||||
|
||||
static fromMediaArray(mediaList, start){
|
||||
//Queued Media List
|
||||
const queuedMediaList = [];
|
||||
//Start Time Offset
|
||||
let startOffset = 0;
|
||||
|
||||
for(let media of mediaList){
|
||||
//Convert mediaObj to queuedMedia and push to the back of the list
|
||||
queuedMediaList.push(this.fromMedia(media, start + startOffset, 0));
|
||||
|
||||
//Set start offset to end of the current item
|
||||
startOffset += (media.duration * 1000) + 5;
|
||||
}
|
||||
|
||||
return queuedMediaList;
|
||||
}
|
||||
|
||||
//methods
|
||||
genUUID(){
|
||||
this.uuid = crypto.randomUUID();
|
||||
|
|
|
|||
|
|
@ -605,13 +605,16 @@ channelSchema.methods.addToPlaylist = async function(name, media){
|
|||
//If the playlist name matches
|
||||
if(playlist.name == name){
|
||||
//Push the given media into the found playlist
|
||||
//this.media.playlists[listIndex].push(media);
|
||||
|
||||
//Make note of the found index
|
||||
foundIndex = listIndex
|
||||
}
|
||||
});
|
||||
|
||||
//Set media status schema discriminator
|
||||
media.status = 'saved';
|
||||
|
||||
//Add the media to the playlist
|
||||
this.media.playlists[foundIndex].media.push(media);
|
||||
|
||||
//Save the changes made to the chan doc
|
||||
|
|
|
|||
|
|
@ -48,4 +48,5 @@ const mediaSchema = new mongoose.Schema({
|
|||
}
|
||||
);
|
||||
|
||||
|
||||
module.exports = mediaSchema;
|
||||
|
|
@ -25,7 +25,8 @@ const playlistMediaProperties = new mongoose.Schema({
|
|||
uuid: {
|
||||
type: mongoose.SchemaTypes.UUID,
|
||||
required:true,
|
||||
unique: true
|
||||
unique: true,
|
||||
default: crypto.randomUUID()
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ const {mongoose} = require('mongoose');
|
|||
//Local Imports
|
||||
const playlistMediaSchema = require('./playlistMediaSchema');
|
||||
|
||||
module.exports = new mongoose.Schema({
|
||||
const playlistSchema = new mongoose.Schema({
|
||||
name: {
|
||||
type: mongoose.SchemaTypes.String,
|
||||
required: true,
|
||||
|
|
@ -32,3 +32,9 @@ module.exports = new mongoose.Schema({
|
|||
default: []
|
||||
}]
|
||||
});
|
||||
|
||||
playlistSchema.methods.test = function(){
|
||||
console.log(this.name);
|
||||
}
|
||||
|
||||
module.exports = playlistSchema;
|
||||
Loading…
Reference in a new issue