Initial commit

This commit is contained in:
Trysdyn Black 2022-08-29 13:02:39 -07:00
commit a6f48c3e1a
10 changed files with 760 additions and 0 deletions

124
img/16x9.css Normal file
View file

@ -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;
}

BIN
img/16x9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

128
img/4x3.css Normal file
View file

@ -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;
}

BIN
img/4x3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 KiB

BIN
img/RobotoMono-Regular.ttf Normal file

Binary file not shown.

118
img/main.css Normal file
View file

@ -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;
}

BIN
img/speaker.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 718 B

160
js/live.js Normal file
View file

@ -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);

62
js/timer.js Normal file
View file

@ -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)

168
live.html Normal file
View file

@ -0,0 +1,168 @@
<!DOCTYPE html>
<html>
<head>
<script src='https://player.twitch.tv/js/embed/v1.js'></script>
<script src='js/live.js'></script>
<script src='js/timer.js'></script>
<link rel='stylesheet' type='text/css' href='img/main.css' />
<link id='css_4x3' rel='stylesheet' type='text/css' href='img/4x3.css' />
<link id='css_16x9' rel='stylesheet' type='text/css' href='img/16x9.css' disabled />
</head>
<body>
<div id='livedisplay'>
<img class='backdrop' id='backdrop_4x3' src='img/4x3.png' />
<img class='backdrop' id='backdrop_16x9' src='img/16x9.png' />
<div id='game'>Game Name</div>
<div class='ratname' id='rat0name'>Labrat 1</div>
<div class='ratname' id='rat1name'>Labrat 2</div>
<div class='ratname' id='rat2name'>Labrat 3</div>
<div class='ratname' id='rat3name'>Labrat 4</div>
<div class='ratpronouns' id='rat0pronouns'>they/them</div>
<div class='ratpronouns' id='rat1pronouns'>they/them</div>
<div class='ratpronouns' id='rat2pronouns'>they/them</div>
<div class='ratpronouns' id='rat3pronouns'>they/them</div>
<img class='ratspeaker' id='rat0speaker' src='img/speaker.png' />
<img class='ratspeaker' id='rat1speaker' src='img/speaker.png' />
<img class='ratspeaker' id='rat2speaker' src='img/speaker.png' />
<img class='ratspeaker' id='rat3speaker' src='img/speaker.png' />
<div id='scientist'>Lead Scientist:</div>
<div id='timer'>0:00:00</div>
<div class='embed' id='embed0'></div>
<div class='embed' id='embed1'></div>
<div class='embed' id='embed2'></div>
<div class='embed' id='embed3'></div>
</div>
<div class='controls'>
<table>
<tr>
<!-- Global Controls -->
Overlay:
<button type='button' name='go_4x3' onclick='set_state(false);'>4x3</button>
<button type='button' name='go_16x0' onclick='set_state(true);'>16x9</button>
<td>
<h3>Global Settings</h3>
<p>
<label for='gamename_text'>Game Name</label>
<input id='gamename_text' type='text' />
</p>
<p>
<label for='scientist_text'>Scientist</label>
<input id='scientist_text' type='text' />
</p>
<p>
<label></label>
<button type='button' name='update_global' onclick='update_globals();'>Update</button>
<button type='button' name='update_all' onclick='update_all();'>Update All</button>
</p>
</td>
<!-- Timer Controls -->
<td>
<h3>Timer Controls</h3>
<p>
<input type='number' id='timer_h' min=0 max=99 size=2 value=1 /> H
<input type='number' id='timer_m' min=0 max=59 size=2 value=0 /> M
<input type='number' id='timer_s' min=0 max=59 size=2 value=0 /> S
</p>
<p>
<button type='button' name='timer_reset' onclick='reset_timer();'>Reset</button>
<button type='button' name='timer_start' onclick='start_timer();'>Start</button>
<button type='button' name='timer_reset' onclick='stop_timer();'>Stop</button>
</p>
</td>
</tr>
<tr>
<!-- Top Left Control -->
<td>
<h3>Top-Left Stream</h3>
<p>
<label for='embed0_stream'>Twitch Stream</label>
<input id='embed0_stream' type='text' />
</p>
<p>
<label for='embed0_name'>Labrat Name</label>
<input id='embed0_name' type='text' />
</p>
<p>
<label for='embed0_pronouns'>Labrat Pronouns</label>
<input id='embed0_pronouns' type='text' />
</p>
<p>
<label></label>
<button type='button' name='update' onclick='update_embed(0);'>Update</button>
<button type='button' name='dump' onclick='dump_stream(0);'>Dump</button>
<button type='button' name='focus' onclick='focus_audio(0);'>Audio</button>
</p>
</td>
<!-- Top Right Control -->
<td>
<h3>Top-Right Stream</h3>
<p>
<label for='embed1_stream'>Twitch Stream</label>
<input id='embed1_stream' type='text' />
</p>
<p>
<label for='embed1_name'>Labrat Name</label>
<input id='embed1_name' type='text' />
</p>
<p>
<label for='embed1_pronouns'>Labrat Pronouns</label>
<input id='embed1_pronouns' type='text' />
</p>
<p>
<label></label>
<button type='button' name='update' onclick='update_embed(1);'>Update</button>
<button type='button' name='dump' onclick='dump_stream(1);'>Dump</button>
<button type='button' name='focus' onclick='focus_audio(1);'>Audio</button>
</p>
</td>
</tr>
<tr>
<!-- Bottom Left Control -->
<td>
<h3>Bottom-Left Stream</h3>
<p>
<label for='embed2_stream'>Twitch Stream</label>
<input id='embed2_stream' type='text' />
</p>
<p>
<label for='embed2_name'>Labrat Name</label>
<input id='embed2_name' type='text' />
</p>
<p>
<label for='embed2_pronouns'>Labrat Pronouns</label>
<input id='embed2_pronouns' type='text' />
</p>
<p>
<label></label>
<button type='button' name='update' onclick='update_embed(2);'>Update</button>
<button type='button' name='dump' onclick='dump_stream(2);'>Dump</button>
<button type='button' name='focus' onclick='focus_audio(2);'>Audio</button>
</p>
</td>
<!-- Bottom Right Control -->
<td>
<h3>Bottom-Right Stream</h3>
<p>
<label for='embed3_stream'>Twitch Stream</label>
<input id='embed3_stream' type='text' />
</p>
<p>
<label for='embed3_name'>Labrat Name</label>
<input id='embed3_name' type='text' />
</p>
<p>
<label for='embed3_pronouns'>Labrat Pronouns</label>
<input id='embed3_pronouns' type='text' />
</p>
<p>
<label></label>
<button type='button' name='update' onclick='update_embed(3);'>Update</button>
<button type='button' name='dump' onclick='dump_stream(3);'>Dump</button>
<button type='button' name='focus' onclick='focus_audio(3);'>Audio</button>
</p>
</td>
</tr>
</table>
</div>
</body>
</html>