Added persistent rescheduling of nowPlaying after server goes down.

This commit is contained in:
rainbow napkin 2025-02-11 07:39:20 -05:00
parent 179a10fb72
commit a41541d07b
10 changed files with 124 additions and 25 deletions

View file

@ -19,6 +19,7 @@ const connectedUser = require('./connectedUser');
const queue = require('./media/queue'); const queue = require('./media/queue');
const flairModel = require('../../schemas/flairSchema'); const flairModel = require('../../schemas/flairSchema');
const permissionModel = require('../../schemas/permissionSchema'); const permissionModel = require('../../schemas/permissionSchema');
const channelModel = require('../../schemas/channel/channelSchema');
module.exports = class{ module.exports = class{
constructor(server, chanDB){ constructor(server, chanDB){
@ -27,7 +28,7 @@ module.exports = class{
this.tokeCommands = chanDB.tokeCommands; this.tokeCommands = chanDB.tokeCommands;
//Keeping these in a map was originally a vestige but it's more preformant than an array or object so :P //Keeping these in a map was originally a vestige but it's more preformant than an array or object so :P
this.userList = new Map(); this.userList = new Map();
this.queue = new queue(server, this); this.queue = new queue(server, chanDB, this);
} }
async handleConnection(userDB, chanDB, socket){ async handleConnection(userDB, chanDB, socket){

View file

@ -24,7 +24,7 @@ const loggerUtils = require('../../../utils/loggerUtils');
const channelModel = require('../../../schemas/channel/channelSchema'); const channelModel = require('../../../schemas/channel/channelSchema');
module.exports = class{ module.exports = class{
constructor(server, channel){ constructor(server, chanDB, channel){
//Set server //Set server
this.server = server this.server = server
//Set channel //Set channel
@ -47,6 +47,9 @@ module.exports = class{
//create boolean to hold schedule lock //create boolean to hold schedule lock
this.locked = false; this.locked = false;
//Rehydrate channel queue from database
this.rehydrateQueue(chanDB);
} }
defineListeners(socket){ defineListeners(socket){
@ -441,8 +444,8 @@ module.exports = class{
return false; return false;
} }
//If the item has already started and it's not being forced //If the item has already started
if((mediaObj.startTime < new Date().getTime())){ if((mediaObj.startTime < new Date().getTime()) && !force){
//Set time stamp to existing timestamp plus the difference between the orginal start-date and now //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) mediaObj.startTimeStamp = mediaObj.startTimeStamp + ((new Date().getTime() - mediaObj.startTime) / 1000)
//Start the item now //Start the item now
@ -493,7 +496,7 @@ module.exports = class{
return mediaObj; return mediaObj;
} }
start(mediaObj, timestamp = mediaObj.startTimeStamp){ async start(mediaObj, timestamp = mediaObj.startTimeStamp){
//Silently end the media //Silently end the media
this.end(true); this.end(true);
@ -503,6 +506,19 @@ module.exports = class{
//Set current playing media //Set current playing media
this.nowPlaying = mediaObj; this.nowPlaying = mediaObj;
try{
//Get our channel
const chanDB = await channelModel.findOne({name: this.channel.name});
//Set the now playing queued media document
chanDB.media.nowPlaying = mediaObj;
//Save the channel
await chanDB.save();
}catch(err){
loggerUtils.localExceptionHandler(err);
}
//Send play signal out to the channel //Send play signal out to the channel
this.sendMedia(); this.sendMedia();
@ -655,4 +671,31 @@ module.exports = class{
broadcastQueue(){ broadcastQueue(){
this.server.io.in(this.channel.name).emit('queue',{queue: Array.from(this.schedule)}) this.server.io.in(this.channel.name).emit('queue',{queue: Array.from(this.schedule)})
} }
async rehydrateQueue(chanDB){
try{
//If we didn't get handed a freebie
if(chanDB == null){
//Go out and get it done ourselves
chanDB = await channelModel.findOne({name:this.channel.name});
}
//If we couldn't find the channel
if(chanDB == null){
//FUCK
throw new Error(`Unable to find channel document ${this.channel.name} while rehydrating queue!`);
}
//Rehydrate the currently playing item
const wasPlaying = chanDB.media.nowPlaying.rehydrate();
//Schedule it
this.scheduleMedia(wasPlaying, null, true);
//if something fucked up
}catch(err){
//bitch about it in the server console
loggerUtils.localExceptionHandler(err);
}
}
} }

View file

@ -18,7 +18,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
const media = require('./media'); const media = require('./media');
module.exports = class extends media{ module.exports = class extends media{
constructor(title, fileName, url, id, type, duration, startTime, startTimeStamp){ constructor(title, fileName, url, id, type, duration, startTime, startTimeStamp, earlyEnd, uuid){
//Call derived constructor //Call derived constructor
super(title, fileName, url, id, type, duration); super(title, fileName, url, id, type, duration);
//Set media start time //Set media start time
@ -27,10 +27,17 @@ module.exports = class extends media{
this.startTimeStamp = startTimeStamp; this.startTimeStamp = startTimeStamp;
//Create empty variable to hold early end if media is stopped early //Create empty variable to hold early end if media is stopped early
this.earlyEnd = null; this.earlyEnd = null;
//Set status for discriminator key
this.status = 'queued';
//Generate id unique to this specific entry of this specific file within this specific channel's queue //If we have a null uuid (can't use default argument because of 'this')
//That way even if we have six copies of the same video queued, we can still uniquely idenitify each instance if(uuid == null){
this.genUUID(); //Generate id unique to this specific entry of this specific file within this specific channel's queue
//That way even if we have six copies of the same video queued, we can still uniquely idenitify each instance
this.genUUID();
}else{
this.uuid = uuid;
}
} }
//statics //statics

View file

@ -29,6 +29,7 @@ const emoteModel = require('../emoteSchema');
//DB Schemas //DB Schemas
const channelPermissionSchema = require('./channelPermissionSchema'); const channelPermissionSchema = require('./channelPermissionSchema');
const channelBanSchema = require('./channelBanSchema'); const channelBanSchema = require('./channelBanSchema');
const queuedMediaSchema = require('./media/queuedMediaSchema');
//Utils //Utils
const { exceptionHandler, errorHandler } = require('../../utils/loggerUtils'); const { exceptionHandler, errorHandler } = require('../../utils/loggerUtils');
@ -98,6 +99,11 @@ const channelSchema = new mongoose.Schema({
default: emoteModel.typeEnum[0] default: emoteModel.typeEnum[0]
} }
}], }],
media: {
nowPlaying: queuedMediaSchema,
scheduled: [queuedMediaSchema],
archived: [queuedMediaSchema],
},
//Thankfully we don't have to keep track of alts, ips, or deleted users so this should be a lot easier than site-wide bans :P //Thankfully we don't have to keep track of alts, ips, or deleted users so this should be a lot easier than site-wide bans :P
banList: [channelBanSchema] banList: [channelBanSchema]
}); });

View file

@ -42,6 +42,10 @@ const mediaSchema = new mongoose.Schema({
type: mongoose.SchemaTypes.Number, type: mongoose.SchemaTypes.Number,
required: true, required: true,
}, },
}); },
{
discriminatorKey: 'status'
}
);
module.exports = mediaSchema; module.exports = mediaSchema;

View file

@ -19,16 +19,44 @@ const {mongoose} = require('mongoose');
//Local Imports //Local Imports
const mediaSchema = require('./mediaSchema'); const mediaSchema = require('./mediaSchema');
const queuedMedia = require('../../../app/channel/media/queuedMedia');
const queuedProperties = new mongoose.Schema({ const queuedProperties = new mongoose.Schema({
startTime: { startTime: {
type: mongoose.SchemaTypes.Number, type: mongoose.SchemaTypes.Number,
required: true, required: true,
}, },
startTimeStamp: {
type: mongoose.SchemaTypes.Number,
required: false,
},
earlyEnd: {
type: mongoose.SchemaTypes.Number,
required: false,
},
uuid: { uuid: {
type: mongoose.SchemaTypes.UUID, type: mongoose.SchemaTypes.UUID,
required: true, required: true,
} }
},
{
discriminatorKey: 'status'
}); });
module.exports = mediaSchema.descriminiator('queued', queuedProperties); //methods
queuedProperties.methods.rehydrate = function(){
return new queuedMedia(
this.title,
this.fileName,
this.url,
this.id,
this.type,
this.duration,
this.startTime,
this.startTimeStamp,
this.earlyEnd,
this.uuid.toString()
);
}
module.exports = mediaSchema.discriminator('queued', queuedProperties);

View file

@ -26,12 +26,17 @@ module.exports.errorHandler = function(res, msg, type = "Generic", status = 400)
} }
} }
module.exports.exceptionHandler = function(res, err){ module.exports.localExceptionHandler = function(err){
//If we're being verbose //If we're being verbose
if(config.verbose){ if(config.verbose){
//Log the error //Log the error
console.log(err) console.log(err)
} }
}
module.exports.exceptionHandler = function(res, err){
//Locally handle the exception
module.exports.localExceptionHandler(err);
//if not yell at the browser for fucking up, and tell it what it did wrong. //if not yell at the browser for fucking up, and tell it what it did wrong.
module.exports.errorHandler(res, err.message, "Caught Exception"); module.exports.errorHandler(res, err.message, "Caught Exception");
@ -42,11 +47,8 @@ module.exports.socketErrorHandler = function(socket, msg, type = "Generic"){
} }
module.exports.socketExceptionHandler = function(socket, err){ module.exports.socketExceptionHandler = function(socket, err){
//If we're being verbose //Locally handle the exception
if(config.verbose){ module.exports.localExceptionHandler(err);
//Log the error
console.log(err)
}
//if not yell at the browser for fucking up, and tell it what it did wrong. //if not yell at the browser for fucking up, and tell it what it did wrong.
return module.exports.socketErrorHandler(socket, err.msg, "Caught Exception"); return module.exports.socketErrorHandler(socket, err.msg, "Caught Exception");

View file

@ -16,8 +16,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
<link rel="stylesheet" type="text/css" href="/css/popup/changePassword.css"> <link rel="stylesheet" type="text/css" href="/css/popup/changePassword.css">
<h3 id="password-change-popup-title" class="popup-title">Update Password</h3> <h3 id="password-change-popup-title" class="popup-title">Update Password</h3>
<div id="password-change-popup-content"> <div id="password-change-popup-content">
<p id="password-change-popup-caption">Enter new email and current password below:</p> <p id="password-change-popup-caption">Enter new and confirm current password below:</p>
<input type="password" placeholder="password" id="password-change-popup-old-password"> <input type="password" placeholder="Current Password" id="password-change-popup-old-password">
<input type="password" placeholder="new password" id="password-change-popup-new-password"> <input type="password" placeholder="New Password" id="password-change-popup-new-password">
<input type="password" placeholder="confirm new password" id="password-change-popup-confirm-new-password"> <input type="password" placeholder="Confirm New Password" id="password-change-popup-confirm-new-password">
</div> </div>

View file

@ -80,8 +80,10 @@ class queuePanel extends panelObj{
defineListeners(){ defineListeners(){
//Render queue when we receive a new copy of the queue data from the server //Render queue when we receive a new copy of the queue data from the server
this.client.socket.on("clientMetadata", (data) => {this.renderQueue();}); this.client.socket.on("clientMetadata", () => {this.renderQueue();});
this.client.socket.on("queue", (data) => {this.renderQueue();}); this.client.socket.on("queue", () => {this.renderQueue();});
this.client.socket.on("start", () => {this.renderQueue();});
this.client.socket.on("end", () => {this.renderQueue();});
this.client.socket.on("lock", this.handleScheduleLock.bind(this)); this.client.socket.on("lock", this.handleScheduleLock.bind(this));
this.client.socket.on("error", this.handleQueueError.bind(this)); this.client.socket.on("error", this.handleQueueError.bind(this));
} }
@ -415,6 +417,12 @@ class queuePanel extends panelObj{
} }
clearQueue(){ clearQueue(){
//If we have no body
if(this.ownerDoc.body == null){
//We have bigger issues
return;
}
//Clear out queue container //Clear out queue container
this.queueContainer.innerHTML = '';; this.queueContainer.innerHTML = '';;
//Clear out queue marker container //Clear out queue marker container

View file

@ -227,7 +227,7 @@ class accountSettingsButton{
class changeEmailPopup{ class changeEmailPopup{
constructor(){ constructor(){
this.popup = new canopyUXUtils.popup("changeEmail", true, this.asyncConstructor.bind(this), this.asyncConstructor); this.popup = new canopyUXUtils.popup("changeEmail", true, this.asyncConstructor.bind(this));
} }
asyncConstructor(){ asyncConstructor(){
@ -251,7 +251,7 @@ class changeEmailPopup{
class changePasswordPopup{ class changePasswordPopup{
constructor(){ constructor(){
this.popup = new canopyUXUtils.popup("changePassword", true, this.asyncConstructor.bind(this), this.asyncConstructor); this.popup = new canopyUXUtils.popup("changePassword", true, this.asyncConstructor.bind(this));
} }
asyncConstructor(){ asyncConstructor(){