Newer
Older
Simon Kirsten
committed
// website is the html source of the website.
// It is possible to have this in a serperate .html file and include it at compile time but for simplicity and portability we just keep it here inline.
Simon Kirsten
committed
// Also there are some Visual Studio Code plugins in development that should enable html syntax highlighting here.
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Stream TV</title>
<script src="https://player.twitch.tv/js/embed/v1.js"></script> <!-- twitch embed -->
<style>
body {
display: flex;
margin: 0;
padding: 0;
height: 100vh;
width: calc(100vw + 21.25rem); /* without chat the body is 21.25rem (the size of the chat) wider */
background-color: black;
overflow: hidden;
transition: width 0.3s; /* width changes are animated */
}
body.with-chat {
width: 100vw; /* with chat the width is the normal 100vw */
}
#large-player {
position: relative; /* while this 'relative' has no impact for #large-player it is the reference point for the #small-player-container which is positioned absolute to this element */
display: flex;
flex-grow: 1; /* #large-player fills remaining space while #chat does not */
}
#small-player-container { /* this container is necessary for the #small-player to keep an 16:9 aspect ratio */
position: absolute;
width: 100%;
height: 0;
padding-top: 56.25%; /* this is the trick that keeps the child in a 16:9 ratio - read more here https://www.w3schools.com/howto/howto_css_aspect_ratio.asp */
}
#small-player {
position: absolute;
top: 0; /* pin small player to the top right */
right: 0;
/* Note: the width and height property get set via script */
transition: width 0.3s, height 0.3s; /* width and height changes are animated */
display: flex;
}
#small-player > iframe,
#large-player > iframe { /* the iframes always grow to the parents size */
flex-grow: 1;
pointer-events: none;
}
.hidden > iframe {
display: none;
}
#overlay {
font-family: Roboto, Helvetica, Arial, sans-serif;
color: white;
/* center the content */
top: 0;
right: 0;
left: 0;
bottom: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
#overlay.hidden {
display: none;
}
#chat {
width: 21.25rem;
}
a:link, a:active, a:visited, a:hover {
color: inherit;
}
</style>
</head>
<body>
<div id="large-player" class="hidden">
<div id="small-player-container">
<div id="small-player" class="hidden"></div>
</div>
</div>
<iframe id="chat" frameborder="0" scrolling="no" src="about:blank"></iframe>
<div id="overlay">
<h2>Loading...</h2>
<p>
<a href="https://simons-nzse-2.h-da.io/stream-tv/server">Documentation</a> ·
<a href="https://simons-nzse-2.h-da.io/stream-tv/server/reference/">Reference</a>
</p>
<small>
<a href="javascript:toggleFullscreen()">toggle fullscreen</a>
</small>
</div>
<script>
var large_player;
var small_player;
function newLargePlayer(channel) {
return new Twitch.Player('large-player', {
channel: channel,
controls: false,
muted: false
});
}
function newSmallPlayer(channel) {
return new Twitch.Player('small-player', {
channel: channel,
controls: false,
muted: true
});
}
const large_player_elem = document.getElementById('large-player');
const small_player_elem = document.getElementById('small-player');
const overlay_elem = document.getElementById('overlay');
const chat_elem = document.getElementById('chat');
const defaultState = {
large_channel: '',
small_channel: '',
volume: 0.5,
small_scale: 0.3,
show_chat: false
};
let state = defaultState;
function updateState(newState) {
state = newState;
console.log("State:", state);
// large player:
// we need to destroy the player
if (state.large_channel == '' && large_player) {
large_player.destroy();
large_player = undefined;
chat_elem.src = 'about:blank';
Simon Kirsten
committed
}
// we should be playing something
// we need to create the large player
if (!large_player) {
large_player = newLargePlayer(state.large_channel);
}
// the channel needs to be changed
if (large_player.getChannel() != state.large_channel) {
large_player.setChannel(state.large_channel);
}
// the volume needs to be changed
if (large_player.getVolume() != state.volume) {
large_player.setVolume(state.volume);
}
var chat_elem_src = 'https://www.twitch.tv/embed/' + state.large_channel + '/chat?darkpopout';
if (chat_elem_src != chat_elem.src) {
chat_elem.src = chat_elem_src;
}
Simon Kirsten
committed
}
// small player:
// we need to destroy the player
if (state.small_channel == '' && small_player) {
small_player.destroy();
small_player = undefined;
}
Simon Kirsten
committed
// we should be playing something
// we need to create the small player
if (!small_player) {
small_player = newSmallPlayer(state.small_channel);
}
Simon Kirsten
committed
// the channel needs to be changed
if (small_player.getChannel() != state.small_channel) {
small_player.setChannel(state.small_channel);
}
}
Simon Kirsten
committed
const small_player_size = state.small_scale * 100 + '%';
small_player_elem.style.width = small_player_size;
small_player_elem.style.height = small_player_size;
Simon Kirsten
committed
small_player_elem.className = (state.small_channel == '') ? 'hidden' : '';
large_player_elem.className = (state.large_channel == '') ? 'hidden' : '';
overlay_elem.className = (state.large_channel != '' || state.small_channel != '') ? 'hidden' : '';
Simon Kirsten
committed
document.body.className = state.show_chat ? 'with-chat' : '';
}
Simon Kirsten
committed
if (!window.EventSource) {
overlay_elem.firstElementChild.innerText = "Your browser (probably IE / Edge) does not support EventSource. Please try any other modern browser.";
} else {
const events = new EventSource('/tv/events');
Simon Kirsten
committed
events.onmessage = m => {
updateState(JSON.parse(m.data));
};
Simon Kirsten
committed
events.onopen = () => {
overlay_elem.firstElementChild.innerText = "Nothing playing";
Simon Kirsten
committed
};
events.onerror = err => {
console.error(err);
Simon Kirsten
committed
updateState(defaultState);
overlay_elem.firstElementChild.innerText = "Connection to stream-tv-server failed. Make sure it's running and reload the page.";
};
}
function toggleFullscreen() {
if (!document.fullscreenElement) { // we are not in fullscreen
document.documentElement.requestFullscreen()
} else {
if (document.exitFullscreen) {
document.exitFullscreen();
}
}
}
large_player_elem.addEventListener('mousedown', (event) => {
if (event.detail > 1) { // double click
Simon Kirsten
committed
event.preventDefault();
Simon Kirsten
committed
}
});
Simon Kirsten
committed
</script>
Simon Kirsten
committed
</body>
Simon Kirsten
committed
// websiteHandleFunc just serves the website
func websiteHandleFunc(w http.ResponseWriter, r *http.Request) {
// The "/" pattern (in main.go) matches everything that isn't handled by somebody else, so we need to check
Simon Kirsten
committed
// that we're at the root here.
Simon Kirsten
committed
http.NotFound(w, r)
} else {
w.Header().Set("Content-Type", "text/html")
w.Write(website)
}
}
Simon Kirsten
committed
// Handler returns a http.Handler that serves requests for the / backend
Simon Kirsten
committed
return http.HandlerFunc(websiteHandleFunc)