shuffling, vr

This commit is contained in:
Chris Ham
2018-09-16 21:04:17 -07:00
parent 7c1b93502e
commit 3cec42ef07
3 changed files with 200 additions and 49 deletions

View File

@@ -50,13 +50,13 @@
], ],
"defaultPlaylist": "room-grind" "defaultPlaylist": "room-grind"
}, },
"initialQueueSize": 10, "initialQueueSize": 3,
"videoSceneName": "fgfm", "videoSceneName": "fgfm",
"videoPollSize": 5, "videoPollSize": 5,
"currentActivitySceneItemName": "now-showing-txt", "currentActivitySceneItemName": "now-showing-txt",
"vods": [ "vods": [
{ {
"id": "ottas-seg-escape", "id": "ot-seg-escape",
"category": "Optimal TTAS Segment", "category": "Optimal TTAS Segment",
"label": false, "label": false,
"chatName": "Escape (OTTAS Seg)", "chatName": "Escape (OTTAS Seg)",
@@ -70,7 +70,7 @@
"length": 352 "length": 352
}, },
{ {
"id": "ottas-seg-eastern", "id": "ot-seg-eastern",
"category": "Optimal TTAS Segment", "category": "Optimal TTAS Segment",
"label": false, "label": false,
"chatName": "Eastern (OTTAS Seg)", "chatName": "Eastern (OTTAS Seg)",
@@ -84,7 +84,7 @@
"length": 277 "length": 277
}, },
{ {
"id": "ottas-seg-desert", "id": "ot-seg-desert",
"category": "Optimal TTAS Segment", "category": "Optimal TTAS Segment",
"label": false, "label": false,
"chatName": "Desert (OTTAS Seg)", "chatName": "Desert (OTTAS Seg)",
@@ -98,7 +98,7 @@
"length": 345 "length": 345
}, },
{ {
"id": "ottas-seg-hera", "id": "ot-seg-hera",
"category": "Optimal TTAS Segment", "category": "Optimal TTAS Segment",
"label": false, "label": false,
"chatName": "Hera (OTTAS Seg)", "chatName": "Hera (OTTAS Seg)",
@@ -112,7 +112,7 @@
"length": 299 "length": 299
}, },
{ {
"id": "ottas-seg-atower", "id": "ot-seg-atower",
"category": "Optimal TTAS Segment", "category": "Optimal TTAS Segment",
"label": false, "label": false,
"chatName": "ATower (OTTAS Seg)", "chatName": "ATower (OTTAS Seg)",
@@ -126,7 +126,7 @@
"length": 352 "length": 352
}, },
{ {
"id": "ottas-seg-pod", "id": "ot-seg-pod",
"category": "Optimal TTAS Segment", "category": "Optimal TTAS Segment",
"label": false, "label": false,
"chatName": "PoD (OTTAS Seg)", "chatName": "PoD (OTTAS Seg)",
@@ -140,7 +140,7 @@
"length": 309 "length": 309
}, },
{ {
"id": "ottas-seg-thieves", "id": "ot-seg-thieves",
"category": "Optimal TTAS Segment", "category": "Optimal TTAS Segment",
"label": false, "label": false,
"chatName": "Thieves (OTTAS Seg)", "chatName": "Thieves (OTTAS Seg)",
@@ -154,7 +154,7 @@
"length": 377 "length": 377
}, },
{ {
"id": "ottas-seg-skull", "id": "ot-seg-skull",
"category": "Optimal TTAS Segment", "category": "Optimal TTAS Segment",
"label": false, "label": false,
"chatName": "Skull (OTTAS Seg)", "chatName": "Skull (OTTAS Seg)",
@@ -168,7 +168,7 @@
"length": 267 "length": 267
}, },
{ {
"id": "ottas-seg-ice", "id": "ot-seg-ice",
"category": "Optimal TTAS Segment", "category": "Optimal TTAS Segment",
"label": false, "label": false,
"chatName": "Ice (OTTAS Seg)", "chatName": "Ice (OTTAS Seg)",
@@ -182,7 +182,7 @@
"length": 318 "length": 318
}, },
{ {
"id": "ottas-seg-swamp", "id": "ot-seg-swamp",
"category": "Optimal TTAS Segment", "category": "Optimal TTAS Segment",
"label": false, "label": false,
"chatName": "Swamp (OTTAS Seg)", "chatName": "Swamp (OTTAS Seg)",
@@ -196,7 +196,7 @@
"length": 345 "length": 345
}, },
{ {
"id": "ottas-seg-mire", "id": "ot-seg-mire",
"category": "Optimal TTAS Segment", "category": "Optimal TTAS Segment",
"label": false, "label": false,
"chatName": "Mire (OTTAS Seg)", "chatName": "Mire (OTTAS Seg)",
@@ -210,7 +210,7 @@
"length": 365 "length": 365
}, },
{ {
"id": "ottas-seg-trock", "id": "ot-seg-trock",
"category": "Optimal TTAS Segment", "category": "Optimal TTAS Segment",
"label": false, "label": false,
"chatName": "TRock (OTTAS Seg)", "chatName": "TRock (OTTAS Seg)",
@@ -224,7 +224,7 @@
"length": 361 "length": 361
}, },
{ {
"id": "ottas-seg-gtower", "id": "ot-seg-gtower",
"category": "Optimal TTAS Segment", "category": "Optimal TTAS Segment",
"label": false, "label": false,
"chatName": "GTower (OTTAS Seg)", "chatName": "GTower (OTTAS Seg)",
@@ -238,7 +238,7 @@
"length": 396 "length": 396
}, },
{ {
"id": "ttas-seg-ganon", "id": "ot-seg-ganon",
"category": "Optimal TTAS Segment", "category": "Optimal TTAS Segment",
"label": false, "label": false,
"chatName": "Ganon (TTAS Seg)", "chatName": "Ganon (TTAS Seg)",
@@ -252,7 +252,7 @@
"length": 108 "length": 108
}, },
{ {
"id": "sttas-seg-escape", "id": "st-seg-escape",
"category": "Safe TTAS Segment", "category": "Safe TTAS Segment",
"label": false, "label": false,
"chatName": "Escape (STTAS Seg)", "chatName": "Escape (STTAS Seg)",
@@ -266,7 +266,7 @@
"length": 354 "length": 354
}, },
{ {
"id": "sttas-seg-eastern", "id": "st-seg-eastern",
"category": "Safe TTAS Segment", "category": "Safe TTAS Segment",
"label": false, "label": false,
"chatName": "Eastern (STTAS Seg)", "chatName": "Eastern (STTAS Seg)",
@@ -280,7 +280,7 @@
"length": 281 "length": 281
}, },
{ {
"id": "sttas-seg-desert", "id": "st-seg-desert",
"category": "Safe TTAS Segment", "category": "Safe TTAS Segment",
"label": false, "label": false,
"chatName": "Desert (STTAS Seg)", "chatName": "Desert (STTAS Seg)",
@@ -294,7 +294,7 @@
"length": 347 "length": 347
}, },
{ {
"id": "sttas-seg-hera", "id": "st-seg-hera",
"category": "Safe TTAS Segment", "category": "Safe TTAS Segment",
"label": false, "label": false,
"chatName": "Hera (STTAS Seg)", "chatName": "Hera (STTAS Seg)",
@@ -308,7 +308,7 @@
"length": 303 "length": 303
}, },
{ {
"id": "sttas-seg-atower", "id": "st-seg-atower",
"category": "Safe TTAS Segment", "category": "Safe TTAS Segment",
"label": false, "label": false,
"chatName": "Agah Tower (STTAS Seg)", "chatName": "Agah Tower (STTAS Seg)",
@@ -322,7 +322,7 @@
"length": 354 "length": 354
}, },
{ {
"id": "sttas-seg-pod", "id": "st-seg-pod",
"category": "Safe TTAS Segment", "category": "Safe TTAS Segment",
"label": false, "label": false,
"chatName": "PoD (STTAS Seg)", "chatName": "PoD (STTAS Seg)",
@@ -336,7 +336,7 @@
"length": 310 "length": 310
}, },
{ {
"id": "sttas-seg-thieves", "id": "st-seg-thieves",
"category": "Safe TTAS Segment", "category": "Safe TTAS Segment",
"label": false, "label": false,
"chatName": "Thieves (STTAS Seg)", "chatName": "Thieves (STTAS Seg)",
@@ -350,7 +350,7 @@
"length": 379 "length": 379
}, },
{ {
"id": "sttas-seg-skull", "id": "st-seg-skull",
"category": "Safe TTAS Segment", "category": "Safe TTAS Segment",
"label": false, "label": false,
"chatName": "Skull (STTAS Seg)", "chatName": "Skull (STTAS Seg)",
@@ -364,7 +364,7 @@
"length": 270 "length": 270
}, },
{ {
"id": "sttas-seg-ice", "id": "st-seg-ice",
"category": "Safe TTAS Segment", "category": "Safe TTAS Segment",
"label": false, "label": false,
"chatName": "Ice (STTAS Seg)", "chatName": "Ice (STTAS Seg)",
@@ -378,7 +378,7 @@
"length": 321 "length": 321
}, },
{ {
"id": "sttas-seg-swamp", "id": "st-seg-swamp",
"category": "Safe TTAS Segment", "category": "Safe TTAS Segment",
"label": false, "label": false,
"chatName": "Swamp (STTAS Seg)", "chatName": "Swamp (STTAS Seg)",
@@ -392,7 +392,7 @@
"length": 351 "length": 351
}, },
{ {
"id": "sttas-seg-mire", "id": "st-seg-mire",
"category": "Safe TTAS Segment", "category": "Safe TTAS Segment",
"label": false, "label": false,
"chatName": "Mire (STTAS Seg)", "chatName": "Mire (STTAS Seg)",
@@ -406,7 +406,7 @@
"length": 370 "length": 370
}, },
{ {
"id": "sttas-seg-trock", "id": "st-seg-trock",
"category": "Safe TTAS Segment", "category": "Safe TTAS Segment",
"label": false, "label": false,
"chatName": "TRock (STTAS Seg)", "chatName": "TRock (STTAS Seg)",
@@ -420,7 +420,7 @@
"length": 364 "length": 364
}, },
{ {
"id": "sttas-seg-gtower", "id": "st-seg-gtower",
"category": "Safe TTAS Segment", "category": "Safe TTAS Segment",
"label": false, "label": false,
"chatName": "GTower (STTAS Seg)", "chatName": "GTower (STTAS Seg)",
@@ -711,7 +711,7 @@
"scale.x": 1280 "scale.x": 1280
}, },
"sceneItem": "16x9ph", "sceneItem": "16x9ph",
"length": 1349 "length": 1409
}, },
{ {
"id": "pb-ms", "id": "pb-ms",

125
twitch.js
View File

@@ -3,19 +3,17 @@
*/ */
// @TODO: modularize OBS and Twitch code // @TODO: modularize OBS and Twitch code
// @TODO: Make the bot aware of what video is current active
// @TODO: Change video playlist source on an interval
// @TODO: Rotating background images (leftside) // @TODO: Rotating background images (leftside)
// @TODO: Stream alerts for chat // @TODO: Stream alerts for chat
// @TODO: Instead of playlists being chosen, have a database of files that can be played // @TODO: Room vid requests / import
// dynamically load this into a single media source that hides itself after playback ends // @TODO: Add random chance for room grind playlist to show for certain amount of time
// listen for the event of the video ending and switch the source to the next one in the queue // ☐ add memes to commercial scene
// // ☐ show commercials after a video length cap is hit -- show at conclusion of video
// We can have the video queue work a lot like the songrequest queue. // ☐ add $setcurrent support (to update text label through obs websocket instead of chat)
// Instead of a new playlist being chosen every 2 hours, votes will be held for which *individual* video should be added to the queue next. // ☐ update PB vod lengths to cut off before credits
// A list of 10 or so will be chosen at random to be voted on. The top (x) will be added to the video queue. If nothing gets voted on, something random gets added. // ☐ support for $pause
// This will actually be easier on OBS too -- instead of having multiple playlists I have to manage within there, I can use a single media source and dynamically load whatever video gets chosen into that. // ☐ remove currently playing video from vote choices
// I can also store whatever metadata about the video I want so I can update the labels automatically too. OpieOP
// Import modules // Import modules
const irc = require('irc'); const irc = require('irc');
@@ -43,6 +41,7 @@ obs.connect({ address: config.obs.websocket.address, password: config.obs.websoc
.catch(err => { .catch(err => {
console.log(err); console.log(err);
}); });
// 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:', err);
@@ -214,7 +213,7 @@ const twitchInit = (config, obs) => {
// Listen for commands from everyone else // Listen for commands from everyone else
if (commandNoPrefix === 'rngames') { if (commandNoPrefix === 'rngames') {
twitchChat.say(to, snesGames.sort( function() { return 0.5 - Math.random() } ).slice(0, 10).join(' | ')); twitchChat.say(to, snesGames.sort( () => { return 0.5 - Math.random() } ).slice(0, 10).join(' | '));
} }
} }
}); });
@@ -258,19 +257,34 @@ 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( function() { return 0.5 - Math.random() } ).slice(0, config.initialQueueSize); let 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(); let currentVideo = videoQueue.shift();
let videoTimer; let videoTimer;
const nextVideo = () => {
// play the next video in the queue, or pick one at random if the queue is empty
if (videoQueue.length > 0) {
currentVideo = videoQueue.shift();
//console.log(`Playing next video in queue: ${JSON.stringify(currentVideo)}`);
} else {
currentVideo = config.vods.sort( () => { return 0.5 - Math.random() } ).slice(0, 1).shift();
//console.log(`Queue is empty, vod chosen at random for shuffle: ${JSON.stringify(currentVideo)}`);
}
showVideo(currentVideo);
};
const showVideo = video => { const showVideo = video => {
//console.log(`Showing video: ${JSON.stringify(video)}`);
// 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});
@@ -279,12 +293,12 @@ const streamInit = (config, obs, twitch) => {
} }
}) })
.then(data => { .then(data => {
//console.log('activity label updated');
// Set a timeout for hiding this at the end of the video 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 => {
currentVideo = videoQueue.shift(); nextVideo();
showVideo(currentVideo);
}); });
}, video.length*1000) }, video.length*1000)
}) })
@@ -300,7 +314,7 @@ const streamInit = (config, obs, twitch) => {
let rockTheVote = () => {}; let rockTheVote = () => {};
let rtvInterval = setInterval(() => {rockTheVote()}, 300000); let rtvInterval = setInterval(() => {rockTheVote()}, 300000);
let videoVoteJob = schedule.scheduleJob("*/15 * * * *", () => { let videoVoteJob = new schedule.Job(() => {
// Tally votes from previous election (if there was one), add the winner to the queue // Tally votes from previous election (if there was one), add the winner to the queue
let winner; let winner;
if (currentChoices.length > 0) { if (currentChoices.length > 0) {
@@ -348,7 +362,7 @@ const streamInit = (config, obs, twitch) => {
let inQueue = videoQueue.findIndex(q => q.id === e.id) !== -1; let inQueue = videoQueue.findIndex(q => q.id === e.id) !== -1;
return !inQueue; return !inQueue;
}); });
currentChoices = vodsNotInQueue.sort( function() { return 0.5 - Math.random() } ).slice(0, config.videoPollSize); currentChoices = vodsNotInQueue.sort( () => { return 0.5 - Math.random() } ).slice(0, config.videoPollSize);
// Poll the chat // Poll the chat
let chatChoices = currentChoices.map((c, i) => { let chatChoices = currentChoices.map((c, i) => {
@@ -363,7 +377,6 @@ const streamInit = (config, obs, twitch) => {
rtvInterval = setInterval(() => {rockTheVote()}, 300000); rtvInterval = setInterval(() => {rockTheVote()}, 300000);
}); });
// Track user votes for video queue
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;
@@ -373,17 +386,55 @@ const streamInit = (config, obs, twitch) => {
let commandParts = message.slice(config.twitch.cmdPrefix.length).split(' '); let commandParts = message.slice(config.twitch.cmdPrefix.length).split(' ');
let commandNoPrefix = commandParts[0] || ''; let commandNoPrefix = commandParts[0] || '';
// ADMIN COMMANDS
if (config.twitch.admins.includes(from) || from === config.twitch.username.toLowerCase()) { if (config.twitch.admins.includes(from) || from === config.twitch.username.toLowerCase()) {
// 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 => {
currentVideo = videoQueue.shift(); nextVideo();
showVideo(currentVideo);
}); });
// ADD
} else if (commandNoPrefix === 'add') {
let requestedVideoId = commandParts[1] || false;
if (requestedVideoId === false) {
twitch.botChat.say(to, `Missing video ID`);
return;
}
// make sure request vid isn't in the queue already
if (videoQueue.findIndex(e => e.id === requestedVideoId) !== -1) {
twitch.botChat.say(to, `That video is in the queue already!`);
return;
}
// search for req'd vid by id in config.vods
let vodIndex = config.vods.findIndex(e => e.id === requestedVideoId);
if (vodIndex === -1) {
twitch.botChat.say(to, `A video with that ID does not exist!`);
return;
}
// add to queue if it exists
videoQueue.push(config.vods[vodIndex]);
twitch.botChat.say(to, `${config.vods[vodIndex].chatName} has been added to the queue [${videoQueue.length}]`);
return;
// START VOTE
} else if (commandNoPrefix === 'startvote') {
videoVoteJob.reschedule("*/15 * * * *");
twitch.botChat.say(to, `Voting has been started. Next run: ${videoVoteJob.nextInvocation()}`);
// PAUSE VOTE
} else if (commandNoPrefix === 'pausevote') {
clearInterval(rtvInterval);
videoVoteJob.cancel();
twitch.botChat.say(to, `Voting has been paused.`);
} }
} }
// ALL USER COMMANDS
// VOTE FOR VIDEO
if (commandNoPrefix === 'vote') { if (commandNoPrefix === 'vote') {
let userVote = commandParts[1] || false; let userVote = commandParts[1] || false;
@@ -412,6 +463,7 @@ const streamInit = (config, obs, twitch) => {
userVotes.push({"from": from, "vote": userVote}); userVotes.push({"from": from, "vote": userVote});
twitch.botChat.say(to, `@${from}, your vote has been logged!`); twitch.botChat.say(to, `@${from}, your vote has been logged!`);
} }
// QUEUE STATUS
} else if (commandNoPrefix === 'queue') { } else if (commandNoPrefix === 'queue') {
if (videoQueue.length > 0) { if (videoQueue.length > 0) {
let chatQueue = videoQueue.map((c, i) => { let chatQueue = videoQueue.map((c, i) => {
@@ -421,8 +473,41 @@ const streamInit = (config, obs, twitch) => {
} else { } else {
twitch.botChat.say(to, `No videos currently in queue!`); twitch.botChat.say(to, `No videos currently in queue!`);
} }
// CURRENT VIDEO
} else if (commandNoPrefix === 'current') { } else if (commandNoPrefix === 'current') {
twitch.botChat.say(to, `Now Playing: ${currentVideo.chatName}`); twitch.botChat.say(to, `Now Playing: ${currentVideo.chatName}`);
// NEXT VIDEO
} else if (commandNoPrefix === 'next') {
if (videoQueue.length > 0) {
twitch.botChat.say(to, `Next Video: ${videoQueue[0].chatName}`);
} else {
twitch.botChat.say(to, `No videos currently in queue!`);
}
// VIDEO REQUEST
} else if (commandNoPrefix === 'vr') {
let requestedVideoId = commandParts[1] || false;
if (requestedVideoId === false) {
twitch.botChat.say(to, `Useage: ${config.twitch.cmdPrefix}vr <video-id> | Videos: https://pastebin.com/qv0wDkvB`);
return;
}
// make sure request vid isn't in the queue already
if (videoQueue.findIndex(e => e.id === requestedVideoId) !== -1) {
twitch.botChat.say(to, `That video is in the queue already!`);
return;
}
// search for req'd vid by id in config.vods
let vodIndex = config.vods.findIndex(e => e.id === requestedVideoId);
if (vodIndex === -1) {
twitch.botChat.say(to, `A video with that ID does not exist!`);
return;
}
// add to queue if it exists
videoQueue.push(config.vods[vodIndex]);
twitch.botChat.say(to, `${config.vods[vodIndex].chatName} has been added to the queue [${videoQueue.length}]`);
return;
} }
} }
}); });

66
vid-id-list Executable file
View File

@@ -0,0 +1,66 @@
Request videos using $vr <video-id> where video-id is one of the following:
e.g. $vr st-seg-gtower
Optimal TTAS Segments
---------------------
ot-seg-escape
ot-seg-eastern
ot-seg-desert
ot-seg-hera
ot-seg-atower
ot-seg-pod
ot-seg-thieves
ot-seg-skull
ot-seg-ice
ot-seg-swamp
ot-seg-mire
ot-seg-trock
ot-seg-gtower
ot-seg-ganon
Safe/RTA Theory TAS Segments
----------------------------
st-seg-escape
st-seg-eastern
st-seg-desert
st-seg-hera
st-seg-atower
st-seg-pod
st-seg-thieves
st-seg-skull
st-seg-ice
st-seg-swamp
st-seg-mire
st-seg-trock
st-seg-gtower
NMG Golds
---------
nmg-gold-escape
nmg-gold-eastern
nmg-gold-desert
nmg-gold-hera
nmg-gold-atower
nmg-gold-pod
nmg-gold-thieves
nmg-gold-skull
nmg-gold-ice
nmg-gold-swamp
nmg-gold-mire
nmg-gold-trock
nmg-gold-gtower
nmg-gold-ganon
Personal Bests
--------------
pb-100-ahp
pb-ab
pb-ad
pb-any-nmg
pb-any-no-eg
pb-master-sword
pb-ms
pb-ms-no-eg
pb-rbo