325 lines
11 KiB
JavaScript
Executable File
325 lines
11 KiB
JavaScript
Executable File
const util = require('./util'),
|
|
emitter = require('events').EventEmitter,
|
|
sysutil = require('util');
|
|
|
|
function FGFM(config) {
|
|
// Set up initial state
|
|
this.config = config.config;
|
|
this.obs = config.obs;
|
|
this.state = {
|
|
showStatus: 'IDLE',
|
|
videoQueue: [],
|
|
recentlyPlayed: [],
|
|
currentVideo: null,
|
|
videoTimer: null,
|
|
lastCommercialShownAt: Date.now(),
|
|
commercialPlaying: false
|
|
};
|
|
|
|
emitter.call(this);
|
|
|
|
this.startingSoon = (streamStartDelaySeconds, showStartDelaySeconds) => {
|
|
// @TODO: Move these defaults to config
|
|
if (typeof streamStartDelaySeconds === 'undefined') {
|
|
streamStartDelaySeconds = 1;
|
|
} else {
|
|
streamStartDelaySeconds = parseInt(streamStartDelaySeconds);
|
|
}
|
|
|
|
if (typeof showStartDelaySeconds === 'undefined') {
|
|
showStartDelaySeconds = 300;
|
|
} else {
|
|
showStartDelaySeconds = parseInt(showStartDelaySeconds);
|
|
showStartDelaySeconds += streamStartDelaySeconds;
|
|
}
|
|
|
|
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 after delay
|
|
console.log(`The stream will start in ${streamStartDelaySeconds} seconds!`);
|
|
setTimeout(() => {this.obs.startStream().then(() => {this.emit('STREAM_STARTED')})}, streamStartDelaySeconds*1000);
|
|
|
|
// Start the "show" after stream+show delay
|
|
// @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
|
|
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.emit('SHOW_STARTED');
|
|
};
|
|
|
|
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!`);
|
|
this.emit('SHOW_ENDING', creditsDelaySeconds);
|
|
|
|
let end = () => {
|
|
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;
|
|
|
|
// 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';
|
|
this.emit('SHOW_ENDED');
|
|
}, endDelaySeconds*1000);
|
|
})
|
|
.catch(console.error);
|
|
};
|
|
|
|
if (creditsDelaySeconds > 0) {
|
|
setTimeout(end, creditsDelaySeconds*1000);
|
|
} else {
|
|
end();
|
|
}
|
|
};
|
|
|
|
// Shows.. a... video
|
|
this.showVideo = video => {
|
|
console.log(`Showing video: ${video.chatName}`);
|
|
|
|
this.obs.playVideoInScene(video, this.config.defaultSceneName, this.nextVideo)
|
|
.then(timer => {
|
|
// track timer so we can cancel callback later on if necessary
|
|
this.state.videoTimer = timer;
|
|
|
|
// update activity label and show/hide appropriately
|
|
if (video.hasOwnProperty('label') && video.label !== false) {
|
|
this.obs.showActivity(video.label);
|
|
} else {
|
|
this.obs.hideActivity();
|
|
}
|
|
})
|
|
.catch(console.error);
|
|
};
|
|
|
|
// Adds a gameplay vod to the queue
|
|
this.addVideo = video => {
|
|
return this.state.videoQueue.push(video);
|
|
};
|
|
|
|
// Adds a room to the queue and handles looping setup
|
|
this.addRoomVideo = (room, loop) => {
|
|
let loops = 1;
|
|
if (typeof loop === 'undefined' || loop === true) {
|
|
loops = Math.floor(this.config.roomVidPlaytime / room.videoData.length);
|
|
}
|
|
console.log(`Adding room video for ${room.dungeonName} - ${room.roomName} to the queue (${loops} loops)`);
|
|
|
|
let video = {
|
|
filePath: `${this.config.roomVidsBasePath}${room.winPath}`,
|
|
sceneItem: (room.videoData.width === 960) ? "4x3ph" : "16x9ph",
|
|
length: room.videoData.length,
|
|
label: room.roomName,
|
|
chatName: room.roomName,
|
|
loops: loops,
|
|
requestedBy: room.requestedBy
|
|
};
|
|
|
|
this.state.videoQueue.push(video);
|
|
};
|
|
|
|
// 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
|
|
let ignoreStates = ['ENDING', 'ENDED', 'PAUSED'];
|
|
if (ignoreStates.includes(this.state.showStatus)) {
|
|
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) {
|
|
console.log(`It has been ${secondsSinceLastCommercial} seconds since the last commercial break!`);
|
|
// Random chance for it to be "everybody wow"
|
|
let memeId = false;
|
|
if ((Math.floor(Math.random() * 100) + 1) <= this.config.auwChance) {
|
|
console.log(`Showing AUW!`);
|
|
memeId = 'auw';
|
|
}
|
|
|
|
this.showMeme(memeId)
|
|
.then(() => {
|
|
this.state.lastCommercialShownAt = Date.now();
|
|
this.nextVideo();
|
|
})
|
|
.catch(console.error);
|
|
|
|
return;
|
|
}
|
|
|
|
// Keep track of recently played videos
|
|
if (this.state.recentlyPlayed.length === this.config.recentlyPlayedMemory) {
|
|
this.state.recentlyPlayed.shift();
|
|
}
|
|
this.state.recentlyPlayed.push(this.state.currentVideo.id);
|
|
|
|
// If a commercial is playing, wait until it's done to switch
|
|
while (this.state.commercialPlaying === true) {}
|
|
|
|
// play the next video in the queue, or pick one at random if the queue is empty
|
|
if (this.state.videoQueue.length > 0) {
|
|
this.state.currentVideo = this.state.videoQueue.shift();
|
|
} else {
|
|
// Random chance for room grind to be played for an amount of time instead of another video be shuffled to
|
|
if ((Math.floor(Math.random() * 100) + 1) <= this.config.roomGrindChance) {
|
|
console.log(`Room grind selected!`);
|
|
// show room-grind source
|
|
this.obs.showRoomGrind(this.config.roomGrindPlaytime, () => {this.nextVideo()})
|
|
.then(timer => {
|
|
this.state.videoTimer = timer;
|
|
})
|
|
.catch(console.error);
|
|
|
|
return;
|
|
}
|
|
|
|
// Random chance for room videos to be added
|
|
if ((Math.floor(Math.random() * 100) + 1) <= this.config.roomShuffleChance) {
|
|
console.log(`Room vids selected!`);
|
|
|
|
this.addRoomVideo(this.config.rooms.sort(util.randSort).slice(0, 1).shift());
|
|
|
|
// play the first one
|
|
this.state.currentVideo = this.state.videoQueue.shift();
|
|
} else {
|
|
// filter recently played from shuffle
|
|
let freshVods = this.config.vods.alttp.filter(e => {
|
|
return e.includeInShuffle === true && !this.state.recentlyPlayed.includes(e.id);
|
|
});
|
|
this.state.currentVideo = freshVods.sort(util.randSort).slice(0, 1).shift();
|
|
}
|
|
}
|
|
|
|
this.showVideo(this.state.currentVideo);
|
|
};
|
|
|
|
// "Commercials"
|
|
this.showCommercial = (video, callback) => {
|
|
return new Promise((resolve, reject) => {
|
|
let handleFinish = () => {
|
|
console.log('commercial is finished playing...');
|
|
this.state.commercialPlaying = false;
|
|
if (typeof callback !== 'undefined') callback();
|
|
}
|
|
|
|
this.obs.playVideoInScene(video, this.config.commercialSceneName, handleFinish)
|
|
.then(timer => {
|
|
this.state.commercialPlaying = true;
|
|
resolve(timer);
|
|
})
|
|
.catch(reject);
|
|
});
|
|
};
|
|
|
|
// Memes-By-Id
|
|
this.showMeme = id => {
|
|
return new Promise((resolve, reject) => {
|
|
// find the vod in memes
|
|
let video = this.config.vods.memes.find(e => e.id === id);
|
|
if (!video) {
|
|
reject(`No meme found matching ID ${id}`);
|
|
}
|
|
|
|
let handleFinish = () => {
|
|
if (id === 'auw') {
|
|
this.obs.hide("owen", this.config.commercialSceneName);
|
|
}
|
|
resolve();
|
|
};
|
|
|
|
this.showCommercial(video, handleFinish)
|
|
.then(videoHasStarted => {
|
|
// in the case of 'auw', show owen
|
|
if (id === 'auw') {
|
|
this.obs.show("owen", this.config.commercialSceneName);
|
|
}
|
|
})
|
|
.catch(console.error);
|
|
});
|
|
};
|
|
|
|
// Skip the current video and play the next
|
|
this.skip = () => {
|
|
clearTimeout(this.state.videoTimer);
|
|
this.obs.hide(this.state.currentVideo.sceneItem, this.config.defaultSceneName).then(this.nextVideo).catch(console.error);
|
|
};
|
|
|
|
// Clears.. the... queue
|
|
this.clearQueue = () => {
|
|
this.state.videoQueue = [];
|
|
};
|
|
|
|
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;
|