Ein Client darf einen Stand fortschreiben, nicht zerstören
Ein Film stand bei 46 Minuten; nach einem Neuladen bot die Detailseite nur
noch „Abspielen" an. Gemessen am laufenden Server, was eine einzige Meldung
„Stelle 500 s, Laufzeit 60 s" anrichtet:
Stelle 500 s -> 60 s
Laufzeit 600 s -> 60 s
gesehen nein -> ja
Weiterschauen ja -> nein
Der Server beschnitt die Stelle auf die gemeldete Laufzeit, ersetzte die
richtige Laufzeit durch sie und setzte das Häkchen. Ein Film war damit weg.
Und 60 ist keine erfundene Zahl. Wer einen gerechneten Strom abspielt, misst
an einer Playlist, die noch wächst, und sieht anfangs nur einen Stummel.
Genau den meldete die App als Laufzeit — und sie meldet beim Öffnen
ausserdem die Zeit null, bevor sie überhaupt gesprungen ist.
Drei Stellen, dieselbe Regel:
- Server: die gemessene Laufzeit schlägt die gemeldete. Die Datei zuerst,
nicht der gemerkte Wert — stünde dort schon ein Stummel, bliebe er sonst
für immer stehen. So heilt sich ein verdorbener Eintrag von selbst.
- Server: eine gemeldete Null löscht keine gemerkte Stelle. Wer wirklich
von vorn anfängt, verliert nichts: zehn Sekunden später meldet derselbe
Spieler die 10, und die zählt. Zurückspulen bleibt unberührt.
- App: dieselbe Rangfolge bei der Laufzeit, und beim Öffnen wird die
Stelle gemeldet, an der der Spieler einsteigen will, statt der Null.
Im Webspieler kam eine eigene Null dazu: bei Direktwiedergabe gibt der
Server `startSeconds: 0` zurück und der Browser springt selbst, sobald die
Metadaten da sind. Kommen sie nie, weil er mit der Datei nicht zurechtkommt,
blieb `filmzeit()` auf null — und der Rückfall aufs Umkodieren, der
ausdrücklich „an der Stelle weiter, an der es hing" meint, fing von vorn an.
Der ausstehende Sprung zählt jetzt mit.
Fünf neue Prüfungen halten die gemessenen Zahlen fest, damit die Regel nicht
wieder umkippt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
51d1db4641
commit
339a6c07d0
@@ -56,15 +56,47 @@ public static class PlaybackEndpoints
|
||||
var zustand = await Zustand(db, benutzer, id);
|
||||
if (zustand is null) return Results.NotFound();
|
||||
|
||||
// Die Laufzeit vom Client hat Vorrang: er misst an der Fassung,
|
||||
// die er gerade abspielt. Fehlt sie, hilft die Datei weiter —
|
||||
// ohne Laufzeit gäbe es keinen Anteil und damit kein „gesehen".
|
||||
var laenge = anfrage.RuntimeSeconds is { } sekunden and > 0
|
||||
? TimeSpan.FromSeconds(sekunden)
|
||||
: zustand.Runtime ?? await Dateilaufzeit(db, zustand);
|
||||
// **Die gemessene Laufzeit schlägt die gemeldete.**
|
||||
//
|
||||
// Vorher war es umgekehrt, begründet damit, der Client messe an
|
||||
// der Fassung, die er gerade abspielt. Was das anrichtet, ist
|
||||
// gemessen: eine einzige Meldung „Stelle 500 s, Laufzeit 60 s"
|
||||
// machte aus der Stelle 500 die Stelle 60, aus der Laufzeit 600
|
||||
// die Laufzeit 60, setzte das Häkchen „gesehen" und nahm den
|
||||
// Titel aus „Weiterschauen". Der Film war damit weg, und auf der
|
||||
// Detailseite stand nur noch „Abspielen".
|
||||
//
|
||||
// Und 60 ist keine erfundene Zahl. Wer einen gerechneten Strom
|
||||
// abspielt, misst an einer Playlist, die noch wächst, und sieht
|
||||
// nur, was bisher erzeugt wurde. Scheitert die Wiedergabe gleich
|
||||
// darauf, bleibt genau dieser Stummel als „Laufzeit" stehen.
|
||||
//
|
||||
// Die Datei zuerst, nicht der gemerkte Wert: stünde dort schon
|
||||
// ein verdorbener Stummel, bliebe er sonst für immer stehen. So
|
||||
// heilt sich der Eintrag bei der nächsten Meldung von selbst.
|
||||
var ausDatei = await Dateilaufzeit(db, zustand);
|
||||
var laenge =
|
||||
ausDatei is { Ticks: > 0 } ? ausDatei
|
||||
: zustand.Runtime is { Ticks: > 0 } ? zustand.Runtime
|
||||
: anfrage.RuntimeSeconds is { } sekunden and > 0
|
||||
? TimeSpan.FromSeconds(sekunden)
|
||||
: null;
|
||||
|
||||
var ergebnis = ProgressRule.Apply(
|
||||
TimeSpan.FromSeconds(anfrage.PositionSeconds), laenge, regeln);
|
||||
// **Eine gemeldete Null löscht keine gemerkte Stelle.**
|
||||
//
|
||||
// Kein Spieler steht je wirklich bei null, ausser im Augenblick
|
||||
// des Öffnens — und da hat er die Stelle, an der er anfangen
|
||||
// will, oft noch gar nicht gesetzt. Die App meldet beim Start
|
||||
// ausdrücklich Zeit null; der Browser tut es im Fenster zwischen
|
||||
// „Datei geöffnet" und „Sprung ausgeführt". Beides hat bisher
|
||||
// eine Stelle von 46 Minuten gelöscht.
|
||||
//
|
||||
// Wer wirklich von vorn anfängt, verliert dadurch nichts: zehn
|
||||
// Sekunden später meldet derselbe Spieler die 10, und die zählt.
|
||||
var gemeldet = TimeSpan.FromSeconds(anfrage.PositionSeconds);
|
||||
if (gemeldet <= TimeSpan.Zero) gemeldet = zustand.Position;
|
||||
|
||||
var ergebnis = ProgressRule.Apply(gemeldet, laenge, regeln);
|
||||
|
||||
// **Erst zählen, dann überschreiben.** Der Zuwachs ergibt sich aus
|
||||
// der alten Stelle; eine Zeile später gibt es die nicht mehr.
|
||||
|
||||
Reference in New Issue
Block a user