This commit is contained in:
Chris Ham
2018-09-15 22:46:19 -07:00
parent be542c40e2
commit 7c1b93502e
3 changed files with 971 additions and 898 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -50,6 +50,10 @@
],
"defaultPlaylist": "room-grind"
},
"initialQueueSize": 10,
"videoSceneName": "fgfm",
"videoPollSize": 5,
"currentActivitySceneItemName": "now-showing-txt",
"vods": [
{
"id": "ottas-seg-escape",
@@ -61,7 +65,9 @@
"crop.left": 0,
"position.x": 320,
"scale.x": 960
}
},
"sceneItem": "4x3ph",
"length": 352
},
{
"id": "ottas-seg-eastern",
@@ -73,7 +79,9 @@
"crop.left": 0,
"position.x": 320,
"scale.x": 960
}
},
"sceneItem": "4x3ph",
"length": 277
},
{
"id": "ottas-seg-desert",
@@ -85,7 +93,9 @@
"crop.left": 0,
"position.x": 320,
"scale.x": 960
}
},
"sceneItem": "4x3ph",
"length": 345
},
{
"id": "ottas-seg-hera",
@@ -97,7 +107,9 @@
"crop.left": 0,
"position.x": 320,
"scale.x": 960
}
},
"sceneItem": "4x3ph",
"length": 299
},
{
"id": "ottas-seg-atower",
@@ -109,7 +121,9 @@
"crop.left": 0,
"position.x": 320,
"scale.x": 960
}
},
"sceneItem": "4x3ph",
"length": 352
},
{
"id": "ottas-seg-pod",
@@ -121,7 +135,9 @@
"crop.left": 0,
"position.x": 320,
"scale.x": 960
}
},
"sceneItem": "4x3ph",
"length": 309
},
{
"id": "ottas-seg-thieves",
@@ -133,7 +149,9 @@
"crop.left": 0,
"position.x": 320,
"scale.x": 960
}
},
"sceneItem": "4x3ph",
"length": 377
},
{
"id": "ottas-seg-skull",
@@ -145,7 +163,9 @@
"crop.left": 0,
"position.x": 320,
"scale.x": 960
}
},
"sceneItem": "4x3ph",
"length": 267
},
{
"id": "ottas-seg-ice",
@@ -157,7 +177,9 @@
"crop.left": 0,
"position.x": 320,
"scale.x": 960
}
},
"sceneItem": "4x3ph",
"length": 318
},
{
"id": "ottas-seg-swamp",
@@ -169,7 +191,9 @@
"crop.left": 0,
"position.x": 320,
"scale.x": 960
}
},
"sceneItem": "4x3ph",
"length": 345
},
{
"id": "ottas-seg-mire",
@@ -181,7 +205,9 @@
"crop.left": 0,
"position.x": 320,
"scale.x": 960
}
},
"sceneItem": "4x3ph",
"length": 365
},
{
"id": "ottas-seg-trock",
@@ -193,7 +219,9 @@
"crop.left": 0,
"position.x": 320,
"scale.x": 960
}
},
"sceneItem": "4x3ph",
"length": 361
},
{
"id": "ottas-seg-gtower",
@@ -205,7 +233,9 @@
"crop.left": 0,
"position.x": 320,
"scale.x": 960
}
},
"sceneItem": "4x3ph",
"length": 396
},
{
"id": "ttas-seg-ganon",
@@ -217,7 +247,9 @@
"crop.left": 0,
"position.x": 320,
"scale.x": 960
}
},
"sceneItem": "4x3ph",
"length": 108
},
{
"id": "sttas-seg-escape",
@@ -229,7 +261,9 @@
"crop.left": 0,
"position.x": 320,
"scale.x": 960
}
},
"sceneItem": "4x3ph",
"length": 354
},
{
"id": "sttas-seg-eastern",
@@ -241,7 +275,9 @@
"crop.left": 0,
"position.x": 320,
"scale.x": 960
}
},
"sceneItem": "4x3ph",
"length": 281
},
{
"id": "sttas-seg-desert",
@@ -253,7 +289,9 @@
"crop.left": 0,
"position.x": 320,
"scale.x": 960
}
},
"sceneItem": "4x3ph",
"length": 347
},
{
"id": "sttas-seg-hera",
@@ -265,7 +303,9 @@
"crop.left": 0,
"position.x": 320,
"scale.x": 960
}
},
"sceneItem": "4x3ph",
"length": 303
},
{
"id": "sttas-seg-atower",
@@ -277,7 +317,9 @@
"crop.left": 0,
"position.x": 320,
"scale.x": 960
}
},
"sceneItem": "4x3ph",
"length": 354
},
{
"id": "sttas-seg-pod",
@@ -289,7 +331,9 @@
"crop.left": 0,
"position.x": 320,
"scale.x": 960
}
},
"sceneItem": "4x3ph",
"length": 310
},
{
"id": "sttas-seg-thieves",
@@ -301,7 +345,9 @@
"crop.left": 0,
"position.x": 320,
"scale.x": 960
}
},
"sceneItem": "4x3ph",
"length": 379
},
{
"id": "sttas-seg-skull",
@@ -313,7 +359,9 @@
"crop.left": 0,
"position.x": 320,
"scale.x": 960
}
},
"sceneItem": "4x3ph",
"length": 270
},
{
"id": "sttas-seg-ice",
@@ -325,7 +373,9 @@
"crop.left": 0,
"position.x": 320,
"scale.x": 960
}
},
"sceneItem": "4x3ph",
"length": 321
},
{
"id": "sttas-seg-swamp",
@@ -337,7 +387,9 @@
"crop.left": 0,
"position.x": 320,
"scale.x": 960
}
},
"sceneItem": "4x3ph",
"length": 351
},
{
"id": "sttas-seg-mire",
@@ -349,7 +401,9 @@
"crop.left": 0,
"position.x": 320,
"scale.x": 960
}
},
"sceneItem": "4x3ph",
"length": 370
},
{
"id": "sttas-seg-trock",
@@ -361,7 +415,9 @@
"crop.left": 0,
"position.x": 320,
"scale.x": 960
}
},
"sceneItem": "4x3ph",
"length": 364
},
{
"id": "sttas-seg-gtower",
@@ -373,7 +429,9 @@
"crop.left": 0,
"position.x": 320,
"scale.x": 960
}
},
"sceneItem": "4x3ph",
"length": 408
},
{
"id": "nmg-gold-escape",
@@ -385,7 +443,9 @@
"crop.left": 0,
"position.x": 320,
"scale.x": 960
}
},
"sceneItem": "4x3ph",
"length": 358
},
{
"id": "nmg-gold-eastern",
@@ -397,7 +457,9 @@
"crop.left": 0,
"position.x": 320,
"scale.x": 960
}
},
"sceneItem": "4x3ph",
"length": 302
},
{
"id": "nmg-gold-desert",
@@ -409,7 +471,9 @@
"crop.left": 0,
"position.x": 320,
"scale.x": 960
}
},
"sceneItem": "4x3ph",
"length": 374
},
{
"id": "nmg-gold-hera",
@@ -421,7 +485,9 @@
"crop.left": 0,
"position.x": 320,
"scale.x": 960
}
},
"sceneItem": "4x3ph",
"length": 332
},
{
"id": "nmg-gold-atower",
@@ -433,7 +499,9 @@
"crop.left": 0,
"position.x": 320,
"scale.x": 960
}
},
"sceneItem": "4x3ph",
"length": 315
},
{
"id": "nmg-gold-pod",
@@ -445,7 +513,9 @@
"crop.left": 0,
"position.x": 320,
"scale.x": 960
}
},
"sceneItem": "4x3ph",
"length": 376
},
{
"id": "nmg-gold-thieves",
@@ -457,7 +527,9 @@
"crop.left": 0,
"position.x": 320,
"scale.x": 960
}
},
"sceneItem": "4x3ph",
"length": 432
},
{
"id": "nmg-gold-skull",
@@ -469,7 +541,9 @@
"crop.left": 0,
"position.x": 320,
"scale.x": 960
}
},
"sceneItem": "4x3ph",
"length": 328
},
{
"id": "nmg-gold-ice",
@@ -481,7 +555,9 @@
"crop.left": 0,
"position.x": 320,
"scale.x": 960
}
},
"sceneItem": "4x3ph",
"length": 382
},
{
"id": "nmg-gold-swamp",
@@ -493,7 +569,9 @@
"crop.left": 0,
"position.x": 320,
"scale.x": 960
}
},
"sceneItem": "4x3ph",
"length": 418
},
{
"id": "nmg-gold-mire",
@@ -505,7 +583,9 @@
"crop.left": 0,
"position.x": 320,
"scale.x": 960
}
},
"sceneItem": "4x3ph",
"length": 432
},
{
"id": "nmg-gold-trock",
@@ -517,7 +597,9 @@
"crop.left": 0,
"position.x": 320,
"scale.x": 960
}
},
"sceneItem": "4x3ph",
"length": 434
},
{
"id": "nmg-gold-gtower",
@@ -529,7 +611,9 @@
"crop.left": 0,
"position.x": 320,
"scale.x": 960
}
},
"sceneItem": "4x3ph",
"length": 433
},
{
"id": "nmg-gold-ganon",
@@ -541,7 +625,9 @@
"crop.left": 0,
"position.x": 320,
"scale.x": 960
}
},
"sceneItem": "4x3ph",
"length": 117
},
{
"id": "pb-100-ahp",
@@ -553,7 +639,9 @@
"crop.left": 320,
"position.x": 320,
"scale.x": 1280
}
},
"sceneItem": "16x9ph",
"length": 5322
},
{
"id": "pb-ab",
@@ -565,7 +653,9 @@
"crop.left": 320,
"position.x": 320,
"scale.x": 1280
}
},
"sceneItem": "16x9ph",
"length": 4730
},
{
"id": "pb-ad",
@@ -577,7 +667,9 @@
"crop.left": 320,
"position.x": 320,
"scale.x": 1280
}
},
"sceneItem": "16x9ph",
"length": 5084
},
{
"id": "pb-any-nmg",
@@ -589,7 +681,9 @@
"crop.left": 320,
"position.x": 320,
"scale.x": 1280
}
},
"sceneItem": "16x9ph",
"length": 5715
},
{
"id": "pb-any-no-eg",
@@ -601,7 +695,9 @@
"crop.left": 320,
"position.x": 320,
"scale.x": 1280
}
},
"sceneItem": "16x9ph",
"length": 2352
},
{
"id": "pb-master-sword",
@@ -613,7 +709,9 @@
"crop.left": 320,
"position.x": 320,
"scale.x": 1280
}
},
"sceneItem": "16x9ph",
"length": 1349
},
{
"id": "pb-ms",
@@ -625,7 +723,9 @@
"crop.left": 320,
"position.x": 320,
"scale.x": 1280
}
},
"sceneItem": "16x9ph",
"length": 3068
},
{
"id": "pb-ms-no-eg",
@@ -637,7 +737,9 @@
"crop.left": 320,
"position.x": 320,
"scale.x": 1280
}
},
"sceneItem": "16x9ph",
"length": 738
},
{
"id": "pb-rbo",
@@ -649,7 +751,9 @@
"crop.left": 320,
"position.x": 320,
"scale.x": 1280
}
},
"sceneItem": "16x9ph",
"length": 5250
}
],
"vodsToCrop": [

148
twitch.js
View File

@@ -258,93 +258,53 @@ const twitchInit = (config, obs) => {
const streamInit = (config, obs, twitch) => {
return new Promise((resolve, reject) => {
console.log(`Setting up initial video queue...`);
// @TODO: Choose X random vods to start
// Shuffle the vods and pick the first X
let videoQueue = config.vods.sort( function() { return 0.5 - Math.random() } ).slice(0, 5);
console.log(`Initial queue: ${videoQueue.map((c, i) => `[${i+1}] ${c.chatName}`).join(' | ')}`);
// @TODO: Load the first vod into the source, show it, listen to event of it being done, load the next, etc.
// @TODO: Switch to fg.fm scene unless it's already active
// @TODO: change the item properties (file path, transformation) then show it
let defaultSceneItemProperties = {
"position.x": 0,
"position.y": 0,
"position.alignment": 0,
"rotation": 0.0,
"crop.top": 0,
"crop.bottom": 0,
"crop.left": 0,
"crop.right": 0,
"scale.x": 1280,
"scale.y": 720
};
let videoQueue = config.vods.sort( function() { 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;
console.log(`First video up: ${currentVideo.chatName}`);
console.log(Object.assign({"item": "placeholder", "visible": true}, defaultSceneItemProperties, currentVideo.sceneItemProperties));
const showVideo = video => {
// set the file path
obs.setSourceSettings({"sourceName": video.sceneItem, "sourceSettings": {"local_file": video.filePath}})
.then(data => {
// show the video
return obs.setSceneItemProperties({"item": video.sceneItem, "scene-name": config.videoSceneName, "visible": true});
})
.then(data => {
// 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});
} else {
return obs.setSceneItemProperties({"item": config.currentActivitySceneItemName, "scene-name": config.videoSceneName, "visible": false});
}
})
.then(data => {
// 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);
});
}, video.length*1000)
})
.catch(console.error);
};
obs.setSourceSettings({"sourceName": "placeholder", "sourceSettings": {"local_file": currentVideo.filePath}})
.then(data => {
let props = Object.assign({"item": "placeholder", "visible": true}, defaultSceneItemProperties, currentVideo.sceneItemProperties);
console.log(props);
obs.setSceneItemProperties(props)
.then(res => {
// Update activity label
setTimeout(() => {
if (currentVideo.label !== false) {
twitch.editorChat.say(twitchChannel, `!setactivity ${currentVideo.label}`);
twitch.editorChat.say(twitchChannel, `$show current-activity`);
} else {
twitch.editorChat.say(twitchChannel, `$hide current-activity`);
}
}, 5000);
})
})
.catch(console.error);
obs.onSceneItemVisibilityChanged(data => {
if (data['item-name'] === 'placeholder' && data['item-visible'] === false) {
currentVideo = videoQueue.shift();
obs.setSourceSettings({"sourceName": "placeholder", "sourceSettings": {"local_file": currentVideo.filePath}})
.then(data => {
let props = Object.assign({"item": "placeholder", "visible": true}, defaultSceneItemProperties, currentVideo.sceneItemProperties);
console.log(props);
obs.setSceneItemProperties(props);
// Update activity label
if (currentVideo.label !== false) {
twitch.editorChat.say(twitchChannel, `!setactivity ${currentVideo.label}`);
twitch.editorChat.say(twitchChannel, `$show current-activity`);
} else {
twitch.editorChat.say(twitchChannel, `$hide current-activity`);
}
})
.catch(console.error);
}
});
console.log(`Showing first video: ${currentVideo.chatName}`);
showVideo(currentVideo);
console.log(`Initializing stream timers...`);
// When: Every 4 hours at 55 past
// @TODO: change to a random interval of time!
// What: AUW
let auwJob = schedule.scheduleJob("55 */4 * * *", () => {
// AUW
//twitch.editorChat.say(twitchChannel, `${config.twitch.cmdPrefix}auw`);
});
//console.log(`AUW is scheduled to be shown at ${auwJob.nextInvocation()}`);
let userVotes = currentChoices = [];
let rockTheVote = () => {};
let rtvInterval = false;
let rtvInterval = setInterval(() => {rockTheVote()}, 300000);
let videoVoteJob = schedule.scheduleJob("*/15 * * * *", () => {
// Tally votes from previous election (if there was one), add the winner to the queue
let winner;
if (currentChoices.length > 0)
{
if (userVotes.length === 0)
{
if (currentChoices.length > 0) {
if (userVotes.length === 0) {
// choose a random element from currentChoices
winner = util.randElement(currentChoices);
console.log(`VIDEO CHOSEN RANDOMLY: ${winner.chatName}`);
@@ -386,27 +346,23 @@ const streamInit = (config, obs, twitch) => {
// choose more random videos from config.vods (that aren't already in the queue)
let vodsNotInQueue = config.vods.filter(e => {
let inQueue = videoQueue.findIndex(q => q.id === e.id) !== -1;
console.log(`search results for ${e.id} in queue:`, inQueue);
return !inQueue;
});
currentChoices = vodsNotInQueue.sort( function() { return 0.5 - Math.random() } ).slice(0, 5);
currentChoices = vodsNotInQueue.sort( function() { return 0.5 - Math.random() } ).slice(0, config.videoPollSize);
// post choices to chat + set reminders to post every 5 minutes
// Poll the chat
let chatChoices = currentChoices.map((c, i) => {
return `[${i+1}] ${c.chatName}`;
});
rockTheVote = () => {
console.log(`Vote for which video you'd like to add to the queue using ${config.twitch.cmdPrefix}vote #: ${chatChoices.join(' | ')}`);
twitch.botChat.say(twitchChannel, `Vote for which video you'd like to add to the queue using ${config.twitch.cmdPrefix}vote #: ${chatChoices.join(' | ')}`)
};
//rockTheVote();
clearInterval(rtvInterval);
rockTheVote();
rtvInterval = setInterval(() => {rockTheVote()}, 300000);
});
if (!rtvInterval) {
rtvInterval = setInterval(rockTheVote, 300000);
}
// Track user votes for video queue
twitch.botChat.addListener('message', (from, to, message) => {
// Ignore everything from blacklisted users
@@ -416,6 +372,18 @@ const streamInit = (config, obs, twitch) => {
if (message.startsWith(config.twitch.cmdPrefix)) {
let commandParts = message.slice(config.twitch.cmdPrefix.length).split(' ');
let commandNoPrefix = commandParts[0] || '';
if (config.twitch.admins.includes(from) || from === config.twitch.username.toLowerCase()) {
if (commandNoPrefix === 'skip') {
clearTimeout(videoTimer);
obs.setSceneItemProperties({"item": currentVideo.sceneItem, "scene-name": config.videoSceneName, "visible": false})
.then(res => {
currentVideo = videoQueue.shift();
showVideo(currentVideo);
});
}
}
if (commandNoPrefix === 'vote') {
let userVote = commandParts[1] || false;
@@ -445,10 +413,14 @@ const streamInit = (config, obs, twitch) => {
twitch.botChat.say(to, `@${from}, your vote has been logged!`);
}
} else if (commandNoPrefix === 'queue') {
let chatQueue = videoQueue.map((c, i) => {
return `[${i+1}] ${c.chatName}`;
});
twitch.botChat.say(to, chatQueue.join(' | '));
if (videoQueue.length > 0) {
let chatQueue = videoQueue.map((c, i) => {
return `[${i+1}] ${c.chatName}`;
});
twitch.botChat.say(to, chatQueue.join(' | '));
} else {
twitch.botChat.say(to, `No videos currently in queue!`);
}
} else if (commandNoPrefix === 'current') {
twitch.botChat.say(to, `Now Playing: ${currentVideo.chatName}`);
}