xaostube/src/Player.svelte

199 lines
4.2 KiB
Svelte

<script>
import { Link, useNavigate } from "svelte-navigator";
import { onMount } from "svelte";
import Spinner from "./components/Spinner.svelte";
const navigate = useNavigate();
export let audioId;
export let videoId;
// @ts-ignore
let ytReady = Boolean(window.YT);
let videoUrl = "";
let audioUrl = "";
let videoReady = false;
let audioReady = false;
let videoEl;
let audioEl;
let videoPlayer;
let audioPlayer;
function extract(url) {
const match = url.match(/(youtu\.be\/|v=)([\w-]+)/);
if (match) {
return match[2];
} else {
return "???";
}
}
function synchronize(what) {
if (what == "audio") {
audioPlayer.seekTo(videoPlayer.getCurrentTime(), true);
} else {
videoPlayer.seekTo(audioPlayer.getCurrentTime(), true);
}
}
async function init() {
const currentResult = await fetch("grid.php");
if (!currentResult.ok) {
alert(currentResult.status);
return;
}
const current = await currentResult.json();
videoUrl = current.find((link) => link.id == videoId).url;
audioUrl = current.find((link) => link.id == audioId).url;
videoEl.innerHTML = "";
let videoPlayerEl = document.createElement("div");
videoEl.appendChild(videoPlayerEl);
// @ts-ignore
videoPlayer = new YT.Player(videoPlayerEl, {
videoId: extract(videoUrl),
events: {
onReady: () => {
videoReady = true;
},
onStateChange: (ev) => {
// Paused
if (ev.data == 2) {
synchronize("audio");
}
},
},
});
audioEl.innerHTML = "";
let audioPlayerEl = document.createElement("div");
audioEl.appendChild(audioPlayerEl);
// @ts-ignore
audioPlayer = new YT.Player(audioPlayerEl, {
videoId: extract(audioUrl),
events: {
onReady: () => {
audioReady = true;
},
onStateChange: (ev) => {
// Paused
if (ev.data == 2) {
synchronize("video");
}
},
},
});
}
onMount(() => {
const tag = document.createElement("script");
tag.src = "https://www.youtube.com/iframe_api";
const firstScriptTag = document.getElementsByTagName("script")[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
// @ts-ignore
window.onYouTubeIframeAPIReady = () => {
ytReady = true;
};
});
$: {
audioId;
videoId;
if (ytReady) {
init();
}
}
function go() {
videoPlayer.playVideo();
videoPlayer.mute();
audioPlayer.playVideo();
}
async function onBroken(id) {
const result = await fetch(`grid.php?id=${id}`, {
method: "DELETE",
});
if (result.ok) {
alert(`ID ${id} marked as broken, thanks!`);
navigate("/");
} else {
alert(`ERR: ${result.status} ${result.statusText}`);
}
}
</script>
<main>
<a
class="link"
class:disabled={!videoReady || !audioReady}
href="javascript:;"
on:click={go}>START</a
>
<div id="player">
<h2 title={videoUrl}>Video</h2>
{#if !videoReady}<Spinner />{/if}
<div id="videoPlayer" bind:this={videoEl} />
<a class="broken" href="javascript:;" on:click={() => onBroken(videoId)}
>broken?</a
>
<h2 title={audioUrl}>Audio</h2>
{#if !audioReady}<Spinner />{/if}
<div id="audioPlayer" bind:this={audioEl} />
<a class="broken" href="javascript:;" on:click={() => onBroken(audioId)}
>broken?</a
>
</div>
<Link class="link" to="/">BACK</Link>
</main>
<style>
main {
text-align: center;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
}
:global(.link) {
font-family: inherit;
font-size: inherit;
padding: 1em 2em;
margin: 2rem;
color: white;
background-color: rgba(255, 255, 255, 0.25);
border-radius: 2em;
border: 2px solid rgba(255, 255, 255, 0);
outline: none;
width: 200px;
font-variant-numeric: tabular-nums;
cursor: pointer;
text-decoration: none;
}
:global(#audioPlayer > *) {
height: 128px;
}
a {
color: red;
opacity: 0.5;
font-size: 80%;
}
.disabled {
color: gray;
cursor: not-allowed;
}
</style>