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

125
twitch.js
View File

@@ -3,19 +3,17 @@
*/
// @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: Stream alerts for chat
// @TODO: Instead of playlists being chosen, have a database of files that can be played
// dynamically load this into a single media source that hides itself after playback ends
// listen for the event of the video ending and switch the source to the next one in the queue
//
// We can have the video queue work a lot like the songrequest queue.
// 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.
// 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.
// 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.
// I can also store whatever metadata about the video I want so I can update the labels automatically too. OpieOP
// @TODO: Room vid requests / import
// @TODO: Add random chance for room grind playlist to show for certain amount of time
// ☐ 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)
// ☐ update PB vod lengths to cut off before credits
// ☐ support for $pause
// ☐ remove currently playing video from vote choices
// Import modules
const irc = require('irc');
@@ -43,6 +41,7 @@ obs.connect({ address: config.obs.websocket.address, password: config.obs.websoc
.catch(err => {
console.log(err);
});
// Listen for errors from OBS
obs.on('error', err => {
console.error('OBS socket error:', err);
@@ -214,7 +213,7 @@ const twitchInit = (config, obs) => {
// Listen for commands from everyone else
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) => {
return new Promise((resolve, reject) => {
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(' | ')}`);
let currentVideo = videoQueue.shift();
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 => {
//console.log(`Showing video: ${JSON.stringify(video)}`);
// set the file path
obs.setSourceSettings({"sourceName": video.sceneItem, "sourceSettings": {"local_file": video.filePath}})
.then(data => {
// show the video
//console.log('local_file updated');
return obs.setSceneItemProperties({"item": video.sceneItem, "scene-name": config.videoSceneName, "visible": true});
})
.then(data => {
//console.log('scene item shown');
// update activity label and show/hide appropriately
if (video.label !== false) {
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 => {
//console.log('activity label updated');
// Set a timeout for hiding this at the end of the video and play the next video
videoTimer = setTimeout(() => {
obs.setSceneItemProperties({"item": video.sceneItem, "scene-name": config.videoSceneName, "visible": false})
.then(data => {
currentVideo = videoQueue.shift();
showVideo(currentVideo);
nextVideo();
});
}, video.length*1000)
})
@@ -300,7 +314,7 @@ const streamInit = (config, obs, twitch) => {
let rockTheVote = () => {};
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
let winner;
if (currentChoices.length > 0) {
@@ -348,7 +362,7 @@ const streamInit = (config, obs, twitch) => {
let inQueue = videoQueue.findIndex(q => q.id === e.id) !== -1;
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
let chatChoices = currentChoices.map((c, i) => {
@@ -363,7 +377,6 @@ const streamInit = (config, obs, twitch) => {
rtvInterval = setInterval(() => {rockTheVote()}, 300000);
});
// Track user votes for video queue
twitch.botChat.addListener('message', (from, to, message) => {
// Ignore everything from blacklisted users
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 commandNoPrefix = commandParts[0] || '';
// ADMIN COMMANDS
if (config.twitch.admins.includes(from) || from === config.twitch.username.toLowerCase()) {
// SKIP
if (commandNoPrefix === 'skip') {
//console.log(`admin is skipping video: ${JSON.stringify(currentVideo)}`);
clearTimeout(videoTimer);
obs.setSceneItemProperties({"item": currentVideo.sceneItem, "scene-name": config.videoSceneName, "visible": false})
.then(res => {
currentVideo = videoQueue.shift();
showVideo(currentVideo);
nextVideo();
});
// 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') {
let userVote = commandParts[1] || false;
@@ -412,6 +463,7 @@ const streamInit = (config, obs, twitch) => {
userVotes.push({"from": from, "vote": userVote});
twitch.botChat.say(to, `@${from}, your vote has been logged!`);
}
// QUEUE STATUS
} else if (commandNoPrefix === 'queue') {
if (videoQueue.length > 0) {
let chatQueue = videoQueue.map((c, i) => {
@@ -421,8 +473,41 @@ const streamInit = (config, obs, twitch) => {
} else {
twitch.botChat.say(to, `No videos currently in queue!`);
}
// CURRENT VIDEO
} else if (commandNoPrefix === 'current') {
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