From 0b68db126563597d0623569f56bfff16d5a4f033 Mon Sep 17 00:00:00 2001 From: rainbow napkin Date: Sun, 12 Jan 2025 17:50:54 -0500 Subject: [PATCH] Started work on back-end for synced video playback. --- src/app/channel/activeChannel.js | 3 ++ src/app/channel/channelManager.js | 4 +- src/app/channel/media/media.js | 8 ---- src/app/channel/media/queue.js | 69 ++++++++++++++++++++++++++++ src/app/channel/media/queuedMedia.js | 43 +++++++++++++++++ src/app/channel/media/yanker.js | 14 ++++++ 6 files changed, 131 insertions(+), 10 deletions(-) create mode 100644 src/app/channel/media/queue.js create mode 100644 src/app/channel/media/queuedMedia.js diff --git a/src/app/channel/activeChannel.js b/src/app/channel/activeChannel.js index 68e9b99..336a52c 100644 --- a/src/app/channel/activeChannel.js +++ b/src/app/channel/activeChannel.js @@ -16,6 +16,7 @@ along with this program. If not, see .*/ //local imports const connectedUser = require('./connectedUser'); +const queue = require('./media/queue'); const flairModel = require('../../schemas/flairSchema'); const permissionModel = require('../../schemas/permissionSchema'); @@ -26,6 +27,7 @@ module.exports = class{ 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 this.userList = new Map(); + this.queue = new queue(server, this); } async handleConnection(userDB, chanDB, socket){ @@ -52,6 +54,7 @@ module.exports = class{ //if everything looks good, admit the connection to the channel socket.join(socket.chan); + //Hand off the connection initiation to it's user object await userObj.handleConnection(userDB, chanDB, socket) //Send out the userlist diff --git a/src/app/channel/channelManager.js b/src/app/channel/channelManager.js index 6113873..799df1a 100644 --- a/src/app/channel/channelManager.js +++ b/src/app/channel/channelManager.js @@ -70,12 +70,12 @@ module.exports = class{ return; } - //Define listeners + //Define listeners for inter-channel classes this.defineListeners(socket); this.chatHandler.defineListeners(socket); this.mediaYanker.defineListeners(socket); - //Connect the socket to it's given channel + //Hand off the connection to it's given active channel object //Lil' hacky to pass chanDB like that, but why double up on DB calls? activeChan.handleConnection(userDB, chanDB, socket); }else{ diff --git a/src/app/channel/media/media.js b/src/app/channel/media/media.js index 30e21e5..ddf1f3d 100644 --- a/src/app/channel/media/media.js +++ b/src/app/channel/media/media.js @@ -24,13 +24,5 @@ module.exports = class{ this.id = id; this.type = type; this.duration = duration; - - //Generate initial UUID, this will most likely be replaced when media object is added to a playlist - //If this ends up being entirely redudnant in the future we'll remove it for performance - this.genUUID(); - } - - genUUID(){ - this.uuid = crypto.randomUUID(); } } \ No newline at end of file diff --git a/src/app/channel/media/queue.js b/src/app/channel/media/queue.js new file mode 100644 index 0000000..6e2ece0 --- /dev/null +++ b/src/app/channel/media/queue.js @@ -0,0 +1,69 @@ +/*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 .*/ + +//Local imports +const queuedMedia = require('./queuedMedia'); + +module.exports = class{ + constructor(server, channel){ + //Set server + this.server = server + //Set channel + this.channel = channel; + //Create variable to hold sync delta + this.syncDelta = 1000; + //Create variable to hold sync timer + this.syncTimer = null; + //Create variable to hold currently playing media object + this.nowPlaying = null; + //Create variable to hold current timestamp within the video + this.timestamp = 0; + } + + queueMedia(inputMedia){ + //Create new media object, start it now + const mediaObj = queuedMedia.fromMedia(inputMedia, new Date().getTime()); + } + + play(mediaObj){ + //reset current timestamp + this.timestamp = 0; + + //Set current playing media + this.nowPlaying = mediaObj; + + //Send play signal out to the channel + this.server.io.in(this.channel.name).emit("play", {media: this.nowPlaying}); + + //Kick off the sync timer + this.syncTimer = setTimeout(this.sync.bind(this), this.syncDelta); + } + + sync(){ + //Send sync signal out to the channel + this.server.io.in(this.channel.name).emit("sync", {timestamp: this.timestamp}); + + //If the media hasn't finished playing + if(this.timeStamp < this.nowPlaying.duration){ + + //Increment the time stamp + this.timestamp++; + + //Call the sync function in another second + this.syncTimer = setTimeout(this.sync.bind(this), this.syncDelta); + } + } +} \ No newline at end of file diff --git a/src/app/channel/media/queuedMedia.js b/src/app/channel/media/queuedMedia.js new file mode 100644 index 0000000..1c562dd --- /dev/null +++ b/src/app/channel/media/queuedMedia.js @@ -0,0 +1,43 @@ +/*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 .*/ + +//Local Imports +const media = require('./media'); + +module.exports = class extends media{ + constructor(title, fileName, id, type, duration, startTime){ + //Call derived constructor + super(title, fileName, id, type, duration); + //Set media start time + this.startTime = startTime; + + //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(); + } + + //statics + static fromMedia(media, startTime){ + //Create and return queuedMedia object from given media object and arguments + return new this(media.title, media.fileName, media.id, media.type, media.duration, startTime); + } + + //methods + genUUID(){ + this.uuid = crypto.randomUUID(); + } + +} \ No newline at end of file diff --git a/src/app/channel/media/yanker.js b/src/app/channel/media/yanker.js index c6ad7be..367f259 100644 --- a/src/app/channel/media/yanker.js +++ b/src/app/channel/media/yanker.js @@ -29,6 +29,7 @@ module.exports = class{ defineListeners(socket){ socket.on("yank", (data) => {this.testYank(socket, data)}); + socket.on("play", (data) => {this.testPlay(socket, data)}); } async testYank(socket, data){ @@ -39,6 +40,19 @@ module.exports = class{ } } + async testPlay(socket, data){ + try{ + //Pull media list + const mediaList = await this.yankMedia(data.url); + //Get active channel from server/socket + const chan = this.server.activeChannels.get(socket.chan) + //Queue the first media object given + chan.queue.queueMedia(mediaList[0]); + }catch(err){ + return loggerUtils.socketExceptionHandler(socket, err); + } + } + async yankMedia(url){ const pullType = await this.getMediaType(url)