diff --git a/web/app/src/spieler.ts b/web/app/src/spieler.ts index 9038943..74c52fe 100644 --- a/web/app/src/spieler.ts +++ b/web/app/src/spieler.ts @@ -763,17 +763,59 @@ export function spieler(anfrage: Spieleranfrage): void { zeigeFehler(videofehler()); }; - zeitleiste.oninput = () => { stand.textContent = dauer(Number(zeitleiste.value)); }; - zeitleiste.onchange = () => void springen(Number(zeitleiste.value)); + /** Ob der Regler gerade dem Zeiger gehört und nicht der Uhr. */ + let amRegler = false; + /** Wohin gesprungen wurde — bis der Spieler dort angekommen ist. */ + let sprungziel: number | null = null; + let sprungAb = 0; + + zeitleiste.oninput = () => { + // Auch Tastatur und Finger kommen hier an, nicht nur die Maus. + amRegler = true; + stand.textContent = dauer(Number(zeitleiste.value)); + }; + zeitleiste.onchange = () => { + amRegler = false; + const ziel = Number(zeitleiste.value); + sprungziel = ziel; + sprungAb = performance.now(); + void springen(ziel); + }; + // Ein abgebrochener Zeiger meldet kein `change`. Ohne das bliebe der + // Regler stehen, bis jemand ihn wieder anfasst. + zeitleiste.onpointercancel = () => { amRegler = false; }; /** Die Gesamtlänge des Films — nicht die des Stroms. */ const gesamt = anfrage.laenge ?? 0; function zeitAuffrischen(): void { - const jetzt = filmzeit(); + let jetzt = filmzeit(); + + // **Der Regler gehört dem Zeiger, nicht der Uhr.** + // + // `timeupdate` kommt viermal je Sekunde und schrieb `value` jedes Mal + // neu — auch mitten im Ziehen. Der Knopf sprang dadurch unter dem + // Finger an die laufende Stelle zurück, und nur wer festhielt und + // gegenhielt, kam irgendwo an. Gleich danach dasselbe noch einmal: ein + // Sprung braucht einen Augenblick, und in dem meldet der Spieler + // weiter die alte Stelle. Ungeprüft übernommen zieht das den Regler + // zurück, kurz bevor er nach vorn springt. + if (amRegler) { + jetzt = Number(zeitleiste.value); + } else if (sprungziel !== null) { + // Die Notbremse: landet der Sprung nie — weil der Strom neu gebaut + // wird und woanders herauskommt —, bliebe die Leiste sonst für + // immer stehen. + if (Math.abs(jetzt - sprungziel) < 1.5 || performance.now() - sprungAb > 30_000) { + sprungziel = null; + } else { + jetzt = sprungziel; + } + } + const laenge = gesamt > 0 ? gesamt : vorlauf + (Number.isFinite(video.duration) ? video.duration : 0); zeitleiste.max = String(Math.max(1, laenge)); - zeitleiste.value = String(jetzt); + if (!amRegler) zeitleiste.value = String(jetzt); stand.textContent = dauer(jetzt); rest.textContent = laenge > jetzt ? `−${dauer(laenge - jetzt)}` : '';