Started work on JSDoc. src/app started, src/app/channel/media complete.
This commit is contained in:
parent
c64b315fdf
commit
fd760e4dab
|
|
@ -14,7 +14,19 @@ 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/>.*/
|
||||
|
||||
/**
|
||||
* Class representing a single chat message
|
||||
*/
|
||||
class chat{
|
||||
/**
|
||||
*
|
||||
* @param {connectedUser} user - User who sent the message
|
||||
* @param {string} flair - Flair ID String for the flair used to send the message
|
||||
* @param {Number} highLevel - Number representing current high level
|
||||
* @param {String} msg - Contents of the message, with links replaced with numbered file-seperator markers
|
||||
* @param {String} type - Message Type Identifier, used for client-side processing.
|
||||
* @param {Array} links - Array of URLs/Links included in the message.
|
||||
*/
|
||||
constructor(user, flair, highLevel, msg, type, links){
|
||||
this.user = user;
|
||||
this.flair = flair;
|
||||
|
|
|
|||
|
|
@ -16,7 +16,16 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
|||
const config = require('../../../config.json');
|
||||
const channelModel = require('../../schemas/channel/channelSchema');
|
||||
|
||||
/**
|
||||
* Class representing a stored chat buffer
|
||||
*/
|
||||
class chatBuffer{
|
||||
/**
|
||||
* Instantiates a new chat buffer for a given channel
|
||||
* @param {channelManager} server - Parent Server Object
|
||||
* @param {Document} chanDB - chanDB to rehydrate buffer from
|
||||
* @param {activeChannel} channel - Parent Channel Object
|
||||
*/
|
||||
constructor(server, chanDB, channel){
|
||||
//Grab parent server and chan objects
|
||||
this.server = server;
|
||||
|
|
@ -41,6 +50,10 @@ class chatBuffer{
|
|||
this.busyDelay = 5;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a given chat to the chat buffer in RAM and sets any appropriate timers for DB transactions
|
||||
* @param {chat} chat - Chat object to commit to buffer
|
||||
*/
|
||||
push(chat){
|
||||
//push chat into RAM buffer
|
||||
this.buffer.push(chat);
|
||||
|
|
@ -57,21 +70,36 @@ class chatBuffer{
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the oldest item from the chat buffer
|
||||
*
|
||||
* Was originally created in-case we needed to trigger timing functions
|
||||
*
|
||||
* Left here since it seems like good form anywho, since this would be a private, or at least protected member in another language
|
||||
*/
|
||||
shift(){
|
||||
//remove chat from RAM buffer, no need to handle DB timing here, since push should be called when this is called.
|
||||
this.buffer.shift();
|
||||
}
|
||||
|
||||
//Called after 10 seconds of chat room inactivity
|
||||
/**
|
||||
* Called after 10 seconds of chat room inactivity
|
||||
*/
|
||||
handleInactivity(){
|
||||
this.saveDB(`${this.inactivityDelay} seconds of inactivity.`);
|
||||
}
|
||||
|
||||
//Called after 5 minutes of solid activity
|
||||
/**
|
||||
* Called after 5 minutes of solid activity
|
||||
*/
|
||||
handleBusyRoom(){
|
||||
this.saveDB(`${this.busyDelay} minutes of activity.`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves RAM-Based buffer to Channel Document in DB
|
||||
* @param {String} reason - Reason for DB save, formatted as 'x minutes/seconds of in/activity', used for logging purposes
|
||||
* @param {Document} chanDB - Channel Doc to work with, can be left empty for method to auto-find through channel name.
|
||||
*/
|
||||
async saveDB(reason, chanDB){
|
||||
//clear existing timers
|
||||
clearTimeout(this.inactivityTimer);
|
||||
|
|
|
|||
|
|
@ -14,7 +14,20 @@ 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/>.*/
|
||||
|
||||
/**
|
||||
* Object representing a piece of media
|
||||
*/
|
||||
module.exports = class{
|
||||
/**
|
||||
* Creates a new media object from scraped information
|
||||
* @param {String} title - Chosen title of media
|
||||
* @param {String} fileName - Original filename/title of media provided by source
|
||||
* @param {String} url - Original URL to file
|
||||
* @param {String} id - Video ID from source (IE: youtube watch code/archive.org file path)
|
||||
* @param {String} type - Original video source
|
||||
* @param {Number} duration - Length of media in seconds
|
||||
* @param {String} rawLink - URL to raw file copy of media, not applicable to all sources
|
||||
*/
|
||||
constructor(title, fileName, url, id, type, duration, rawLink = url){
|
||||
this.title = title;
|
||||
this.fileName = fileName
|
||||
|
|
|
|||
|
|
@ -24,14 +24,26 @@ const yanker = require('../../../utils/media/yanker');
|
|||
const channelModel = require('../../../schemas/channel/channelSchema');
|
||||
const { userModel } = require('../../../schemas/user/userSchema');
|
||||
|
||||
/**
|
||||
* Class containing playlist management logic for a single channel
|
||||
*/
|
||||
module.exports = class{
|
||||
constructor(server, chanDB, channel){
|
||||
/**
|
||||
* Instantiates a new object to handle playlist management for a single channel
|
||||
* @param {channelManager} server - Parent server object
|
||||
* @param {activeChannel} channel - Parent Channel object for desired channel queue
|
||||
*/
|
||||
constructor(server, channel){
|
||||
//Set server
|
||||
this.server = server
|
||||
//Set channel
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines server-side socket.io listeners for newly connected sockets
|
||||
* @param {Socket} socket - Newly connected socket to define listeners against
|
||||
*/
|
||||
defineListeners(socket){
|
||||
//Channel Playlist Listeners
|
||||
socket.on("getChannelPlaylists", () => {this.getChannelPlaylists(socket)});
|
||||
|
|
@ -59,6 +71,12 @@ module.exports = class{
|
|||
}
|
||||
|
||||
//Validation/Sanatization functions
|
||||
/**
|
||||
* Validates client requests to create a playlist
|
||||
* @param {Socket} socket - Newly connected socket to define listeners against
|
||||
* @param {Object} data - Data handed over from the client
|
||||
* @returns {Object} returns validated titles
|
||||
*/
|
||||
createPlaylistValidator(socket, data){
|
||||
//Create empty array to hold titles
|
||||
const safeTitles = [];
|
||||
|
|
@ -89,6 +107,12 @@ module.exports = class{
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates client requests to add media to a playlist
|
||||
* @param {Socket} socket - Newly connected socket to define listeners against
|
||||
* @param {String} URL - URL String handed over from the client
|
||||
* @returns {Array} List of media objects which where added
|
||||
*/
|
||||
async addToPlaylistValidator(socket, url){
|
||||
//If we where given a bad URL
|
||||
if(typeof url != 'string' || !validator.isURL(url)){
|
||||
|
|
@ -118,6 +142,12 @@ module.exports = class{
|
|||
return mediaList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates client requests to queue media from a playlist
|
||||
* @param {Socket} socket - Newly connected socket to define listeners against
|
||||
* @param {Object} data - Data handed over from the client
|
||||
* @returns {Number} returns validated start time on success
|
||||
*/
|
||||
queueFromChannelPlaylistValidator(socket, data){
|
||||
//Validate UUID
|
||||
if(typeof data.uuid != 'string' || !validator.isUUID(data.uuid)){
|
||||
|
|
@ -131,6 +161,12 @@ module.exports = class{
|
|||
return this.channel.queue.getStart(data.start)
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates client requests to rename the playlist validator
|
||||
* @param {Socket} socket - Newly connected socket to define listeners against
|
||||
* @param {Object} data - Data handed over from the client
|
||||
* @returns {String} returns escaped/trimmed name upon success
|
||||
*/
|
||||
renameChannelPlaylistValidator(socket, data){
|
||||
//If the title is too long
|
||||
if(typeof data.name != 'string' || !validator.isLength(data.name, {min: 1, max:30})){
|
||||
|
|
@ -144,6 +180,11 @@ module.exports = class{
|
|||
return validator.escape(validator.trim(data.name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates client requests to change default titles for a given playlist
|
||||
* @param {Object} data - Data handed over from the client
|
||||
* @returns
|
||||
*/
|
||||
changeDefaultTitlesValidator(data){
|
||||
//Create empty array to hold titles
|
||||
const safeTitles = [];
|
||||
|
|
@ -161,6 +202,11 @@ module.exports = class{
|
|||
return safeTitles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates client requests to rename the playlist validator
|
||||
* @param {Socket} socket - Newly connected socket to define listeners against
|
||||
* @param {Object} data - Data handed over from the client
|
||||
*/
|
||||
deletePlaylistMediaValidator(socket, data){
|
||||
//If we don't have a valid UUID
|
||||
if(typeof data.uuid != 'string' || !validator.isUUID(data.uuid)){
|
||||
|
|
@ -174,6 +220,11 @@ module.exports = class{
|
|||
}
|
||||
|
||||
//Get playlist functions
|
||||
/**
|
||||
* Sends channel playlist data to a requesting socket
|
||||
* @param {Socket} socket - Newly connected socket to define listeners against
|
||||
* @param {Mongoose.Document} chanDB - Channnel Document Passthrough to save on DB Access
|
||||
*/
|
||||
async getChannelPlaylists(socket, chanDB){
|
||||
try{
|
||||
//if we wherent handed a channel document
|
||||
|
|
@ -189,6 +240,11 @@ module.exports = class{
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends user playlist data to a requesting socket
|
||||
* @param {Socket} socket - Newly connected socket to define listeners against
|
||||
* @param {Mongoose.Document} userDB - Channnel Document Passthrough to save on DB Access
|
||||
*/
|
||||
async getUserPlaylists(socket, userDB){
|
||||
try{
|
||||
//if we wherent handed a user document
|
||||
|
|
@ -205,6 +261,12 @@ module.exports = class{
|
|||
}
|
||||
|
||||
//Create playlist functions
|
||||
/**
|
||||
* Creates a new channel playlist
|
||||
* @param {Socket} socket - Requesting socket
|
||||
* @param {Object} data - Data handed over from the client
|
||||
* @param {Mongoose.Document} chanDB - Channnel Document Passthrough to save on DB Access
|
||||
*/
|
||||
async createChannelPlaylist(socket, data, chanDB){
|
||||
try{
|
||||
//Validate Data
|
||||
|
|
@ -248,6 +310,12 @@ module.exports = class{
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new user playlist
|
||||
* @param {Socket} socket - Requesting socket
|
||||
* @param {Object} data - Data handed over from the client
|
||||
* @param {Mongoose.Document} userDB - User Document Passthrough to save on DB Access
|
||||
*/
|
||||
async createUserPlaylist(socket, data, userDB){
|
||||
try{
|
||||
//Validate Data
|
||||
|
|
@ -290,6 +358,12 @@ module.exports = class{
|
|||
}
|
||||
|
||||
//Delete playlist functions
|
||||
/**
|
||||
* Deletes a user playlist
|
||||
* @param {Socket} socket - Requesting socket
|
||||
* @param {Object} data - Data handed over from the client
|
||||
* @param {Mongoose.Document} userDB - User Document Passthrough to save on DB Access
|
||||
*/
|
||||
async deleteChannelPlaylist(socket, data, chanDB){
|
||||
try{
|
||||
//if we wherent handed a channel document
|
||||
|
|
@ -318,6 +392,12 @@ module.exports = class{
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a Channel playlist
|
||||
* @param {Socket} socket - Requesting socket
|
||||
* @param {Object} data - Data handed over from the client
|
||||
* @param {Mongoose.Document} chanDB - Channnel Document Passthrough to save on DB Access
|
||||
*/
|
||||
async deleteUserPlaylist(socket, data, userDB){
|
||||
try{
|
||||
//if we wherent handed a user document
|
||||
|
|
@ -345,6 +425,12 @@ module.exports = class{
|
|||
}
|
||||
|
||||
//Add Media Functions
|
||||
/**
|
||||
* Adds media to channel playlist
|
||||
* @param {Socket} socket - Requesting socket
|
||||
* @param {Object} data - Data handed over from the client
|
||||
* @param {Mongoose.Document} chanDB - Channnel Document Passthrough to save on DB Access
|
||||
*/
|
||||
async addToChannelPlaylist(socket, data, chanDB){
|
||||
try{
|
||||
//if we wherent handed a channel document
|
||||
|
|
@ -390,6 +476,12 @@ module.exports = class{
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds media to user playlist
|
||||
* @param {Socket} socket - Requesting socket
|
||||
* @param {Object} data - Data handed over from the client
|
||||
* @param {Mongoose.Document} userDB - User Document Passthrough to save on DB Access
|
||||
*/
|
||||
async addToUserPlaylist(socket, data, userDB){
|
||||
try{
|
||||
//Validate URL and pull media
|
||||
|
|
@ -432,6 +524,12 @@ module.exports = class{
|
|||
}
|
||||
|
||||
//Queuing Functions
|
||||
/**
|
||||
* Queues an entire channel playlist
|
||||
* @param {Socket} socket - Requesting socket
|
||||
* @param {Object} data - Data handed over from the client
|
||||
* @param {Mongoose.Document} chanDB - Channel Document Passthrough to save on DB Access
|
||||
*/
|
||||
async queueChannelPlaylist(socket, data, chanDB){
|
||||
try{
|
||||
//if we wherent handed a channel document
|
||||
|
|
@ -479,6 +577,13 @@ module.exports = class{
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues an entire user playlist
|
||||
* @param {Socket} socket - Requesting socket
|
||||
* @param {Object} data - Data handed over from the client
|
||||
* @param {Mongoose.Document} userDB - User Document Passthrough to save on DB Access
|
||||
* @param {Mongoose.Document} chanDB - Channel Document Passthrough to save on DB Access
|
||||
*/
|
||||
async queueUserPlaylist(socket, data, userDB, chanDB){
|
||||
try{
|
||||
//if we wherent handed a user document
|
||||
|
|
@ -532,6 +637,12 @@ module.exports = class{
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues media from a given channel playlist
|
||||
* @param {Socket} socket - Requesting socket
|
||||
* @param {Object} data - Data handed over from the client
|
||||
* @param {Mongoose.Document} chanDB - Channel Document Passthrough to save on DB Access
|
||||
*/
|
||||
async queueFromChannelPlaylist(socket, data, chanDB){
|
||||
try{
|
||||
//Validate data
|
||||
|
|
@ -576,6 +687,13 @@ module.exports = class{
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues media from a given user playlist
|
||||
* @param {Socket} socket - Requesting socket
|
||||
* @param {Object} data - Data handed over from the client
|
||||
* @param {Mongoose.Document} userDB - User Document Passthrough to save on DB Access
|
||||
* @param {Mongoose.Document} chanDB - Channel Document Passthrough to save on DB Access
|
||||
*/
|
||||
async queueFromUserPlaylist(socket, data, userDB, chanDB){
|
||||
try{
|
||||
//Validate data
|
||||
|
|
@ -626,6 +744,12 @@ module.exports = class{
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues random media from a given channel playlist
|
||||
* @param {Socket} socket - Requesting socket
|
||||
* @param {Object} data - Data handed over from the client
|
||||
* @param {Mongoose.Document} chanDB - Channel Document Passthrough to save on DB Access
|
||||
*/
|
||||
async queueRandomFromChannelPlaylist(socket, data, chanDB){
|
||||
try{
|
||||
//if we wherent handed a channel document
|
||||
|
|
@ -667,6 +791,13 @@ module.exports = class{
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues random media from a given user playlist
|
||||
* @param {Socket} socket - Requesting socket
|
||||
* @param {Object} data - Data handed over from the client
|
||||
* @param {Mongoose.Document} userDB - User Document Passthrough to save on DB Access
|
||||
* @param {Mongoose.Document} chanDB - Channel Document Passthrough to save on DB Access
|
||||
*/
|
||||
async queueRandomFromUserPlaylist(socket, data, userDB, chanDB){
|
||||
try{
|
||||
//if we wherent handed a channel document
|
||||
|
|
@ -715,6 +846,12 @@ module.exports = class{
|
|||
}
|
||||
|
||||
//Rename playlist functions
|
||||
/**
|
||||
* Renames a channel playlist
|
||||
* @param {Socket} socket - Requesting socket
|
||||
* @param {Object} data - Data handed over from the client
|
||||
* @param {Mongoose.Document} chanDB - Channel Document Passthrough to save on DB Access
|
||||
*/
|
||||
async renameChannelPlaylist(socket, data, chanDB){
|
||||
try{
|
||||
//Validate and Sanatize name
|
||||
|
|
@ -766,6 +903,12 @@ module.exports = class{
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renames a user playlist
|
||||
* @param {Socket} socket - Requesting socket
|
||||
* @param {Object} data - Data handed over from the client
|
||||
* @param {Mongoose.Document} userDB - User Document Passthrough to save on DB Access
|
||||
*/
|
||||
async renameUserPlaylist(socket, data, userDB){
|
||||
try{
|
||||
//Validate and Sanatize name
|
||||
|
|
@ -816,6 +959,12 @@ module.exports = class{
|
|||
}
|
||||
|
||||
//Change default title list functions
|
||||
/**
|
||||
* Changes default titles for a given channel playlist
|
||||
* @param {Socket} socket - Requesting socket
|
||||
* @param {Object} data - Data handed over from the client
|
||||
* @param {Mongoose.Document} chanDB - Channel Document Passthrough to save on DB Access
|
||||
*/
|
||||
async changeDefaultTitlesChannelPlaylist(socket, data, chanDB){
|
||||
try{
|
||||
//if we wherent handed a channel document
|
||||
|
|
@ -850,6 +999,12 @@ module.exports = class{
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes default titles for a given user playlist
|
||||
* @param {Socket} socket - Requesting socket
|
||||
* @param {Object} data - Data handed over from the client
|
||||
* @param {Mongoose.Document} userDB - User Document Passthrough to save on DB Access
|
||||
*/
|
||||
async changeDefaultTitlesUserPlaylist(socket, data, userDB){
|
||||
try{
|
||||
//if we wherent handed a user document
|
||||
|
|
@ -884,6 +1039,12 @@ module.exports = class{
|
|||
}
|
||||
|
||||
//Delete playlist media functions
|
||||
/**
|
||||
* Deletes media from a given channel playlist
|
||||
* @param {Socket} socket - Requesting socket
|
||||
* @param {Object} data - Data handed over from the client
|
||||
* @param {Mongoose.Document} chanDB - Channel Document Passthrough to save on DB Access
|
||||
*/
|
||||
async deleteChannelPlaylistMedia(socket, data, chanDB){
|
||||
try{
|
||||
//Validate UUID
|
||||
|
|
@ -927,6 +1088,12 @@ module.exports = class{
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes media from a given user playlist
|
||||
* @param {Socket} socket - Requesting socket
|
||||
* @param {Object} data - Data handed over from the client
|
||||
* @param {Mongoose.Document} userDB - User Document Passthrough to save on DB Access
|
||||
*/
|
||||
async deleteUserPlaylistMedia(socket, data, userDB){
|
||||
try{
|
||||
//Validate UUID
|
||||
|
|
|
|||
|
|
@ -23,7 +23,16 @@ const yanker = require('../../../utils/media/yanker');
|
|||
const loggerUtils = require('../../../utils/loggerUtils');
|
||||
const channelModel = require('../../../schemas/channel/channelSchema');
|
||||
|
||||
/**
|
||||
* Object represneting a single channel's media queue
|
||||
*/
|
||||
module.exports = class{
|
||||
/**
|
||||
* Instantiates a new media queue for a given channel
|
||||
* @param {channelManager} server - Parent server object
|
||||
* @param {Document} chanDB - Related Channel Document from DB
|
||||
* @param {activeChannel} channel - Parent Channel object for desired channel queue
|
||||
*/
|
||||
constructor(server, chanDB, channel){
|
||||
//Set server
|
||||
this.server = server
|
||||
|
|
@ -63,6 +72,10 @@ module.exports = class{
|
|||
this.rehydrateQueue(chanDB);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines server-side socket.io listeners for newly connected sockets
|
||||
* @param {Socket} socket - Newly connected socket to define listeners against
|
||||
*/
|
||||
defineListeners(socket){
|
||||
//Queueing Functions
|
||||
socket.on("queue", (data) => {this.queueURL(socket, data)});
|
||||
|
|
@ -75,6 +88,11 @@ module.exports = class{
|
|||
}
|
||||
|
||||
//--- USER FACING QUEUEING FUNCTIONS ---
|
||||
/**
|
||||
* Accepts new URL's to queue from the client
|
||||
* @param {Socket} socket - Socket we're receiving the URL from
|
||||
* @param {Object} data - Event payload
|
||||
*/
|
||||
async queueURL(socket, data){
|
||||
//Get the current channel from the database
|
||||
const chanDB = await channelModel.findOne({name: socket.chan});
|
||||
|
|
@ -134,6 +152,10 @@ module.exports = class{
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes requests to stop currently playing media from client
|
||||
* @param {Socket} socket - Socket we received the request from
|
||||
*/
|
||||
async stopMedia(socket){
|
||||
try{
|
||||
//Get the current channel from the database
|
||||
|
|
@ -148,6 +170,11 @@ module.exports = class{
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes client requests to delete queued media
|
||||
* @param {Socket} socket - Requesting socket
|
||||
* @param {Object} data - Event payload
|
||||
*/
|
||||
async deleteMedia(socket, data){
|
||||
//Get the current channel from the database
|
||||
const chanDB = await channelModel.findOne({name: socket.chan});
|
||||
|
|
@ -170,6 +197,11 @@ module.exports = class{
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes request to delete a range of media items from the queue
|
||||
* @param {Socket} socket - Requesting socket
|
||||
* @param {Object} data - Event payload
|
||||
*/
|
||||
async deleteRange(socket, data){
|
||||
//Get the current channel from the database
|
||||
const chanDB = await channelModel.findOne({name: socket.chan});
|
||||
|
|
@ -198,6 +230,11 @@ module.exports = class{
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes request to move queued media item
|
||||
* @param {Socket} socket - Requesting socket
|
||||
* @param {Object} data - Event payload
|
||||
*/
|
||||
async moveMedia(socket, data){
|
||||
//Get the current channel from the database
|
||||
const chanDB = await channelModel.findOne({name: socket.chan});
|
||||
|
|
@ -226,6 +263,10 @@ module.exports = class{
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle client request to (un)lock queue
|
||||
* @param {Socket} socket - Requesting socket
|
||||
*/
|
||||
async toggleLock(socket){
|
||||
//Get the current channel from the database
|
||||
const chanDB = await channelModel.findOne({name: socket.chan});
|
||||
|
|
@ -240,6 +281,11 @@ module.exports = class{
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle client request to start an HLS live stream
|
||||
* @param {Socket} socket - Requesting socket
|
||||
* @param {Object} data - Event payload
|
||||
*/
|
||||
async goLive(socket, data){
|
||||
try{
|
||||
let title = "Livestream";
|
||||
|
|
@ -330,6 +376,10 @@ module.exports = class{
|
|||
}
|
||||
|
||||
//--- INTERNAL USE ONLY QUEUEING FUNCTIONS ---
|
||||
/**
|
||||
* Clears and scheduling timers
|
||||
* @param {Boolean} noArchive - Disables Archiving
|
||||
*/
|
||||
async stopScheduleTimers(noArchive = true){
|
||||
//End any currently playing media media w/o archiving
|
||||
await this.stop();
|
||||
|
|
@ -349,6 +399,11 @@ module.exports = class{
|
|||
this.preSwitchTimer = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates start times, and replaces bad ones with 5ms in the future
|
||||
* @param {Number} start - Start time to validate by JS Epoch (millis)
|
||||
* @returns Start time as JS Epoch (millis)
|
||||
*/
|
||||
getStart(start){
|
||||
//Pull current time
|
||||
const now = new Date().getTime();
|
||||
|
|
@ -380,6 +435,10 @@ module.exports = class{
|
|||
return start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates next item to play, and sets timer to play it at it's scheduled start
|
||||
* @param {Boolean} volatile - Disables DB Transactions if true
|
||||
*/
|
||||
refreshNextTimer(volatile = false){
|
||||
//If we're streamlocked
|
||||
if(this.streamLock){
|
||||
|
|
@ -423,6 +482,13 @@ module.exports = class{
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes range of media items from the queue
|
||||
* @param {Number} start - Start date by JS Epoch (millis)
|
||||
* @param {Number} end - End date by JS Epoch (millis)
|
||||
* @param {Socket} socket - Requesting Socket
|
||||
* @param {Boolean} noUnfinished - Set to true to include items that may be currently playing
|
||||
*/
|
||||
async removeRange(start = new Date().getTime() - 60 * 1000, end = new Date().getTime(), socket, noUnfinished = false){
|
||||
//If we're streamlocked
|
||||
if(this.streamLock){
|
||||
|
|
@ -431,6 +497,7 @@ module.exports = class{
|
|||
//Yell at the user for being an asshole
|
||||
loggerUtils.socketErrorHandler(socket, "You cannot edit the schedule while livestreaming!", "queue");
|
||||
}
|
||||
|
||||
//Stop while we're ahead since the stream hasn't ended yet
|
||||
return;
|
||||
}
|
||||
|
|
@ -467,6 +534,13 @@ module.exports = class{
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reschedules a media item
|
||||
* @param {String} uuid - UUID of item to reschedule
|
||||
* @param {Number} start - New start time by JS Epoch (Millis)
|
||||
* @param {Socket} socket - Requesting Socket
|
||||
* @param {Mongoose.Document} chanDB - Channnel Document Passthrough to save on DB Access
|
||||
*/
|
||||
async rescheduleMedia(uuid, start = new Date().getTime(), socket, chanDB){
|
||||
//If we're streamlocked
|
||||
if(this.streamLock){
|
||||
|
|
@ -564,6 +638,14 @@ module.exports = class{
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a media item
|
||||
* @param {String} uuid - UUID of item to reschedule
|
||||
* @param {Socket} socket - Requesting Socket
|
||||
* @param {Mongoose.Document} chanDB - Channnel Document Passthrough to save on DB Access
|
||||
* @param {Boolean} noScheduling - Disables schedule timer refresh if true
|
||||
* @returns {Media} Deleted Media Item
|
||||
*/
|
||||
async removeMedia(uuid, socket, chanDB, noScheduling = false){
|
||||
//If we're streamlocked
|
||||
if(this.streamLock){
|
||||
|
|
@ -695,35 +777,46 @@ module.exports = class{
|
|||
return media;
|
||||
}
|
||||
|
||||
async scheduleMedia(media, socket, chanDB, force = false, volatile = false, startVolatile = false, saveLate = false, noSave = 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.
|
||||
Maps seem like a good choice, if it wheren't for the issue of keeping them ordered...
|
||||
|
||||
That's where this comes in. You see if we temporarily store it in a sparse array and convert into a map,
|
||||
we can quickly and easily create a properly sorted schedule map that, out side of adding items, behaves normally.
|
||||
|
||||
Also a note on preformance:
|
||||
While .forEach ONLY runs through populated items in sparse arrays, many JS implementations run through them in the background,
|
||||
simply skipping them before executing the provided function. Looping through object.keys(arr), however, avoids this entirely,
|
||||
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 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.
|
||||
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 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
|
||||
https://community.appsmith.com/content/blog/dark-side-foreach-why-you-should-think-twice-using-it
|
||||
/**
|
||||
* Schedules a Media Item
|
||||
*
|
||||
* 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.
|
||||
* Maps seem like a good choice, if it wheren't for the issue of keeping them ordered...
|
||||
*
|
||||
* That's where this comes in. You see if we temporarily store it in a sparse array and convert into a map,
|
||||
* we can quickly and easily create a properly sorted schedule map that, out side of adding items, behaves normally.
|
||||
*
|
||||
* Also a note on preformance:
|
||||
* While .forEach ONLY runs through populated items in sparse arrays, many JS implementations run through them in the background,
|
||||
* simply skipping them before executing the provided function. Looping through object.keys(arr), however, avoids this entirely,
|
||||
* 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 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.
|
||||
* 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 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
|
||||
* https://community.appsmith.com/content/blog/dark-side-foreach-why-you-should-think-twice-using-it
|
||||
*
|
||||
* @param {Media} - Media item to schedule
|
||||
* @param {Socket} socket - Requesting Socket
|
||||
* @param {Mongoose.Document} chanDB - Channnel Document Passthrough to save on DB Access
|
||||
* @param {Boolean} force - Ignore certain conditions that would prevent scehduling and play the bitch anyways, used for internal function calls
|
||||
* @param {Boolean} volatile - Prevent DB Writes, used for internal function calls
|
||||
* @param {Boolean} startVolatile - Runs refreshNextTimer calls without DB writes, used for internal function calls
|
||||
* @param {Boolean} saveLate - Saves items even if they're about to, or have already started. Used for internal function calls
|
||||
* @param {Boolean} noSave - Allows function to edit Channel Document, but not save. Used for internal function calls in which the channel document is passed through, but will be saved immediatly after the scheduleMedia() call.
|
||||
*/
|
||||
|
||||
async scheduleMedia(media, socket, chanDB, force = false, volatile = false, startVolatile = false, saveLate = false, noSave = false){
|
||||
//If we're streamlocked and this isn't being forced
|
||||
if(this.streamLock && !force){
|
||||
//If an originating socket was provided for this request
|
||||
|
|
@ -869,10 +962,19 @@ module.exports = class{
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called 10 seconds before media begins to play
|
||||
* @param {queuedMedia} mediaObj - Media object that's about to play
|
||||
*/
|
||||
async preSwitch(mediaObj){
|
||||
this.handleRawRefresh(mediaObj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes expired raw links before media plays
|
||||
* @param {queuedMedia} mediaObj - Media object that's about to play
|
||||
* @returns {queuedMedia} passes through Media object with updated link upon success
|
||||
*/
|
||||
async handleRawRefresh(mediaObj){
|
||||
//Check if media needs a new raw link and update if it does
|
||||
if(await yanker.refreshRawLink(mediaObj)){
|
||||
|
|
@ -887,6 +989,12 @@ module.exports = class{
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Kicks off a media item
|
||||
* @param {queuedMedia} mediaObj - Media object that's about to play
|
||||
* @param {Number} timestamp - Media start timestamp in seconds
|
||||
* @param {Boolean} volatile - Disables DB Transactions
|
||||
*/
|
||||
async start(mediaObj, timestamp = mediaObj.startTimeStamp, volatile = false){
|
||||
//If something is already playing
|
||||
if(this.nowPlaying != null){
|
||||
|
|
@ -951,6 +1059,10 @@ module.exports = class{
|
|||
return mediaObj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a syncronization ping out to client Sockets and increments the tracked timestamp by the Synchronization Delta
|
||||
* Called auto-magically by the Synchronization Timer
|
||||
*/
|
||||
sync(){
|
||||
//Send sync signal out to the channel
|
||||
this.server.io.in(this.channel.name).emit("sync", {timestamp: this.timestamp});
|
||||
|
|
@ -977,6 +1089,13 @@ module.exports = class{
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* End currently playing media
|
||||
* @param {Boolean} quiet - Enable to prevent ending the media client-side
|
||||
* @param {Boolean} noArchive - Enable to prevent ended media from being written to channel archive. Deletes media if Volatile is false
|
||||
* @param {Boolean} volatile - Enable to prevent DB Transactions
|
||||
* @param {Mongoose.Document} chanDB - Pass through Channel Document to save on DB Transactions
|
||||
*/
|
||||
async end(quiet = false, noArchive = false, volatile = false, chanDB){
|
||||
try{
|
||||
//If we're not playing anything
|
||||
|
|
@ -1056,6 +1175,11 @@ module.exports = class{
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends running Livestream
|
||||
* @param {queuedMedia} wasPlaying - Media object that was playing while we started the Livestream
|
||||
* @param {Mongoose.Document} chanDB - Pass through Channel Document to save on DB Transactions
|
||||
*/
|
||||
async endLivestream(wasPlaying, chanDB){
|
||||
try{
|
||||
//If we wheren't handed a channel
|
||||
|
|
@ -1107,6 +1231,11 @@ module.exports = class{
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrites livestream over scheduled media content after it has ended
|
||||
* @param {queuedMedia} wasPlaying - Media object that was playing while we started the Livestream
|
||||
* @param {Mongoose.Document} chanDB - Pass through Channel Document to save on DB Transactions
|
||||
*/
|
||||
async livestreamOverwriteSchedule(wasPlaying, chanDB){
|
||||
try{
|
||||
//Get current epoch
|
||||
|
|
@ -1191,6 +1320,11 @@ module.exports = class{
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes back any missed content scheduled during Livestream after Livestream has ended.
|
||||
* @param {queuedMedia} wasPlaying - Media object that was playing while we started the Livestream
|
||||
* @param {Mongoose.Document} chanDB - Pass through Channel Document to save on DB Transactions
|
||||
*/
|
||||
async livestreamPushbackSchedule(wasPlaying, chanDB){
|
||||
try{
|
||||
//Get current epoch
|
||||
|
|
@ -1272,6 +1406,11 @@ module.exports = class{
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops currently playing media item
|
||||
* @param {Socket} socket - Requesting Socket
|
||||
* @returns returns false if there is nothing to stop
|
||||
*/
|
||||
stop(socket){
|
||||
//If we're not currently playing anything
|
||||
if(this.nowPlaying == null){
|
||||
|
|
@ -1297,6 +1436,13 @@ module.exports = class{
|
|||
this.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns scheduled media between two given datetimes
|
||||
* @param {Number} start - Start date by JS Epoch (Millis)
|
||||
* @param {Number} end - End date by JS Epoch (Millis)
|
||||
* @param {Boolean} noUnfinished - Enable to include currently playing media
|
||||
* @returns {queuedMedia} Found Media Objects
|
||||
*/
|
||||
getItemsBetweenEpochs(start, end, noUnfinished = false){
|
||||
//Create an empty array to hold found items
|
||||
const foundItems = [];
|
||||
|
|
@ -1317,6 +1463,11 @@ module.exports = class{
|
|||
return foundItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a media item by epoch
|
||||
* @param {Number} epoch - Date to check by JS Epoch (Millis)
|
||||
* @returns {queuedMedia} found media item
|
||||
*/
|
||||
getItemAtEpoch(epoch = new Date().getTime()){
|
||||
//Loop through scheduled items
|
||||
for(let item of this.schedule){
|
||||
|
|
@ -1331,6 +1482,11 @@ module.exports = class{
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets last item from a given epoch
|
||||
* @param {Number} epoch - Date to check by JS Epoch (Millis), defaults to now
|
||||
* @returns Last played item
|
||||
*/
|
||||
getLastItem(epoch = new Date().getTime()){
|
||||
//Create variable to hold the last item
|
||||
let last;
|
||||
|
|
@ -1356,6 +1512,11 @@ module.exports = class{
|
|||
return last;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets next item from a given epoch
|
||||
* @param {Number} epoch - Date to check by JS Epoch (Millis), defaults to now
|
||||
* @returns {queuedMedia} Next item on the schedule
|
||||
*/
|
||||
getNextItem(epoch = new Date().getTime()){
|
||||
//Iterate through the schedule
|
||||
for(let item of this.schedule){
|
||||
|
|
@ -1366,6 +1527,11 @@ module.exports = class{
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Scheduled Item by UUID
|
||||
* @param {String} uuid - UUID of item to reschedule
|
||||
* @returns {queuedMedia} found item
|
||||
*/
|
||||
getItemByUUID(uuid){
|
||||
//Iterate through the schedule
|
||||
for(let item of this.schedule){
|
||||
|
|
@ -1377,6 +1543,10 @@ module.exports = class{
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send media update to a specific socket or broadcast it to the entire channel
|
||||
* @param {Socket} socket - Requesting Socket
|
||||
*/
|
||||
sendMedia(socket){
|
||||
//Create data object
|
||||
const data = {
|
||||
|
|
@ -1396,10 +1566,19 @@ module.exports = class{
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts channel queue
|
||||
* @param {Mongoose.Document} chanDB - Pass through Channel Document to save on DB Transactions
|
||||
*/
|
||||
async broadcastQueue(chanDB){
|
||||
this.server.io.in(this.channel.name).emit('queue',{queue: await this.prepQueue(chanDB)});
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares channel queue for network transmission
|
||||
* @param {Mongoose.Document} chanDB - Pass through Channel Document to save on DB Transactions
|
||||
* @returns de-hydrated scehdule information
|
||||
*/
|
||||
async prepQueue(chanDB){
|
||||
try{
|
||||
//If we didn't get handed a freebie
|
||||
|
|
@ -1454,6 +1633,10 @@ module.exports = class{
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rehydrates media schedule from DB
|
||||
* @param {Mongoose.Document} chanDB - Pass through Channel Document to save on DB Transactions
|
||||
*/
|
||||
async rehydrateQueue(chanDB){
|
||||
try{
|
||||
//If we didn't get handed a freebie
|
||||
|
|
|
|||
|
|
@ -17,7 +17,18 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
|||
//Local Imports
|
||||
const media = require('./media');
|
||||
|
||||
/**
|
||||
* Class extending media which represents a queued piece of media
|
||||
* @extends media
|
||||
*/
|
||||
module.exports = class extends media{
|
||||
/**
|
||||
* Creates a new queued media object
|
||||
* @param {Number} startTime - JS Epoch representing start time
|
||||
* @param {Number} startTimeStamp - Media start time stamp in seconds (relative to duration)
|
||||
* @param {Number} earlyEnd - Media end timestamp in seconds (relative to duration)
|
||||
* @param {String} uuid - Media object's unique identifier
|
||||
*/
|
||||
constructor(title, fileName, url, id, type, duration, rawLink, startTime, startTimeStamp = 0, earlyEnd, uuid){
|
||||
//Call derived constructor
|
||||
super(title, fileName, url, id, type, duration, rawLink);
|
||||
|
|
@ -41,6 +52,13 @@ module.exports = class extends media{
|
|||
}
|
||||
|
||||
//statics
|
||||
/**
|
||||
* Creates a queuedMedia object from a media object
|
||||
* @param {media} media - Media object to queue
|
||||
* @param {Number} startTime - Start time formatted as a JS Epoch
|
||||
* @param {Number} startTimeStamp - Start time stamp in seconds
|
||||
* @returns
|
||||
*/
|
||||
static fromMedia(media, startTime, startTimeStamp){
|
||||
//Create and return queuedMedia object from given media object and arguments
|
||||
return new this(
|
||||
|
|
@ -55,6 +73,12 @@ module.exports = class extends media{
|
|||
startTimeStamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts array of media objects into array of queuedMedia objects
|
||||
* @param {[media]} mediaList - Array of media objects to queue
|
||||
* @param {Number} start - Start time formatted as JS Epoch
|
||||
* @returns Array of converted queued media objects
|
||||
*/
|
||||
static fromMediaArray(mediaList, start){
|
||||
//Queued Media List
|
||||
const queuedMediaList = [];
|
||||
|
|
@ -73,10 +97,18 @@ module.exports = class extends media{
|
|||
}
|
||||
|
||||
//methods
|
||||
/**
|
||||
* Generates new unique identifier for queued media
|
||||
*/
|
||||
genUUID(){
|
||||
this.uuid = crypto.randomUUID();
|
||||
}
|
||||
|
||||
/**
|
||||
* return the end time of a given queuedMedia object
|
||||
* @param {boolean} fullTime - Overrides early ends
|
||||
* @returns end time of given queuedMedia object
|
||||
*/
|
||||
getEndTime(fullTime = false){
|
||||
//If we have an early ending
|
||||
if(this.earlyEnd == null || fullTime){
|
||||
|
|
|
|||
Loading…
Reference in a new issue