commit a6f48c3e1a1e85fdb0caab544b6702b966d12583 Author: Trysdyn Black Date: Mon Aug 29 13:02:39 2022 -0700 Initial commit diff --git a/img/16x9.css b/img/16x9.css new file mode 100644 index 0000000..b7db154 --- /dev/null +++ b/img/16x9.css @@ -0,0 +1,124 @@ +#backdrop_4x3 { + display: none; +} + +#backdrop_16x9 { + display: block; +} + +.ratname { + display: none +} + +.ratpronouns { + display: none +} + +.ratspeaker { + width: 32px; + height: 32px; +} + +#game { + font-size: 24px; + left: 70px; + width: 450px; + bottom: 12px; +} + +#scientist { + font-size: 24px; + right: 70px; + width: 450px; + bottom: 12px; +} + +#timer { + font-size: 38px; + left: 472px; + width: 336px; + top: 666px; +} + +#rat0name { + left: 458px; + bottom: 665px; +} + +#rat0pronouns { + left: 458px; + top: 56px; +} + +#rat0speaker { + left: 25px; + top: 5px; + display: block; +} + +#rat1name { + left: 620px; + bottom: 581px; +} + +#rat1pronouns { + left: 620px; + top: 140px; +} + +#rat1speaker { + transform: scaleX(-1); + right: 25px; + top: 5px; +} + +#rat2name { + left: 458px; + bottom: 145px; +} + +#rat2pronouns { + left: 458px; + top: 576px; +} + +#rat2speaker { + left: 25px; + bottom: 50px; +} + +#rat3name { + left: 620px; + bottom: 59px; +} + +#rat3pronouns { + left: 620px; + top: 662px; +} + +#rat3speaker { + transform: scaleX(-1); + right: 25px; + bottom: 50px; +} + +#embed0 { + left: 70px; + top: 7px; +} + +#embed1 { + left: 651px; + top: 7px; +} + +#embed2 { + left: 70px; + top: 345px; +} + +#embed3 { + left: 651px; + top: 345px; +} \ No newline at end of file diff --git a/img/16x9.png b/img/16x9.png new file mode 100644 index 0000000..b854bee Binary files /dev/null and b/img/16x9.png differ diff --git a/img/4x3.css b/img/4x3.css new file mode 100644 index 0000000..26b7995 --- /dev/null +++ b/img/4x3.css @@ -0,0 +1,128 @@ +#backdrop_4x3 { + display: block; +} + +#backdrop_16x9 { + display: none; +} + +.ratname { + width: 200px; + font-size: 22px; +} + +.ratpronouns { + width: 200px; + font-size: 18px; +} + +.ratspeaker { + width: 32px; + height: 32px; +} + +#game { + font-size: 24px; + left: 460px; + width: 360px; + bottom: 480px; + vertical-align: bottom; + line-height: 0.96em; +} + +#scientist { + font-size: 16px; + left: 490px; + width: 300px; + top: 315px; +} + +#timer { + font-size: 68px; + left: 472px; + width: 336px; + top: 232px; +} + +#rat0name { + left: 458px; + bottom: 665px; +} + +#rat0pronouns { + left: 458px; + top: 56px; +} + +#rat0speaker { + left: 459px; + top: 329px; + display: block; +} + +#rat1name { + left: 620px; + bottom: 581px; +} + +#rat1pronouns { + left: 620px; + top: 140px; +} + +#rat1speaker { + transform: scaleX(-1); + left: 790px; + top: 329px; +} + +#rat2name { + left: 458px; + bottom: 145px; +} + +#rat2pronouns { + left: 458px; + top: 576px; +} + +#rat2speaker { + left: 459px; + top: 359px; +} + +#rat3name { + left: 620px; + bottom: 59px; +} + +#rat3pronouns { + left: 620px; + top: 662px; +} + +#rat3speaker { + transform: scaleX(-1); + left: 790px; + top: 359px; +} + +#embed0 { + left: 22px; + top: 22px; +} + +#embed1 { + left: 835px; + top: 22px; +} + +#embed2 { + left: 22px; + top: 382px; +} + +#embed3 { + left: 835px; + top: 382px; +} \ No newline at end of file diff --git a/img/4x3.png b/img/4x3.png new file mode 100644 index 0000000..bb54def Binary files /dev/null and b/img/4x3.png differ diff --git a/img/RobotoMono-Regular.ttf b/img/RobotoMono-Regular.ttf new file mode 100644 index 0000000..b158a33 Binary files /dev/null and b/img/RobotoMono-Regular.ttf differ diff --git a/img/main.css b/img/main.css new file mode 100644 index 0000000..3d599d0 --- /dev/null +++ b/img/main.css @@ -0,0 +1,118 @@ +@font-face { + font-family: 'Roboto Mono Regular'; + src: url('RobotoMono-Regular.ttf') format('truetype'); +} + +html, body { + background: #222; + margin: 0px; + padding: 0px; +} + +.ratname { + z-index: 1; + color: #fff; + font-family: 'Roboto Mono Regular'; + text-align: center; + display: block; + position: absolute; + +} + +.ratpronouns { + z-index: 1; + color: #fff; + font-family: 'Roboto Mono Regular'; + text-align: center; + display: block; + position: absolute; +} + +.ratspeaker { + z-index: 1; + position: absolute; + display: none; +} + +.embed { + z-index: 0; + display: none; + position: absolute; +} + +#game { + z-index: 1; + color: #fff; + font-family: 'Roboto Mono Regular'; + display: block; + position: absolute; + text-align: center; +} + +#scientist { + z-index: 1; + color: #fff; + font-family: 'Roboto Mono Regular'; + display: block; + position: absolute; + text-align: center; +} + +#timer { + z-index: 1; + color: #fff; + font-family: 'Roboto Mono Regular'; + font-weight: bold; + text-align: center; + display: block; + position: absolute; +} + +.backdrop { + z-index: 1; + position: relative; + pointer-events: none; + display: block; + top: 0; + left: 0; +} + +#livedisplay { + overflow: hidden; + position: relative; + width: fit-content; + top: 0px; + left: 0px; + margin: 0px; + padding: 0px; +} + +.controls { + margin-top: 20px; + color: #ccc; +} + +.controls td { + border: 1px solid; + vertical-align: top; +} + +.controls h3 { + margin: 0px; + padding: 0px; + text-align: center; +} + +.controls p { + display: table-row; +} + +.controls label { + display: table-cell; + text-align: right; +} + +.controls input, button { + display: table-cell; + margin-left: 5px; +} diff --git a/img/speaker.png b/img/speaker.png new file mode 100644 index 0000000..06c4880 Binary files /dev/null and b/img/speaker.png differ diff --git a/js/live.js b/js/live.js new file mode 100644 index 0000000..60d9e0d --- /dev/null +++ b/js/live.js @@ -0,0 +1,160 @@ +// Dimensions of each embed, used when initializing the embeds +embed_height = 315; +embed_width = 560; + +// Twitch player embed objects go here so we can address the four embeds +// with embeds[0] .. embeds[3] +embeds = []; + +// Toggles between 4x3 and 16x9 overlays by shuffling CSS stylesheets +// The boolean argument is pretty much "is this widescreen?" +function set_state(widescreen) { + css_normal = document.getElementById('css_4x3'); + css_wide = document.getElementById('css_16x9'); + if (widescreen) { + css_normal.disabled = true; + css_wide.disabled = false; + } else { + css_normal.disabled = false; + css_wide.disabled = true; + } +} + +// Update everything +function update_all() { + update_globals(); + update_embed(0); + update_embed(1); + update_embed(2); + update_embed(3); +} + +// Update all global textual elements +function update_globals() { + change_game_name(); + change_scientist(); +} + +// Update individual textual elements in the overlay +function change_game_name() { + game_name = document.getElementById('gamename_text').value; + e = document.getElementById('game'); + e.innerHTML = game_name; +} + +function change_scientist() { + scientist = document.getElementById('scientist_text').value; + e = document.getElementById('scientist'); + e.innerHTML = "Lead Scientist: " + scientist; +} + +function update_embed(embed_num) { + change_embed_channel(embed_num); + n = document.getElementById('rat' + embed_num + 'name'); + p = document.getElementById('rat' + embed_num + 'pronouns'); + + n.innerHTML = document.getElementById('embed' + embed_num + '_name').value; + p.innerHTML = document.getElementById('embed' + embed_num + '_pronouns').value; +} + +// Instruct a given embed to change what channel it's streaming +function change_embed_channel(embed_num) { + channel_name = document.getElementById('embed' + embed_num + '_stream').value; + + if (channel_name == '' || channel_name == null) { + return false; + } + + e = document.getElementById('embed' + embed_num); + e.style.display = 'block'; + + embeds[embed_num].setChannel(channel_name); + embeds[embed_num].play(); +} + +// This is a fast call to immediately halt a stream. It's used as an +// emergency "something bad is happening" button, and can just be used to +// hide one of the embeds if a player isn't available. +// This is overkill: it hides the DOM element, then forces an error to make +// the player crash. This is restorable by using change_embed_channel() +function dump_stream(embed_num) { + // Hide the element entirely + document.getElementById('embed' + embed_num).style.display = 'none'; + + // Pass an invalid channel to the player so it errors out + // This is gross but it stops all activity for certain + embeds[embed_num].setChannel(); +} + +// A pretty brute force way to make sure one, and only one, embed is playing +// audio. We mute all four embeds then immediately unmute the provided one. +// We also juggle display styling for speaker elements so only the audio source +// has a speaker displayed next to it +function focus_audio(embed_num) { + embeds.forEach(e => e.setMuted(true)); + embeds[embed_num].setMuted(false); + + speakers = document.querySelectorAll('.ratspeaker'); + speakers.forEach(e => e.style.display = 'none'); + + e = document.getElementById('rat' + embed_num + 'speaker') + e.style.display = 'block'; +} + +// This is the primary setup function that runs when the DOM is loaded +// At the moment all it does is set up embeds +function setup() { + // Setup Embeds + // These start with JST as the channel because some channel needs to be + // provided. We simply disable autoplay so we don't get four JSTs. + // When we switch embed channels, part of that function forces playing + // to start + embeds.push(new Twitch.Embed('embed0', { + height: embed_height, + width: embed_width, + autoplay: false, + muted: false, + layout: 'video', + controls: false, + channel: 'jank_science_theater', + parent: ['voidfox.com'] + })); + + embeds.push(new Twitch.Embed('embed1', { + height: embed_height, + width: embed_width, + autoplay: false, + muted: true, + layout: 'video', + controls: false, + channel: 'jank_science_theater', + parent: ['voidfox.com'] + })); + + embeds.push(new Twitch.Embed('embed2', { + height: embed_height, + width: embed_width, + autoplay: false, + muted: true, + layout: 'video', + controls: false, + channel: 'jank_science_theater', + parent: ['voidfox.com'] + })); + + embeds.push(new Twitch.Embed('embed3', { + height: embed_height, + width: embed_width, + autoplay: false, + muted: true, + layout: 'video', + controls: false, + channel: 'jank_science_theater', + parent: ['voidfox.com'] + })); + + // Try to start volume at a known level + embeds.forEach(e => e.setVolume(1.0)); +} + +document.addEventListener('DOMContentLoaded', setup); diff --git a/js/timer.js b/js/timer.js new file mode 100644 index 0000000..9fcb32d --- /dev/null +++ b/js/timer.js @@ -0,0 +1,62 @@ +// Timekeeping info for the timer +timer_start = 0; +timer_sec = 0; + +// Runs once a second to maintain the timer +// Tablutes how long it's been, subtracts that from timer length, then +// updates the HTML element representing the timer +function timer_tick() { + // Don't do anything if a timer isn't running + if (!timer_start) { + return false + } + + // Take how long it's been since we started the timer and subtract that + // from the timer duration to get how long is remaining + remain = timer_sec - ((Date.now() - timer_start) / 1000); + + // Stop at 0 + if (remain < 0) {remain = 0;} + + // Update the element + document.getElementById('timer').innerHTML = seconds_to_string(remain); +} + +// Utility function to convert seconds to H:MM:SS +function seconds_to_string(seconds) { + hours = Math.floor(seconds / 3600); + minutes = Math.floor((seconds % 3600) / 60); + seconds = Math.floor(seconds % 60); + + return String(hours) + ":" + + String(minutes).padStart(2, '0') + ":" + + String(seconds).padStart(2, '0') +} + +// Tabluates how many seconds we want on the timer, stores that info, and +// stops a currently running timer +function reset_timer() { + hours = parseInt(document.getElementById('timer_h').value); + minutes = parseInt(document.getElementById('timer_m').value); + seconds = parseInt(document.getElementById('timer_s').value); + + total = (minutes * 60) + (hours * 3600) + seconds; + + document.getElementById('timer').innerHTML = seconds_to_string(total); + + timer_start = 0; + timer_sec = total; +} + +// Starts the timer by setting 'now' as the timer start time +function start_timer() { + timer_start = Date.now() +} + +// Destroys the timer start time to halt the timer +function stop_timer() { + timer_start = 0; +} + +// Start timer interval +setInterval(timer_tick, 1000) diff --git a/live.html b/live.html new file mode 100644 index 0000000..741b92a --- /dev/null +++ b/live.html @@ -0,0 +1,168 @@ + + + + + + + + + + + +
+ + +
Game Name
+
Labrat 1
+
Labrat 2
+
Labrat 3
+
Labrat 4
+
they/them
+
they/them
+
they/them
+
they/them
+ + + + +
Lead Scientist:
+
0:00:00
+
+
+
+
+
+
+ + + + Overlay: + + + + + + + + + + + + + + + + + + +
+

Global Settings

+

+ + +

+

+ + +

+

+ + + +

+
+

Timer Controls

+

+ H + M + S +

+

+ + + +

+
+

Top-Left Stream

+

+ + +

+

+ + +

+

+ + +

+

+ + + + +

+
+

Top-Right Stream

+

+ + +

+

+ + +

+

+ + +

+

+ + + + +

+
+

Bottom-Left Stream

+

+ + +

+

+ + +

+

+ + +

+

+ + + + +

+
+

Bottom-Right Stream

+

+ + +

+

+ + +

+

+ + +

+

+ + + + +

+
+
+ +