244 lines
6.3 KiB
HTML
244 lines
6.3 KiB
HTML
<!DOCTYPE html>
|
|
<html onclick="videoplayer.muted = false">
|
|
<head>
|
|
<title>Non-Stop MMD</title>
|
|
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+Symbols+2&display=swap" rel="stylesheet" />
|
|
<style>
|
|
html {
|
|
cursor: none;
|
|
background-color: black;
|
|
}
|
|
#videoplayer {
|
|
max-width: 100%;
|
|
max-height: 100%;
|
|
transform: translate(-50%, -50%);
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
}
|
|
.mousie {
|
|
position: fixed;
|
|
user-select: none;
|
|
color: gray;
|
|
text-shadow: 1px 1px 1px black, -1px -1px 1px black, 1px -1px 1px black, -1px 1px 1px black;
|
|
}
|
|
.mousie_pointer {
|
|
font-family: "Noto Sans Symbols 2";
|
|
}
|
|
.mousie_talk {
|
|
position: absolute;
|
|
width: 300px;
|
|
bottom: 30px;
|
|
left: -150px;
|
|
text-align: center;
|
|
word-wrap: break-word;
|
|
white-space: break-spaces;
|
|
font-family: monospace;
|
|
}
|
|
.depressed {
|
|
position: relative;
|
|
top: 3px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<video id="videoplayer"></video>
|
|
<template id="mousie_template">
|
|
<div class="mousie">
|
|
<div class="mousie_pointer">🮰</div>
|
|
<div class="mousie_talk"></div>
|
|
</div>
|
|
</template>
|
|
<script>
|
|
|
|
var playlist;
|
|
|
|
async function nowPlaying() {
|
|
if (!playlist || Date.now() > playlist.timestamp + playlist.totalDuration*1000) {
|
|
playlist = await fetch("playlist").then(res => res.json());
|
|
}
|
|
for (var i = 0, pastDurations = 0; i < playlist.videos.length; i++) {
|
|
var video = playlist.videos[i];
|
|
var videoDurationMs = video.duration * 1000;
|
|
if (Date.now() < playlist.timestamp + pastDurations + videoDurationMs) {
|
|
break;
|
|
}
|
|
pastDurations += videoDurationMs;
|
|
}
|
|
return {
|
|
name: video.name,
|
|
position: (Date.now() - pastDurations - playlist.timestamp) / 1000,
|
|
source: video.source
|
|
};
|
|
}
|
|
|
|
async function sync(force) {
|
|
var {name, position} = await nowPlaying();
|
|
var src = `videos/${encodeURIComponent(name)}`;
|
|
|
|
if (!videoplayer.src.endsWith(src)) {
|
|
console.debug("change src", videoplayer.src, "to", src);
|
|
videoplayer.src = src;
|
|
videoplayer.load();
|
|
}
|
|
|
|
console.debug("desync", (videoplayer.currentTime - position) * 1000, "ms");
|
|
|
|
if (force || Math.abs(position - videoplayer.currentTime) >= 1) {
|
|
console.debug("change pos", videoplayer.currentTime, "to", position);
|
|
videoplayer.currentTime = position;
|
|
}
|
|
|
|
if (videoplayer.paused) try {
|
|
await videoplayer.play();
|
|
} catch (error) {
|
|
videoplayer.muted = true;
|
|
await videoplayer.play();
|
|
}
|
|
}
|
|
|
|
sync();
|
|
setInterval(sync, 5000);
|
|
|
|
videoplayer.addEventListener("ended", () => {
|
|
console.debug("ended");
|
|
sync();
|
|
});
|
|
</script>
|
|
|
|
<script> // mousies
|
|
var ws;
|
|
(function createWs() {
|
|
ws = new WebSocket(location.href.replace('http','ws'));
|
|
ws.binaryType = "arraybuffer";
|
|
ws.onmessage = evt => {
|
|
if (typeof evt.data == "string") {
|
|
var j = JSON.parse(evt.data);
|
|
switch (j[0]) {
|
|
case "join":
|
|
break;
|
|
case "gone":
|
|
document.getElementById("mousie"+j[1])?.remove();
|
|
break;
|
|
case "type":
|
|
updateMousie({id: j[1], txt: j[2]});
|
|
break;
|
|
}
|
|
} else {
|
|
var dv = new DataView(evt.data);
|
|
var id = dv.getUint8(0);
|
|
if (dv.byteLength == 2) {
|
|
if (dv.getUint8(1)) {
|
|
updateMousie({id, depressed: true});
|
|
} else {
|
|
updateMousie({id, depressed: false});
|
|
}
|
|
} else if (dv.byteLength == 5) {
|
|
var x = dv.getInt16(1, false);
|
|
var y = dv.getInt16(3, false);
|
|
updateMousie({id, x, y});
|
|
} else {
|
|
|
|
}
|
|
}
|
|
};
|
|
ws.onclose = () => {
|
|
setTimeout(createWs, 5000);
|
|
playlist = undefined;
|
|
[...document.getElementsByClassName('mousie')].forEach(m => m.remove());
|
|
};
|
|
})();
|
|
|
|
onmousemove = evt => {
|
|
if (ws.readyState != WebSocket.OPEN) return;
|
|
var {left, top, width, height} = videoplayer.getBoundingClientRect();
|
|
var x = (4096 * evt.pageX / width) - (4096 * left / width);
|
|
var y = (4096 * evt.pageY / height) - (4096 * top / height);
|
|
var b = new ArrayBuffer(4);
|
|
var dv = new DataView(b);
|
|
dv.setInt16(0, x, false);
|
|
dv.setInt16(2, y, false);
|
|
ws.send(b);
|
|
updateMousie({x, y});
|
|
};
|
|
|
|
function updateMousie({id = "_self", x, y, txt, depressed}) {
|
|
var mousie_element = document.getElementById("mousie"+id);
|
|
if (!mousie_element) {
|
|
mousie_element = mousie_template.content.firstElementChild.cloneNode(true);
|
|
mousie_element.id = "mousie"+id;
|
|
document.body.appendChild(mousie_element);
|
|
}
|
|
var {left, top, width, height} = videoplayer.getBoundingClientRect();
|
|
if (x) mousie_element.style.left = (x * width / 4096 + left) + "px";
|
|
if (y) mousie_element.style.top = (y * height / 4096 + top - 3) + "px";
|
|
if (txt != null) {
|
|
var talk = mousie_element.querySelector(".mousie_talk");
|
|
if (txt === "") talk.innerText = "";
|
|
else if (txt == "Backspace") talk.innerText = talk.innerText.slice(0,-1);
|
|
else if (txt == "Delete") talk.innerText = talk.innerText.slice(1);
|
|
else talk.innerText += txt;
|
|
}
|
|
if (depressed) {
|
|
mousie_element.querySelector(".mousie_pointer").classList.add("depressed");
|
|
} else if (depressed === false) {
|
|
mousie_element.querySelector(".mousie_pointer").classList.remove("depressed");
|
|
}
|
|
}
|
|
|
|
onkeypress = evt => {
|
|
if (evt.key == "Escape") {
|
|
var txt = "";
|
|
} else if (evt.key == "Enter") {
|
|
var txt = '\n';
|
|
} else {
|
|
var txt = evt.key;
|
|
}
|
|
ws.send(txt);
|
|
updateMousie({txt});
|
|
};
|
|
|
|
onkeydown = evt => {
|
|
if (["Backspace", "Delete", "Escape"].includes(evt.key)) onkeypress(evt);
|
|
};
|
|
|
|
document.documentElement.onpaste = evt => {
|
|
var txt = evt.clipboardData.getData("Text");
|
|
if (txt) {
|
|
ws.send(txt);
|
|
updateMousie({txt});
|
|
}
|
|
};
|
|
|
|
oncontextmenu = evt => {
|
|
evt.preventDefault();
|
|
nowPlaying().then(v => {
|
|
if (v.source) open(v.source, "_blank");
|
|
});
|
|
};
|
|
|
|
onauxclick = evt => {
|
|
evt.preventDefault();
|
|
sync(true);
|
|
};
|
|
|
|
onwheel = evt => {
|
|
if (evt.deltaY > 0) {
|
|
videoplayer.volume = videoplayer.volume + 0.1 > 1 ? 1 : videoplayer.volume + 0.1;
|
|
} else if (evt.deltaY < 0) {
|
|
videoplayer.volume = videoplayer.volume - 0.1 < 0 ? 0 : videoplayer.volume - 0.1;
|
|
}
|
|
};
|
|
|
|
onmousedown = evt => {
|
|
ws.send(new Uint8Array([1]));
|
|
updateMousie({depressed: true});
|
|
};
|
|
onmouseup = evt => {
|
|
ws.send(new Uint8Array([0]));
|
|
updateMousie({depressed: false});
|
|
};
|
|
|
|
</script>
|
|
</body>
|
|
</html> |