If a second ring came in, the old callbacks would clear. If this happened after the first part of the animation (likely), the callback counter would never hit its target.
208 lines
5.9 KiB
HTML
208 lines
5.9 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en" onclick="document.getElementById('unmute').style.display = 'none';">
|
|
<head>
|
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
|
<title>Fáfnir Doorbell</title>
|
|
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha256-4+XzXVhsDmqanXGHaHvgh1gMQKX40OUvDEBTu8JcmNs=" crossorigin="anonymous"></script>
|
|
<style>
|
|
body {
|
|
background-color: #33194d;
|
|
color: white;
|
|
}
|
|
|
|
.flash {
|
|
--flash-half-count: 60;
|
|
--flash-half-duration: .5s;
|
|
animation-delay: 0s,
|
|
var(--flash-half-duration),
|
|
calc( (var(--flash-half-count) - 1) * var(--flash-half-duration) );
|
|
animation-duration: var(--flash-half-duration);
|
|
animation-name: fade, flashing, fade;
|
|
animation-timing-function: ease-in, ease-in-out, ease-in;
|
|
animation-iteration-count: 1, calc(var(--flash-half-count) - 2), 1;
|
|
animation-direction: normal, alternate, reverse;
|
|
animation-play-state: paused;
|
|
}
|
|
|
|
@keyframes fade {
|
|
from {
|
|
background-color: #33194d;
|
|
}
|
|
|
|
to {
|
|
background-color: orange;
|
|
}
|
|
}
|
|
|
|
@keyframes flashing {
|
|
from {
|
|
background-color: orange;
|
|
}
|
|
|
|
to {
|
|
background-color: rebeccapurple;
|
|
}
|
|
}
|
|
|
|
#status_box {
|
|
background-color: black;
|
|
margin: 3em auto;
|
|
width: 30%;
|
|
border-style: ridge;
|
|
border-color: yellow;
|
|
border-width: 5px;
|
|
padding: 15px;
|
|
text-align: center;
|
|
}
|
|
|
|
#status_box table {
|
|
text-align: left;
|
|
}
|
|
|
|
#status_box th {
|
|
padding-right: 2em;
|
|
}
|
|
|
|
.status {
|
|
font-family: monospace;
|
|
font-size: 250%;
|
|
font-variant: small-caps;
|
|
}
|
|
|
|
.connected {
|
|
color: green;
|
|
}
|
|
|
|
.disconnected {
|
|
color: red;
|
|
}
|
|
|
|
#unmute {
|
|
display: none;
|
|
min-width: 60%;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body class="flash">
|
|
<section id="status_box">
|
|
<table>
|
|
<tr>
|
|
<th>Status:</th>
|
|
<td id="status_line"></td>
|
|
</tr><tr>
|
|
<th>Last Ring:</th>
|
|
<td id="last_ring" class="status"></td>
|
|
</tr>
|
|
</table>
|
|
<button id="unmute">Click Anywhere To Enable Sound</button>
|
|
</section>
|
|
|
|
<audio id="chime" src="indoor.mp3"></audio>
|
|
<audio id="beep_seek" preload="auto" src="disconnected.mp3"></audio>
|
|
<audio id="beep_conn" src="connected.mp3"></audio>
|
|
<template id="connected">
|
|
<span class="status connected">Connected</span>
|
|
</template>
|
|
<template id="disconnected">
|
|
<span class="status disconnected">Disconnected</span>
|
|
</template>
|
|
<script>
|
|
"use strict";
|
|
|
|
const CONNECT_TIMEOUT = 1_000;
|
|
const KEEPALIVE_TIMEOUT = 10_000;
|
|
|
|
let sse;
|
|
let reconnect;
|
|
let ping;
|
|
|
|
document.getElementById('beep_seek').load();
|
|
connect();
|
|
function connect() {
|
|
console.log('connecting');
|
|
set_status('disconnected');
|
|
if (sse) {
|
|
sse.onopen = undefined;
|
|
sse.close();
|
|
}
|
|
if (ping) { clearTimeout(ping); }
|
|
sse = new EventSource('events');
|
|
let beep_seek = document.getElementById('beep_seek');
|
|
beep_seek.play().catch(() => { $('#unmute').show() }); // Need user interatction for sound
|
|
|
|
sse.onopen = () => {
|
|
console.log('connected');
|
|
set_status('connected');
|
|
let beep_conn = document.getElementById('beep_conn');
|
|
beep_conn.play();
|
|
clearTimeout(reconnect);
|
|
keepalive();
|
|
|
|
sse.onerror = () => {
|
|
console.log('sse error');
|
|
connect();
|
|
};
|
|
|
|
sse.addEventListener('ping', msg => {
|
|
clearTimeout(ping);
|
|
keepalive();
|
|
});
|
|
|
|
sse.addEventListener('ring', msg => {
|
|
let chime = document.getElementById('chime');
|
|
if (chime.paused) {
|
|
chime.play();
|
|
}
|
|
|
|
let now = new Date();
|
|
let hours = now.getHours();
|
|
let hours12 = (hours + 11) % 12 + 1;
|
|
let mins = now.getMinutes();
|
|
mins = (mins < 10) ? '0' + mins : mins;
|
|
let secs = now.getSeconds();
|
|
secs = (secs < 10) ? '0' + secs : secs;
|
|
let demidiemity = (hours / 12)|0 ? 'PM' : 'AM';
|
|
$('#last_ring').text(`${hours12}:${mins}:${secs} ${demidiemity}`);
|
|
|
|
// CSS Animations don't provide a way to restart them.
|
|
// Instead, we can temporarily override the animation property to 'none' then the
|
|
// animation will restart when we re-inheirit from the stylesheet.
|
|
let flashers = $('.flash').each((_, e) => {
|
|
if (e.style.animationPlayState === 'running') return;
|
|
e.style.animation = '';
|
|
e.style.animationPlayState = 'running';
|
|
|
|
let end_count = 0; // Each sub-animation fires its own event
|
|
let $e = $(e);
|
|
$e.off();
|
|
$e.on('animationend',
|
|
ev => { if (++end_count == 3 ) ev.target.style.animation = 'none paused'});
|
|
});
|
|
});
|
|
};
|
|
|
|
reconnect = setTimeout(connect, CONNECT_TIMEOUT);
|
|
}
|
|
|
|
function get_template(name) {
|
|
return $('#' + name).map((_, n) => n.content).clone();
|
|
}
|
|
|
|
function keepalive() {
|
|
ping = setTimeout(() => {
|
|
console.warn('keepalive ping timed out');
|
|
connect();
|
|
}, KEEPALIVE_TIMEOUT);
|
|
}
|
|
|
|
function set_status(status_template) {
|
|
const template = get_template(status_template);
|
|
const status_line = $('#status_line');
|
|
status_line.empty();
|
|
status_line.append(template);
|
|
}
|
|
|
|
</script>
|
|
</body>
|
|
</html>
|