diff --git a/fgfm.TODO b/fgfm.TODO index 492d488..de95f2d 100755 --- a/fgfm.TODO +++ b/fgfm.TODO @@ -1,15 +1,10 @@ TODO: - ☐ Have director emit events for bots to listen to - - show status changing - - video starting/ending/skipped - ☐ Auto-enable vrmode timer when show starts (listen for event) + ☐ Handle socket disconnect ☐ Start/stop stream automation ☐ Support scheduled start/stop - instead of countdown for X seconds, use datetime argument ☐ Start - Countdown for X minutes is triggered and shown - - Once countdown finishes, switch to intro scene and play - - Switch to fgfm once intro finishes ☐ Decouple twitch chat from GHOBS ☐ Move anything that calls director.state from app into fgfm lib ☐ Restrict # of requests a user can have in the queue at once @@ -46,6 +41,14 @@ StreamWebRemote: ___________________ Archive: + ✔ Stop @done (18-11-06 21:54) @project(TODO) + ✔ video starting/ending/skipped @done (18-11-06 21:53) @project(TODO) + ✔ show status changing @done (18-11-06 21:53) @project(TODO) + ✔ Have director emit events for bots to listen to @done (18-11-06 21:53) @project(TODO) + ✔ Auto-enable vrmode timer when show starts (listen for event) @done (18-11-06 21:53) @project(TODO) + ✔ hide sources before switching to credits @done (18-11-06 21:41) @project(TODO) + ✔ Support a delay for Start Streaming @done (18-11-06 21:41) @project(TODO) + ✔ Add a $rooms alias for $room @done (18-11-06 15:05) @project(TODO) ✔ Remove currently playing video from vote choices @done (18-10-30 11:51) @project(TODO) ✔ track votes for the current playing video @done (18-10-30 11:48) @project(TODO) ✔ if threshold is met, skip @done (18-10-30 11:48) @project(TODO) diff --git a/fgfm.js b/fgfm.js index 658115d..a0264dc 100755 --- a/fgfm.js +++ b/fgfm.js @@ -22,12 +22,7 @@ let snesGames = require('./conf/snesgames.json'); let timersList = require('./conf/timers.json'); let activeTimers = []; -let skipVote = { - target: null, - count: 0 -}; -// @TODO: Move to config -config.skipVoteThreshold = 2; +let skipVote = {target: null, count: 0}; // Main screen turn on const obs = new GHOBS(config); @@ -83,15 +78,34 @@ const streamInit = (config, twitch) => { // All your comfy are belong to us const director = new FGFM({config: config, obs: obs}); + + // Handle show events from the director + director.on('SHOW_STARTED', () => { + // Enable vrmode timer + manageTimer('vr', 'on'); + }); + + director.on('SHOW_ENDING', (secondsUntilCredits) => { + // Disable vrmode timer + manageTimer('vr', 'off'); + + // Let the chat know the stream is ending soon + twitch.botChat.say(config.twitch.channel, `The stream will be ending in ${parseFloat(secondsUntilCredits/60).toFixed(0)} minutes!`); + }); + + director.on('CREDITS_SHOWN', (secondsUntilEnd) => { + twitch.editorChat.say(config.twitch.channel, `Thanks to everyone for watching and lurking! Have a wonderful night and stay comfy. greenhComfy`); + }); // Chat commands const commands = { admin: { init: (cmd) => { - let delaySeconds = cmd.args[1] || 300; + let streamStartDelaySeconds = cmd.args[1] || 1; + let showStartDelaySeconds = cmd.args[2] || 300; - director.startingSoon(delaySeconds); + director.startingSoon(streamStartDelaySeconds, showStartDelaySeconds); }, @@ -101,7 +115,7 @@ const streamInit = (config, twitch) => { end: (cmd) => { - let creditsDelay = cmd.args[1] || 0; + let creditsDelay = cmd.args[1] || 1; let endDelay = cmd.args[2] || 60; director.endTheShow(creditsDelay, endDelay); }, @@ -147,38 +161,12 @@ const streamInit = (config, twitch) => { return; } - // search timers for matching name - let theTimerIndex = timersList.findIndex(e => e.name === timerName); - if (theTimerIndex === -1) { - twitch.botChat.say(cmd.to, `Invalid timer name!`); - return; - } - - let theTimer = timersList[theTimerIndex]; - - // look in activeTimers for current status - let currentTimerIndex = activeTimers.findIndex(e => e.name === timerName); - let timerStatus = cmd.args[2] || false; - if (!timerStatus || timerStatus !== 'on' || timerStatus !== 'off') { - // toggle by default - if (currentTimerIndex === -1) { - timerStatus = 'on'; - } else { - timerStatus = 'off'; - } - } - - if (currentTimerIndex === -1 && timerStatus === 'on') { - let timerFunc = () => { - twitch.botChat.say(config.twitch.channel, theTimer.value); - }; - let timerInterval = setInterval(timerFunc, theTimer.interval*1000); - activeTimers.push({name: theTimer.name, timer: timerInterval}); - timerFunc(); - } else if (timerStatus === 'off') { - clearInterval(activeTimers[currentTimerIndex].timer); - activeTimers.splice(currentTimerIndex, 1); + + try { + manageTimer(timerName, timerStatus); + } catch (e) { + twitch.botChat.say(cmd.to, e); } }, @@ -460,6 +448,11 @@ const streamInit = (config, twitch) => { } }; + // Aliases for chat commands + const aliases = { + "rooms": "room" + }; + // Listen for the above commands twitch.botChat.addListener('message', (from, to, message) => { // Ignore everything from blacklisted users @@ -481,6 +474,9 @@ const streamInit = (config, twitch) => { // Ignore messages without a command if (!key || key.length === 0) return; + // Check for aliased commands + if (aliases.hasOwnProperty(key)) key = aliases[key]; + // Ignore unrecognized commands if (!commands.admin.hasOwnProperty(key) && !commands.user.hasOwnProperty(key)) return; @@ -507,7 +503,41 @@ const streamInit = (config, twitch) => { .catch(console.error); }); + const manageTimer = (timerName, timerStatus) => { + // search timers for matching name + let theTimerIndex = timersList.findIndex(e => e.name === timerName); + if (theTimerIndex === -1) { + throw("Invalid timer name!"); + } + let theTimer = timersList[theTimerIndex]; + + // look in activeTimers for current status + let currentTimerIndex = activeTimers.findIndex(e => e.name === timerName); + + if (!timerStatus || timerStatus !== 'on' || timerStatus !== 'off') { + // toggle by default + if (currentTimerIndex === -1) { + timerStatus = 'on'; + } else { + timerStatus = 'off'; + } + } + + if (currentTimerIndex === -1 && timerStatus === 'on') { + let timerFunc = () => { + twitch.botChat.say(config.twitch.channel, theTimer.value); + }; + let timerInterval = setInterval(timerFunc, theTimer.interval*1000); + activeTimers.push({name: theTimer.name, timer: timerInterval}); + timerFunc(); + } else if (timerStatus === 'off') { + clearInterval(activeTimers[currentTimerIndex].timer); + activeTimers.splice(currentTimerIndex, 1); + } + + return; + } // @TODO: Modularize timed events //console.log(`Initializing stream timers...`); @@ -580,11 +610,5 @@ const streamInit = (config, twitch) => { }); }; -const startTimer = (timer) => { - setInterval(() => { - - }, timer.interval*1000); -}; - // catches Promise errors process.on('unhandledRejection', console.error); diff --git a/lib/fgfm.js b/lib/fgfm.js index d7883e4..eb58f3c 100755 --- a/lib/fgfm.js +++ b/lib/fgfm.js @@ -1,4 +1,6 @@ -const util = require('./util'); +const util = require('./util'), + emitter = require('events').EventEmitter, + sysutil = require('util'); function FGFM(config) { // Set up initial state @@ -14,30 +16,36 @@ function FGFM(config) { commercialPlaying: false }; - this.startingSoon = (autostartDelaySeconds) => { + emitter.call(this); + + this.startingSoon = (streamStartDelaySeconds, showStartDelaySeconds) => { + // @TODO: Move these defaults to config + if (typeof streamStartDelaySeconds === 'undefined') { + streamStartDelaySeconds = 1; + } + + if (typeof showStartDelaySeconds === 'undefined') { + showStartDelaySeconds = 300; + } + 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 + // Show the starting-soon scene this.obs.switchToScene('starting-soon'); - // restore volume + // Restore volume this.obs.setVolume('headphones', 1.0); - // start the stream - this.obs.startStream(); + // Start the stream + console.log(`The stream will start in ${streamStartDelaySeconds} seconds!`); + setTimeout(() => {this.obs.startStream().then(() => {this.emit('STREAM_STARTED')})}, streamStartDelaySeconds*1000); - 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); - } + // @TODO: Actually show the countdown in the scene + console.log(`The show will start in ${showStartDelaySeconds} seconds!`); + setTimeout(this.startTheShow, showStartDelaySeconds*1000); }; // Set up initial queue + start playback @@ -53,6 +61,8 @@ function FGFM(config) { this.obs.setVolume('headphones', 1.0); this.state.showStatus = 'RUNNING'; + + this.emit('SHOW_STARTED'); }; this.endTheShow = (creditsDelaySeconds, endDelaySeconds) => { @@ -65,14 +75,19 @@ function FGFM(config) { } console.log(`Credits will be shown in ${creditsDelaySeconds} seconds!`); + this.emit('SHOW_ENDING', creditsDelaySeconds); let end = () => { - clearTimeout(this.state.videoTimer); - this.state.showStatus = 'ENDING'; + // Hide current video, don't play next video + this.obs.hide(this.state.currentVideo.sceneItem, this.config.defaultSceneName) + clearTimeout(this.state.videoTimer); + this.obs.switchToScene('credits') .then(() => { + this.emit('CREDITS_SHOWN', endDelaySeconds); + if (endDelaySeconds < 5) endDelaySeconds = 5; console.log(`Stream will stop in ${endDelaySeconds} seconds`); let fadeOutDelay = endDelaySeconds - 5; @@ -96,6 +111,7 @@ function FGFM(config) { setTimeout(() => { this.obs.stopStream(); this.state.showStatus = 'ENDED'; + this.emit('SHOW_ENDED'); }, endDelaySeconds*1000); }) .catch(console.error); @@ -287,12 +303,16 @@ function FGFM(config) { this.pause = () => { this.state.showStatus = 'PAUSED'; + this.emit('SHOW_PAUSED'); }; this.resume = () => { this.state.showStatus = 'RUNNING'; this.nextVideo(); + this.emit('SHOW_RESUMED'); }; } +sysutil.inherits(FGFM, emitter); + module.exports = FGFM; diff --git a/sfx/wobbuffet.mp3 b/sfx/wobbuffet.mp3 new file mode 100755 index 0000000..81b8e60 Binary files /dev/null and b/sfx/wobbuffet.mp3 differ