diff --git a/fgfm.TODO b/fgfm.TODO index 14473bf..e1ea65c 100755 --- a/fgfm.TODO +++ b/fgfm.TODO @@ -4,6 +4,22 @@ TODO: ✔ Add cooldowns @done (18-10-02 10:16) + ☐ Silence detection + - If stream has been silent for more than X minutes, try skipping song + ☐ Start/stop stream automation + ☐ Set up the queue upon init so it can be managed during startup + ☐ Start + ✔ Start stream @done (18-10-26 09:44) + ✔ Starting Soon is shown until countdown is triggered @done (18-10-26 09:44) + - Add parameter for countdown + - Countdown for X minutes is triggered and shown + - Once countdown finishes, switch to intro scene and play + - Switch to fgfm once intro finishes + ☐ Stop + - Parameter for how long until the stream should end + - Switch to credits with 1 minute remaining + - Fade out audio sources with 5 seconds left + - Stop Stream ☐ Fix commercial playing issue (switches back to scene early) ☐ Change $auw and $meme to queue up the videos just like the others (don't switch scenes) ☐ Don't auto-init GHOBS or FGFM, make them on-demand @@ -11,7 +27,6 @@ TODO: ☐ Move anything that calls director.state from app into fgfm lib ☐ Restrict # of requests a user can have in the queue at once ☐ Move vrmode timer to this bot, delete from SLCB - ☐ Support a CLI flag to delay showing queue until command is issued ☐ Room vid requests / import ☐ Improved interface for viewer requests ☐ Web interface? Twitch extension? @@ -21,23 +36,15 @@ TODO: ☐ Remove currently playing video from vote choices ☐ Command to add sets of videos to the queue at once (like the entire ttas or all gold segments) ☐ Command to stop video rotation / timers (shutdown) - ☐ Start/stop stream automation - ☐ Start - - Start stream - - Starting Soon is shown until countdown is triggered - - Countdown for X minutes is triggered and shown - - Once countdown finishes, switch to intro scene and play - - Switch to fgfm once intro finishes - ☐ Stop - - Parameter for how long until the stream should end - - Switch to credits with 1 minute remaining - - Fade out audio sources with 5 seconds left - - Stop Stream - ☐ Support for $pause -- not sure how to pull this off + ☐ Support for $pause (pauses queue after current video finishes) ☐ Tool to output list of video ID's / descriptions ☐ Stream alerts for chat ☐ Rotating background images (leftside) ☐ Support gif's via command through gifph + - !slowdance + - !bender + - !carlton + - !weeb Ideas: ☐ Web interface for viewers to issue commands -- twitch extension?! diff --git a/fgfm.js b/fgfm.js index 0151f3b..3925450 100755 --- a/fgfm.js +++ b/fgfm.js @@ -73,12 +73,29 @@ const streamInit = (config, twitch) => { // All your comfy are belong to us const director = new FGFM({config: config, obs: obs}); - director.init(); // Chat commands const commands = { admin: { + init: (cmd) => { + let delaySeconds = cmd.args[1] || 300; + director.startingSoon(delaySeconds); + }, + + + start: (cmd) => { + director.startTheShow(); + }, + + + end: (cmd) => { + let creditsDelay = cmd.args[1] || 0; + let endDelay = cmd.args[2] || 60; + director.endTheShow(creditsDelay, endDelay); + }, + + changevis: (cmd, newVisibility) => { let sceneItem = command.args[1] || false; if (!sceneItem) { diff --git a/lib/fgfm.js b/lib/fgfm.js index 1bc526c..f48c325 100755 --- a/lib/fgfm.js +++ b/lib/fgfm.js @@ -5,6 +5,7 @@ function FGFM(config) { this.config = config.config; this.obs = config.obs; this.state = { + showStatus: 'IDLE', videoQueue: [], recentlyPlayed: [], currentVideo: null, @@ -13,14 +14,96 @@ function FGFM(config) { commercialPlaying: false }; + this.startingSoon = (autostartDelaySeconds) => { + this.state.showStatus = 'STARTING_SOON'; + + // Set up the initial queue by randomly choosing the configured amount of vods included in shuffling + this.state.videoQueue = this.config.vods.alttp.filter(e => e.includeInShuffle === true).sort(util.randSort).slice(0, this.config.initialQueueSize); + + // show the starting-soon scene + this.obs.switchToScene('starting-soon'); + + // restore volume + this.obs.setVolume('headphones', 1.0); + + // start the stream + this.obs.startStream(); + + if (typeof autostartDelaySeconds === 'undefined') { + autostartDelaySeconds = 300; + } + + if (autostartDelaySeconds !== false) { + // @TODO: Actually show the countdown in the scene + console.log(`The show will start in ${autostartDelaySeconds} seconds!`); + setTimeout(this.startTheShow, autostartDelaySeconds*1000); + } + }; + // Set up initial queue + start playback - this.init = () => { + this.startTheShow = () => { // Set up the initial queue by randomly choosing the configured amount of vods included in shuffling this.state.videoQueue = this.config.vods.alttp.filter(e => e.includeInShuffle === true).sort(util.randSort).slice(0, this.config.initialQueueSize); // Start queue playback this.state.currentVideo = this.state.videoQueue.shift(); this.showVideo(this.state.currentVideo); + + // restore volume + this.obs.setVolume('headphones', 1.0); + + this.state.showStatus = 'RUNNING'; + }; + + this.endTheShow = (creditsDelaySeconds, endDelaySeconds) => { + if (typeof creditsDelaySeconds === 'undefined' || creditsDelaySeconds === false) { + creditsDelaySeconds = 0; + } + + if (typeof endDelaySeconds === 'undefined' || endDelaySeconds === false) { + endDelaySeconds = 60; + } + + console.log(`Credits will be shown in ${creditsDelaySeconds} seconds!`); + + let end = () => { + this.state.showStatus = 'ENDING'; + + this.obs.switchToScene('credits') + .then(() => { + if (endDelaySeconds < 5) endDelaySeconds = 5; + console.log(`Stream will stop in ${endDelaySeconds} seconds`); + let fadeOutDelay = endDelaySeconds - 5; + + // Fade out volume with 5 seconds left + setTimeout(() => { + this.obs.getVolume('headphones') + .then(currentVolume => { + console.log(`current volume of headphones: ${currentVolume}`); + let step = 0.1; + while (currentVolume > 0.1) { + currentVolume -= step; + console.log(`setting volume to: ${currentVolume}`); + this.obs.setVolume('headphones', currentVolume); + util.sleep(250); + } + }) + .catch(console.error); + }, fadeOutDelay*1000); + + setTimeout(() => { + this.obs.stopStream(); + this.state.showStatus = 'ENDED'; + }, endDelaySeconds*1000); + }) + .catch(console.error); + }; + + if (creditsDelaySeconds > 0) { + setTimeout(end, creditsDelaySeconds*1000); + } else { + end(); + } }; // Shows.. a... video @@ -71,6 +154,11 @@ function FGFM(config) { // Picks the next video in the queue (shuffles if empty) // Also handles "commercial breaks" if enabled this.nextVideo = () => { + // @TODO: Validate this.state.showStatus -- make sure the "show" hasn't been paused or stopped + if (this.state.showStatus === 'ENDING' || this.state.showStatus === 'ENDED') { + return; + } + // Show a "commercial break" if it's been long enough since the last one let secondsSinceLastCommercial = (Date.now() - this.state.lastCommercialShownAt) / 1000; if (this.config.commercialsEnabled === true && secondsSinceLastCommercial >= this.config.commercialInterval) { diff --git a/lib/ghobs.js b/lib/ghobs.js index 29b0ab8..9ca16c7 100755 --- a/lib/ghobs.js +++ b/lib/ghobs.js @@ -28,6 +28,28 @@ function GHOBS(config) { }); }; + this.startStream = () => { + return this.websocket.startStreaming(); + }; + + this.stopStream = () => { + return this.websocket.stopStreaming(); + }; + + this.setVolume = (source, volume) => { + return this.websocket.setVolume({source: source, volume: volume}); + } + + this.getVolume = (source) => { + return new Promise((resolve, reject) => { + this.websocket.getVolume({source: source}) + .then(res => { + resolve(res.volume); + }) + .catch(reject); + }); + } + // Plays a video in the current scene and hides when finished this.playVideo = (video, callback) => { return new Promise((resolve, reject) => { diff --git a/lib/util.js b/lib/util.js index 941853d..52d3526 100755 --- a/lib/util.js +++ b/lib/util.js @@ -48,4 +48,13 @@ exports.average = function(e) { return avg; }; -exports.randSort = () => { return 0.5 - Math.random() }; \ No newline at end of file +exports.randSort = () => { return 0.5 - Math.random() }; + +exports.sleep = (milliseconds) => { + var start = new Date().getTime(); + for (var i = 0; i < 1e7; i++) { + if ((new Date().getTime() - start) > milliseconds){ + break; + } + } +} \ No newline at end of file