track recently played videos, setactivity
This commit is contained in:
31
config.json
31
config.json
@@ -641,7 +641,7 @@
|
|||||||
"scale.x": 1280
|
"scale.x": 1280
|
||||||
},
|
},
|
||||||
"sceneItem": "16x9ph",
|
"sceneItem": "16x9ph",
|
||||||
"length": 5322
|
"length": 4786
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "pb-ab",
|
"id": "pb-ab",
|
||||||
@@ -655,7 +655,7 @@
|
|||||||
"scale.x": 1280
|
"scale.x": 1280
|
||||||
},
|
},
|
||||||
"sceneItem": "16x9ph",
|
"sceneItem": "16x9ph",
|
||||||
"length": 4730
|
"length": 4200
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "pb-ad",
|
"id": "pb-ad",
|
||||||
@@ -669,7 +669,7 @@
|
|||||||
"scale.x": 1280
|
"scale.x": 1280
|
||||||
},
|
},
|
||||||
"sceneItem": "16x9ph",
|
"sceneItem": "16x9ph",
|
||||||
"length": 5084
|
"length": 4555
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "pb-any-nmg",
|
"id": "pb-any-nmg",
|
||||||
@@ -683,7 +683,7 @@
|
|||||||
"scale.x": 1280
|
"scale.x": 1280
|
||||||
},
|
},
|
||||||
"sceneItem": "16x9ph",
|
"sceneItem": "16x9ph",
|
||||||
"length": 5715
|
"length": 5190
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "pb-any-no-eg",
|
"id": "pb-any-no-eg",
|
||||||
@@ -697,7 +697,7 @@
|
|||||||
"scale.x": 1280
|
"scale.x": 1280
|
||||||
},
|
},
|
||||||
"sceneItem": "16x9ph",
|
"sceneItem": "16x9ph",
|
||||||
"length": 2352
|
"length": 1833
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "pb-master-sword",
|
"id": "pb-master-sword",
|
||||||
@@ -753,30 +753,25 @@
|
|||||||
"scale.x": 1280
|
"scale.x": 1280
|
||||||
},
|
},
|
||||||
"sceneItem": "16x9ph",
|
"sceneItem": "16x9ph",
|
||||||
"length": 5250
|
"length": 4725
|
||||||
}
|
},
|
||||||
],
|
|
||||||
"vodsToCrop": [
|
|
||||||
{
|
{
|
||||||
"id": "pb-100-nmg",
|
"id": "pb-100-nmg",
|
||||||
"category": "Personal Best",
|
"category": "Personal Best",
|
||||||
"label": "Personal Best: 100% NMG (1:49:30) [2017-01-31]",
|
"label": "Personal Best: 100% NMG (1:49:30) [2017-01-31]",
|
||||||
"chatName": "100% NMG (PB)",
|
"chatName": "100% NMG (PB)",
|
||||||
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\100%\\2017-01-31-hundo-14930.mp4"
|
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\100%\\2017-01-31-hundo-14930.mp4",
|
||||||
|
"sceneItem": "legacyph",
|
||||||
|
"length": 6598
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "pb-dg",
|
"id": "pb-dg",
|
||||||
"category": "Personal Best",
|
"category": "Personal Best",
|
||||||
"label": "Personal Best: Defeat Ganon (13:41) [2017-02-20]",
|
"label": "Personal Best: Defeat Ganon (13:41) [2017-02-20]",
|
||||||
"chatName": "Defeat Ganon (PB)",
|
"chatName": "Defeat Ganon (PB)",
|
||||||
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\defeat-ganon\\2017-02-20_12-55-40-defeat-ganon-1341.mp4"
|
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\defeat-ganon\\2017-02-20_12-55-40-defeat-ganon-1341.mp4",
|
||||||
},
|
"sceneItem": "legacyph",
|
||||||
{
|
"length": 868
|
||||||
"id": "pb-low-nmg",
|
|
||||||
"category": "Personal Best",
|
|
||||||
"label": "Personal Best: Low% NMG (1:32:48) [2017-05-11]",
|
|
||||||
"chatName": "Low% NMG (PB)",
|
|
||||||
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\low%-nmg-nsq\\2017-05-11 17-01-52-low%-13248.mkv"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"debug": false
|
"debug": false
|
||||||
|
|||||||
18
fgfm.TODO
Executable file
18
fgfm.TODO
Executable file
@@ -0,0 +1,18 @@
|
|||||||
|
TODO:
|
||||||
|
☐ Room vid requests / import
|
||||||
|
☐ Add random chance for room grind playlist to show for certain amount of time
|
||||||
|
☐ modularize OBS and Twitch code
|
||||||
|
☐ Rotating background images (leftside)
|
||||||
|
☐ Stream alerts for chat
|
||||||
|
☐ add memes to commercial scene
|
||||||
|
☐ show commercials after a video length cap is hit -- show at conclusion of video
|
||||||
|
☐ add $setcurrent support (to update text label through obs websocket instead of chat)
|
||||||
|
☐ support for $pause
|
||||||
|
☐ remove currently playing video from vote choices
|
||||||
|
☐ restrict # of requests a user can have in the queue at once
|
||||||
|
☐ Add cooldowns
|
||||||
|
✔ remember the last X vids played, remove these from shuffle choices @done (18-09-17 14:34)
|
||||||
|
☐ Start/stop stream automation
|
||||||
|
☐ Move vods to their own config
|
||||||
|
☐ Tool to output list of video ID's / descriptions
|
||||||
|
☐ Support viewer skip voting
|
||||||
90
fgfm.js
90
fgfm.js
@@ -2,20 +2,6 @@
|
|||||||
* FG.fm Automation
|
* FG.fm Automation
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// @TODO: Room vid requests / import
|
|
||||||
// @TODO: modularize OBS and Twitch code
|
|
||||||
// @TODO: Rotating background images (leftside)
|
|
||||||
// @TODO: Stream alerts for chat
|
|
||||||
// @TODO: Add random chance for room grind playlist to show for certain amount of time
|
|
||||||
// @TODO: add memes to commercial scene
|
|
||||||
// @TODO: show commercials after a video length cap is hit -- show at conclusion of video
|
|
||||||
// @TODO: add $setcurrent support (to update text label through obs websocket instead of chat)
|
|
||||||
// @TODO: update PB vod lengths to cut off before credits
|
|
||||||
// @TODO: support for $pause
|
|
||||||
// @TODO: remove currently playing video from vote choices
|
|
||||||
// @TODO: restrict # of requests a user can have in the queue at once
|
|
||||||
// @TODO: add cooldowns
|
|
||||||
|
|
||||||
// Import modules
|
// Import modules
|
||||||
const irc = require('irc');
|
const irc = require('irc');
|
||||||
const OBSWebSocket = require('obs-websocket-js');
|
const OBSWebSocket = require('obs-websocket-js');
|
||||||
@@ -24,9 +10,12 @@ const util = require('./lib/util');
|
|||||||
|
|
||||||
// Read internal configuration
|
// Read internal configuration
|
||||||
let config = require('./config.json');
|
let config = require('./config.json');
|
||||||
let currentPlaylist = config.obs.defaultPlaylist;
|
|
||||||
let twitchChannel = config.twitch.channels[0].toLowerCase();
|
|
||||||
const snesGames = require('./conf/snesgames.json');
|
const snesGames = require('./conf/snesgames.json');
|
||||||
|
const twitchChannel = config.twitch.channels[0].toLowerCase();
|
||||||
|
|
||||||
|
let videoQueue = recentlyPlayed = [];
|
||||||
|
let currentVideo;
|
||||||
|
let videoTimer;
|
||||||
|
|
||||||
// Connect to OBS Websocket
|
// Connect to OBS Websocket
|
||||||
const obs = new OBSWebSocket();
|
const obs = new OBSWebSocket();
|
||||||
@@ -45,7 +34,7 @@ obs.connect({ address: config.obs.websocket.address, password: config.obs.websoc
|
|||||||
|
|
||||||
// Listen for errors from OBS
|
// Listen for errors from OBS
|
||||||
obs.on('error', err => {
|
obs.on('error', err => {
|
||||||
console.error('OBS socket error:', err);
|
console.error(`OBS socket error: ${JSON.stringify(err)}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Initialize Twitch chat hooks
|
// Initialize Twitch chat hooks
|
||||||
@@ -84,6 +73,7 @@ const twitchInit = (config, obs) => {
|
|||||||
// Listen for specific commands from admins
|
// Listen for specific commands from admins
|
||||||
if (config.twitch.admins.includes(from) || from === config.twitch.username.toLowerCase()) {
|
if (config.twitch.admins.includes(from) || from === config.twitch.username.toLowerCase()) {
|
||||||
|
|
||||||
|
// SHOW/HIDE SOURCE
|
||||||
if (commandNoPrefix === 'show' || commandNoPrefix === 'hide') {
|
if (commandNoPrefix === 'show' || commandNoPrefix === 'hide') {
|
||||||
|
|
||||||
let newVisibility = (commandNoPrefix === 'show');
|
let newVisibility = (commandNoPrefix === 'show');
|
||||||
@@ -118,7 +108,7 @@ const twitchInit = (config, obs) => {
|
|||||||
.catch(err => {
|
.catch(err => {
|
||||||
twitchChat.say(to, JSON.stringify(err));
|
twitchChat.say(to, JSON.stringify(err));
|
||||||
});
|
});
|
||||||
|
// TOGGLE SOURCE VISIBILITY
|
||||||
} else if (commandNoPrefix === 't') {
|
} else if (commandNoPrefix === 't') {
|
||||||
let target = commandParts[1] || false;
|
let target = commandParts[1] || false;
|
||||||
if (!target) {
|
if (!target) {
|
||||||
@@ -143,6 +133,7 @@ const twitchInit = (config, obs) => {
|
|||||||
.catch(err => {
|
.catch(err => {
|
||||||
twitchChat.say(to, JSON.stringify(err));
|
twitchChat.say(to, JSON.stringify(err));
|
||||||
});
|
});
|
||||||
|
// SWAP -- Hide one source, show another
|
||||||
} else if (commandNoPrefix === 'swap') {
|
} else if (commandNoPrefix === 'swap') {
|
||||||
// hide first argument, show second argument
|
// hide first argument, show second argument
|
||||||
let targetToHide = commandParts[1] || false;
|
let targetToHide = commandParts[1] || false;
|
||||||
@@ -157,9 +148,8 @@ const twitchInit = (config, obs) => {
|
|||||||
obs.setSceneItemProperties({"item": targetToShow, "visible": true});
|
obs.setSceneItemProperties({"item": targetToShow, "visible": true});
|
||||||
})
|
})
|
||||||
.catch(console.error);
|
.catch(console.error);
|
||||||
|
// Black Box "Everybody Wow" "commercial"
|
||||||
} else if (commandNoPrefix === 'auw') {
|
} else if (commandNoPrefix === 'auw') {
|
||||||
// @TODO: switch to 'commercial' scene and show appropriate items, then switch back
|
|
||||||
// this way, playing a commercial doesn't have to know what's playing in the other scene
|
|
||||||
obs.setCurrentScene({"scene-name": "commercial"})
|
obs.setCurrentScene({"scene-name": "commercial"})
|
||||||
.then(res => {
|
.then(res => {
|
||||||
// show the video
|
// show the video
|
||||||
@@ -185,7 +175,7 @@ const twitchInit = (config, obs) => {
|
|||||||
}, 248000);
|
}, 248000);
|
||||||
})
|
})
|
||||||
.catch(console.error);
|
.catch(console.error);
|
||||||
|
// SWITCH SCENES
|
||||||
} else if (commandNoPrefix === 'switch') {
|
} else if (commandNoPrefix === 'switch') {
|
||||||
|
|
||||||
let target = commandParts[1] || false;
|
let target = commandParts[1] || false;
|
||||||
@@ -205,6 +195,21 @@ const twitchInit = (config, obs) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(console.error);
|
.catch(console.error);
|
||||||
|
// SET ON-SCREEN ACTIVITY
|
||||||
|
} else if (commandNoPrefix === 'setactivity') {
|
||||||
|
let target = commandParts.slice(1).join(' ');
|
||||||
|
if (!target) {
|
||||||
|
twitchChat.say(to, `Please provide a new activity`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
obs.setTextGDIPlusProperties({"source": config.currentActivitySceneItemName, "scene-name": config.videoSceneName, "render": true, "text": target})
|
||||||
|
.then(res => {
|
||||||
|
twitchChat.say(to, `Activity updated!`);
|
||||||
|
return;
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
|
// REBOOT
|
||||||
} else if (commandNoPrefix === 'reboot') {
|
} else if (commandNoPrefix === 'reboot') {
|
||||||
console.log('Received request from admin to reboot...');
|
console.log('Received request from admin to reboot...');
|
||||||
twitchChat.say(to, 'Rebooting...');
|
twitchChat.say(to, 'Rebooting...');
|
||||||
@@ -258,34 +263,44 @@ const twitchInit = (config, obs) => {
|
|||||||
const streamInit = (config, obs, twitch) => {
|
const streamInit = (config, obs, twitch) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
console.log(`Setting up initial video queue...`);
|
console.log(`Setting up initial video queue...`);
|
||||||
let videoQueue = config.vods.sort( () => { return 0.5 - Math.random() } ).slice(0, config.initialQueueSize);
|
videoQueue = config.vods.sort( () => { return 0.5 - Math.random() } ).slice(0, config.initialQueueSize);
|
||||||
console.log(`Initial queue: ${videoQueue.map((c, i) => `[${i+1}] ${c.chatName}`).join(' | ')}`);
|
console.log(`Initial queue: ${videoQueue.map((c, i) => `[${i+1}] ${c.chatName}`).join(' | ')}`);
|
||||||
let currentVideo = videoQueue.shift();
|
currentVideo = videoQueue.shift();
|
||||||
let videoTimer;
|
|
||||||
|
|
||||||
|
// Pick the next video in the queue (or shuffle if queue is empty)
|
||||||
const nextVideo = () => {
|
const nextVideo = () => {
|
||||||
|
// add currentVideo.id to recentlyPlayed list, remove oldest video if cap is hit
|
||||||
|
if (recentlyPlayed.length === 3) {
|
||||||
|
recentlyPlayed.shift();
|
||||||
|
}
|
||||||
|
recentlyPlayed.push(currentVideo.id);
|
||||||
|
|
||||||
|
// @TODO: Add a random chance here for room grind to be played for an amount of time
|
||||||
|
|
||||||
|
|
||||||
// play the next video in the queue, or pick one at random if the queue is empty
|
// play the next video in the queue, or pick one at random if the queue is empty
|
||||||
if (videoQueue.length > 0) {
|
if (videoQueue.length > 0) {
|
||||||
currentVideo = videoQueue.shift();
|
currentVideo = videoQueue.shift();
|
||||||
//console.log(`Playing next video in queue: ${JSON.stringify(currentVideo)}`);
|
|
||||||
} else {
|
} else {
|
||||||
currentVideo = config.vods.sort( () => { return 0.5 - Math.random() } ).slice(0, 1).shift();
|
// filter recently played from shuffle
|
||||||
//console.log(`Queue is empty, vod chosen at random for shuffle: ${JSON.stringify(currentVideo)}`);
|
let freshVods = config.vods.filter(e => {
|
||||||
|
return !recentlyPlayed.includes(e.id);
|
||||||
|
});
|
||||||
|
currentVideo = freshVods.sort( () => { return 0.5 - Math.random() } ).slice(0, 1).shift();
|
||||||
}
|
}
|
||||||
showVideo(currentVideo);
|
showVideo(currentVideo);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Show a video and hide it when finished
|
||||||
const showVideo = video => {
|
const showVideo = video => {
|
||||||
//console.log(`Showing video: ${JSON.stringify(video)}`);
|
console.log(`Showing video: ${video.chatName}`);
|
||||||
// set the file path
|
// set the file path
|
||||||
obs.setSourceSettings({"sourceName": video.sceneItem, "sourceSettings": {"local_file": video.filePath}})
|
obs.setSourceSettings({"sourceName": video.sceneItem, "sourceSettings": {"local_file": video.filePath}})
|
||||||
.then(data => {
|
.then(data => {
|
||||||
// show the video
|
// show the video
|
||||||
//console.log('local_file updated');
|
|
||||||
return obs.setSceneItemProperties({"item": video.sceneItem, "scene-name": config.videoSceneName, "visible": true});
|
return obs.setSceneItemProperties({"item": video.sceneItem, "scene-name": config.videoSceneName, "visible": true});
|
||||||
})
|
})
|
||||||
.then(data => {
|
.then(data => {
|
||||||
//console.log('scene item shown');
|
|
||||||
// update activity label and show/hide appropriately
|
// update activity label and show/hide appropriately
|
||||||
if (video.label !== false) {
|
if (video.label !== false) {
|
||||||
return obs.setTextGDIPlusProperties({"source": config.currentActivitySceneItemName, "scene-name": config.videoSceneName, "render": true, "text": video.label});
|
return obs.setTextGDIPlusProperties({"source": config.currentActivitySceneItemName, "scene-name": config.videoSceneName, "render": true, "text": video.label});
|
||||||
@@ -294,8 +309,7 @@ const streamInit = (config, obs, twitch) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(data => {
|
.then(data => {
|
||||||
//console.log('activity label updated');
|
// hide this video when it's finished and play the next video
|
||||||
// Set a timeout for hiding this at the end of the video and play the next video
|
|
||||||
videoTimer = setTimeout(() => {
|
videoTimer = setTimeout(() => {
|
||||||
obs.setSceneItemProperties({"item": video.sceneItem, "scene-name": config.videoSceneName, "visible": false})
|
obs.setSceneItemProperties({"item": video.sceneItem, "scene-name": config.videoSceneName, "visible": false})
|
||||||
.then(data => {
|
.then(data => {
|
||||||
@@ -306,7 +320,6 @@ const streamInit = (config, obs, twitch) => {
|
|||||||
.catch(console.error);
|
.catch(console.error);
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log(`Showing first video: ${currentVideo.chatName}`);
|
|
||||||
showVideo(currentVideo);
|
showVideo(currentVideo);
|
||||||
|
|
||||||
console.log(`Initializing stream timers...`);
|
console.log(`Initializing stream timers...`);
|
||||||
@@ -378,6 +391,7 @@ const streamInit = (config, obs, twitch) => {
|
|||||||
rtvInterval = setInterval(() => {rockTheVote()}, 300000);
|
rtvInterval = setInterval(() => {rockTheVote()}, 300000);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Twitch Chat Commands for Video Queue Control
|
||||||
twitch.botChat.addListener('message', (from, to, message) => {
|
twitch.botChat.addListener('message', (from, to, message) => {
|
||||||
// Ignore everything from blacklisted users
|
// Ignore everything from blacklisted users
|
||||||
if (config.twitch.blacklistedUsers.includes(from)) return;
|
if (config.twitch.blacklistedUsers.includes(from)) return;
|
||||||
@@ -391,7 +405,6 @@ const streamInit = (config, obs, twitch) => {
|
|||||||
if (config.twitch.admins.includes(from) || from === config.twitch.username.toLowerCase()) {
|
if (config.twitch.admins.includes(from) || from === config.twitch.username.toLowerCase()) {
|
||||||
// SKIP
|
// SKIP
|
||||||
if (commandNoPrefix === 'skip') {
|
if (commandNoPrefix === 'skip') {
|
||||||
//console.log(`admin is skipping video: ${JSON.stringify(currentVideo)}`);
|
|
||||||
clearTimeout(videoTimer);
|
clearTimeout(videoTimer);
|
||||||
obs.setSceneItemProperties({"item": currentVideo.sceneItem, "scene-name": config.videoSceneName, "visible": false})
|
obs.setSceneItemProperties({"item": currentVideo.sceneItem, "scene-name": config.videoSceneName, "visible": false})
|
||||||
.then(res => {
|
.then(res => {
|
||||||
@@ -406,13 +419,13 @@ const streamInit = (config, obs, twitch) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// make sure request vid isn't in the queue already
|
// make sure request vid isn't in the queue already
|
||||||
if (videoQueue.findIndex(e => e.id === requestedVideoId) !== -1) {
|
if (videoQueue.findIndex(e => e.id == requestedVideoId) !== -1) {
|
||||||
twitch.botChat.say(to, `That video is in the queue already!`);
|
twitch.botChat.say(to, `That video is in the queue already!`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// search for req'd vid by id in config.vods
|
// search for req'd vid by id in config.vods
|
||||||
let vodIndex = config.vods.findIndex(e => e.id === requestedVideoId);
|
let vodIndex = config.vods.findIndex(e => e.id == requestedVideoId);
|
||||||
if (vodIndex === -1) {
|
if (vodIndex === -1) {
|
||||||
twitch.botChat.say(to, `A video with that ID does not exist!`);
|
twitch.botChat.say(to, `A video with that ID does not exist!`);
|
||||||
return;
|
return;
|
||||||
@@ -433,8 +446,10 @@ const streamInit = (config, obs, twitch) => {
|
|||||||
twitch.botChat.say(to, `Voting has been paused.`);
|
twitch.botChat.say(to, `Voting has been paused.`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
////////////////
|
||||||
|
|
||||||
// ALL USER COMMANDS
|
// USER COMMANDS
|
||||||
|
//
|
||||||
// VOTE FOR VIDEO
|
// VOTE FOR VIDEO
|
||||||
if (commandNoPrefix === 'vote') {
|
if (commandNoPrefix === 'vote') {
|
||||||
let userVote = commandParts[1] || false;
|
let userVote = commandParts[1] || false;
|
||||||
@@ -510,6 +525,7 @@ const streamInit = (config, obs, twitch) => {
|
|||||||
twitch.botChat.say(to, `${config.vods[vodIndex].chatName} has been added to the queue [${videoQueue.length}]`);
|
twitch.botChat.say(to, `${config.vods[vodIndex].chatName} has been added to the queue [${videoQueue.length}]`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
////////////////
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user