From 339a6c07d029f498ef1c8e5d6c484c471de7d507 Mon Sep 17 00:00:00 2001 From: Scarriffle Date: Wed, 7 Oct 2026 21:36:35 +0200 Subject: [PATCH] =?UTF-8?q?Ein=20Client=20darf=20einen=20Stand=20fortschre?= =?UTF-8?q?iben,=20nicht=20zerst=C3=B6ren?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- Flaro/Views/Player/PlayerModel.swift | 32 ++- scratchpad/neu/data/flaro.db | Bin 0 -> 4096 bytes scratchpad/neu/data/flaro.db-shm | Bin 0 -> 32768 bytes scratchpad/neu/data/flaro.db-wal | Bin 0 -> 968232 bytes ...y-15706be2-d1ec-4fa7-918b-920e87c4acce.xml | 16 ++ scratchpad/neu/data/logs/flaro-2026-10-07.log | 1 + .../Flaro.Api/Endpoints/PlaybackEndpoints.cs | 48 ++++- .../tests/Flaro.Api.Tests/FortschrittTests.cs | 185 ++++++++++++++++++ web/app/src/spieler.ts | 16 +- 9 files changed, 279 insertions(+), 19 deletions(-) create mode 100644 scratchpad/neu/data/flaro.db create mode 100644 scratchpad/neu/data/flaro.db-shm create mode 100644 scratchpad/neu/data/flaro.db-wal create mode 100644 scratchpad/neu/data/keys/key-15706be2-d1ec-4fa7-918b-920e87c4acce.xml create mode 100644 scratchpad/neu/data/logs/flaro-2026-10-07.log create mode 100644 server/tests/Flaro.Api.Tests/FortschrittTests.cs diff --git a/Flaro/Views/Player/PlayerModel.swift b/Flaro/Views/Player/PlayerModel.swift index 69f29f9..2998f77 100644 --- a/Flaro/Views/Player/PlayerModel.swift +++ b/Flaro/Views/Player/PlayerModel.swift @@ -1660,24 +1660,36 @@ final class PlayerModel { private func report(state: PlaybackReportState) async { guard let backend, let item else { return } - // Die Laufzeit der Engine ist die genauere, aber sie steht erst ein - // paar Sekunden nach dem Öffnen fest. Für die erste Meldung tut es die - // des Servers — sonst fiele ausgerechnet der Bericht aus, der die - // Sitzung anlegt, und der Titel erschiene nirgends als „läuft gerade". - guard let total = duration ?? item.duration.map({ Duration.milliseconds($0) }) + // **Die Laufzeit des Titels schlägt die der Engine.** + // + // Vorher war es umgekehrt, mit dem Argument, die Engine messe + // genauer. Sie misst aber am Strom und nicht am Film: bei einem + // gerechneten Strom ist das eine Playlist, die noch wächst, und + // anfangs sieht die Engine davon nur einen Stummel. Gemeldet hat sie + // ihn als Laufzeit — und daraus machte der Server die Laufzeit des + // Films: Stelle zusammengestaucht, Häkchen „gesehen" gesetzt, Titel + // aus „Weiterschauen" verschwunden. Gemessen an einem Film, der bei + // 46 Minuten stand und danach wieder bei null anfing. + guard let total = item.duration.map({ Duration.milliseconds($0) }) ?? duration else { return } - // Beim Beginn steht die Zeit noch auf null — das ist keine fehlende - // Angabe, sondern der Anfang. Alle anderen Meldungen ohne Zeit wären - // dagegen wertlos und würden einen gemerkten Fortschritt überschreiben. - guard currentTime > .zero || state == .started else { return } + // **Keine Null melden, die der Spieler gar nicht meint.** + // + // Beim Öffnen steht die Zeit der Engine auf null, obwohl der Spieler + // gleich an einer ganz anderen Stelle einsteigt. Diese Meldung darf + // nicht ausfallen — erst sie legt beim Server die Sitzung an, unter + // der der Titel als „läuft gerade" erscheint. Sie muss nur die + // Stelle nennen, die gemeint ist, statt der, die noch nicht gesetzt + // ist. + let stelle = currentTime > .zero ? currentTime : request.startTime + guard stelle > .zero || state == .started else { return } await backend.reportProgress(PlaybackReport( itemID: item.id, sessionID: playbackSession, versionID: playbackVersionID, state: state, - time: currentTime, + time: stelle, duration: total )) } diff --git a/scratchpad/neu/data/flaro.db b/scratchpad/neu/data/flaro.db new file mode 100644 index 0000000000000000000000000000000000000000..c9cf4b4e788deb2a37a4a5eef5db1c380729a4e1 GIT binary patch literal 4096 zcmWFz^vNtqRY=P(%1ta$FlG>7U}9o$P*7lCU|@t|AVoG{WYC*>nir&9fNV2HHI9bB nXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeDP#6LLLQ)5D literal 0 HcmV?d00001 diff --git a/scratchpad/neu/data/flaro.db-shm b/scratchpad/neu/data/flaro.db-shm new file mode 100644 index 0000000000000000000000000000000000000000..956044943b39cd049a8e610cced0f28e3b3f782c GIT binary patch literal 32768 zcmeI5`IpUg7{}ifibAU~$P7s$S*D0EEn_#9q*7_2MWuyAQKG#jw9vk4v!s31q9{uX zN-HX~S^fd%bWXjWj@$XV=jwj0<~z^#G0$_(>)iXf^L#$f`}@8#^W*!Ix8X=g6_6@G z-7S1Qo2(k%Z0?vT)64RUwvGR@bm+VVWn0&e8?>-~UXJ}d>YtKx|BBT1kN_he?wMiXPm+VLOCkK$|IR|om5ILCCBYC7g$tMj+ zL(+&GLK>3-Qb?MRrX+fn^#9j7b>^Y$Yeo*s(w}}_YJYR?JDjv2N064J6_IbRHHm6S z?UCN2NE_0Yv?J|F5jmP1LpqR-e3-PyB8d*s>D*RoHZRc7=gy@>QgKkv0~I$q)+yoLCz#+ zkzqvs4@A!wjq*3@-LJ{)=ikVL^5F>E~ zuEHeTh}%$x8F&nH@CM$&`}hbe@FRX#P+f|r@uI@q+(&+w`<8OuA&DI}>T+e7Rha~_ zMOj1G`2KhXFNL)#cYUuszd(6@`1otXTVv_0#tfl+=I69!7ETvt*_1F-%nj88_n&OvUSX3-4kf7Go9G;b&}$MMw5SCB4W-tx=ZQglS$q znEZ4p#$X(7!JU|f53yNURi^P1GksWrpVh3uS^sa8nkzD9K*;3UNRZ?`_n>$};6H3>zi44tRw) zAa_vMs7I7#$}KQ9>Kq#~6QHtX0yKn4coehoDwbh2)`x8<=PtSb$VQ!OLuLY0%6&~X zYPqsZx$VnFoo7R40#tTNfQB#$pN6%?)+P7c*{I<*WF|mm=LBd7lkizsTWno&!=sHF zfl;^y_uv71g0E~yR#Mpuk7KUV?A*qFo?Cmlch@WT6XQDY{J0L@?au9uCos>%Q+=VV zGEaGa`1lLLTPt~IAKqtsH}1!i_!wU%+KaE1)iqR}A3pxV@YYJ+*%wdY1rr{8qpY%# O^8E1e7lpUR()l-6aJ3Hr literal 0 HcmV?d00001 diff --git a/scratchpad/neu/data/flaro.db-wal b/scratchpad/neu/data/flaro.db-wal new file mode 100644 index 0000000000000000000000000000000000000000..1536221aa0d297b090345cbf73e9e2ce48c1a152 GIT binary patch literal 968232 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 z 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 w5kLeG0Yu + + 2026-10-07T19:35:00.68989Z + 2026-10-07T19:35:00.68989Z + 2027-01-05T19:35:00.68989Z + + + + + + + oyFqIl0Q27Th3X+CX07TX95s7kMGEQ2OkjOUJEJVkgK7LeTccP4tp3FeJnMzLSuu7rsKVIJa4xINONp+0AOAWg== + + + + \ No newline at end of file diff --git a/scratchpad/neu/data/logs/flaro-2026-10-07.log b/scratchpad/neu/data/logs/flaro-2026-10-07.log new file mode 100644 index 0000000..98b18bf --- /dev/null +++ b/scratchpad/neu/data/logs/flaro-2026-10-07.log @@ -0,0 +1 @@ +21:35:00 INFO Server gestartet — 1.0.0 · DEV diff --git a/server/src/Flaro.Api/Endpoints/PlaybackEndpoints.cs b/server/src/Flaro.Api/Endpoints/PlaybackEndpoints.cs index 22f2541..27d8357 100644 --- a/server/src/Flaro.Api/Endpoints/PlaybackEndpoints.cs +++ b/server/src/Flaro.Api/Endpoints/PlaybackEndpoints.cs @@ -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. diff --git a/server/tests/Flaro.Api.Tests/FortschrittTests.cs b/server/tests/Flaro.Api.Tests/FortschrittTests.cs new file mode 100644 index 0000000..70db830 --- /dev/null +++ b/server/tests/Flaro.Api.Tests/FortschrittTests.cs @@ -0,0 +1,185 @@ +using System.Net.Http.Json; +using Flaro.Api.Endpoints; + +namespace Flaro.Api.Tests; + +/// Was eine Fortschrittsmeldung am gemerkten Stand ändern darf. +/// +/// +/// +/// Der Anlass. Ein Film stand bei 46 Minuten, und nach einem Neuladen +/// bot die Detailseite nur noch „Abspielen" an. Gemessen, woran es lag: 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". +/// +/// +/// Woher so eine Meldung kommt: wer einen gerechneten Strom abspielt, misst +/// an einer Playlist, die noch wächst, und sieht nur das bisher Erzeugte. +/// Scheitert die Wiedergabe gleich darauf — auf dem iPad tat sie das —, +/// bleibt dieser Stummel als „Laufzeit" stehen. Dazu meldet jeder Spieler +/// beim Öffnen einmal die Zeit null, bevor er überhaupt gesprungen ist. +/// +/// +/// Die Regel. Ein Client darf einen Stand fortschreiben, aber nicht +/// zerstören. Die Laufzeit, die der Server kennt, schlägt die gemeldete, und +/// eine gemeldete Null löscht nichts. +/// +/// +[TestFixture] +public sealed class FortschrittTests +{ + private string _bestand = ""; + + [SetUp] + public void Anlegen() => _bestand = Directory.CreateDirectory( + Path.Combine(Path.GetTempPath(), "flaro-fort-" + Guid.CreateVersion7().ToString("N"))).FullName; + + [TearDown] + public void Aufraeumen() + { + try { Directory.Delete(_bestand, recursive: true); } catch (IOException) { /* Kür */ } + } + + /// Ein Server mit einem eingelesenen Film, angemeldet als Administrator. + private async Task<(HttpClient Client, Guid Film)> MitEinemFilm(FlaroServer server) + { + var voll = Path.Combine(_bestand, "Langprobe (2026)", "Langprobe (2026).mkv"); + Directory.CreateDirectory(Path.GetDirectoryName(voll)!); + await File.WriteAllBytesAsync(voll, new byte[2048]); + + var admin = await server.SetUpAndSignIn(); + var client = server.AlsAngemeldeter(admin.Token); + + var angelegt = await client.PostAsJsonAsync("/api/v1/libraries", + new CreateLibraryRequest("Prüfbestand", "Movies", _bestand)); + angelegt.EnsureSuccessStatusCode(); + var bibliothek = (await angelegt.Content.ReadFromJsonAsync())!; + + (await client.PostAsync($"/api/v1/libraries/{bibliothek.Id}/scan", null)).EnsureSuccessStatusCode(); + for (var versuch = 0; versuch < 100; versuch++) + { + var stand = await client.GetFromJsonAsync($"/api/v1/libraries/{bibliothek.Id}/scan"); + if (stand!.State is "finished") break; + if (stand.State is "failed") Assert.Fail($"Der Scan ist gescheitert: {stand.Error}"); + await Task.Delay(50); + } + + var seite = await client.GetFromJsonAsync>("/api/v1/movies"); + return (client, seite!.Items[0].Id); + } + + private static async Task Melden( + HttpClient client, Guid film, double stelle, double? laufzeit) + { + var antwort = await client.PostAsJsonAsync($"/api/v1/items/{film}/progress", + new ProgressRequest(stelle, laufzeit, null)); + antwort.EnsureSuccessStatusCode(); + return (await antwort.Content.ReadFromJsonAsync())!; + } + + /// Eine zu kurz gemeldete Laufzeit nimmt dem Film nicht die Stelle. + /// + /// + /// Das ist der gemeldete Fehler, Zahl für Zahl. Vorher kam hier Stelle 60 + /// heraus, Laufzeit 60 und „gesehen". + /// + [Test] + public async Task Eine_zu_kurze_Laufzeit_zerstoert_den_Stand_nicht() + { + using var server = new FlaroServer(); + var (client, film) = await MitEinemFilm(server); + + await Melden(client, film, 500, 600); + var danach = await Melden(client, film, 500, 60); + + Assert.Multiple(() => + { + Assert.That(danach.PositionSeconds, Is.EqualTo(500).Within(0.001), + "die Stelle darf nicht auf den Stummel zusammengestaucht werden"); + Assert.That(danach.RuntimeSeconds, Is.EqualTo(600).Within(0.001), + "und die bekannte Laufzeit nicht durch ihn ersetzt"); + Assert.That(danach.Watched, Is.False, + "bei 500 von 600 Sekunden ist nichts zu Ende gesehen"); + Assert.That(danach.InProgress, Is.True, + "der Film gehört weiter in die Reihe Weiterschauen"); + }); + } + + /// Eine gemeldete Null löscht keine gemerkte Stelle. + /// + /// + /// Beide Clients tun das. Die App meldet beim Öffnen ausdrücklich + /// die Zeit null — dort legt erst diese Meldung die Sitzung an. Der + /// Browser tut es im Fenster zwischen „Datei geöffnet" und „Sprung + /// ausgeführt", und wenn er die Datei nicht entschlüsseln kann, kommt er + /// aus diesem Fenster nie heraus. + /// + [Test] + public async Task Eine_gemeldete_Null_loescht_die_Stelle_nicht() + { + using var server = new FlaroServer(); + var (client, film) = await MitEinemFilm(server); + + await Melden(client, film, 2771, 11468); + var danach = await Melden(client, film, 0, 11468); + + Assert.That(danach.PositionSeconds, Is.EqualTo(2771).Within(0.001)); + } + + /// Wer wirklich von vorn anfängt, setzt die Stelle trotzdem zurück. + /// + /// + /// Das ist der Preis der Regel darüber, und er ist klein: zehn Sekunden + /// später meldet derselbe Spieler die 10, und die zählt. „Von vorn" + /// bleibt damit „von vorn", nur eben ab der ersten gesehenen Sekunde. + /// + [Test] + public async Task Von_vorn_setzt_die_Stelle_mit_der_ersten_echten_Meldung_zurueck() + { + using var server = new FlaroServer(); + var (client, film) = await MitEinemFilm(server); + + await Melden(client, film, 2771, 11468); + await Melden(client, film, 0, 11468); + var danach = await Melden(client, film, 10, 11468); + + Assert.That(danach.PositionSeconds, Is.EqualTo(10).Within(0.001)); + } + + /// Zurückspulen bleibt möglich. + /// + /// + /// Die Regel gilt der Null, nicht dem Rückwärtsgehen. Wer eine Szene + /// noch einmal sehen will, soll danach auch dort weitermachen. + /// + [Test] + public async Task Zurueckspulen_setzt_die_Stelle_weiter_zurueck() + { + using var server = new FlaroServer(); + var (client, film) = await MitEinemFilm(server); + + await Melden(client, film, 500, 600); + var danach = await Melden(client, film, 120, 600); + + Assert.That(danach.PositionSeconds, Is.EqualTo(120).Within(0.001)); + } + + /// Ohne jede bekannte Laufzeit zählt weiter, was der Client sagt. + /// + /// + /// Sonst gäbe es für einen frisch angefangenen Titel keinen Anteil und + /// damit nie ein „gesehen". Die neue Rangfolge nimmt dem Client nur den + /// Vorrang, nicht das Wort. + /// + [Test] + public async Task Ohne_bekannte_Laufzeit_gilt_die_gemeldete() + { + using var server = new FlaroServer(); + var (client, film) = await MitEinemFilm(server); + + var danach = await Melden(client, film, 100, 1000); + + Assert.That(danach.RuntimeSeconds, Is.EqualTo(1000).Within(0.001)); + } +} diff --git a/web/app/src/spieler.ts b/web/app/src/spieler.ts index 6114468..1193842 100644 --- a/web/app/src/spieler.ts +++ b/web/app/src/spieler.ts @@ -218,7 +218,21 @@ export function spieler(anfrage: Spieleranfrage): void { * weiss selten, welche. */ let umkodierenErzwingen = false; - const filmzeit = (): number => vorlauf + (Number.isFinite(video.currentTime) ? video.currentTime : 0); + /** Wo im Film der Spieler steht. + * + * **Der noch ausstehende Sprung zählt mit.** Bei Direktwiedergabe gibt der + * Server `startSeconds: 0` zurück und der Browser springt selbst, sobald + * die Metadaten da sind. Bis dahin steht `currentTime` auf null — und + * kommen die Metadaten nie, weil der Browser mit der Datei nicht + * zurechtkommt, bleibt es dabei. + * + * Genau dann greift der Rückfall aufs Umkodieren, und der bekam bisher + * diese Null übergeben: „an der Stelle weiter, an der es hing" fing in + * Wahrheit von vorn an. Gemeldet wurde die Null auch, und sie hat auf dem + * Server eine gemerkte Stelle gelöscht. */ + const filmzeit = (): number => zuSpringen > 0 + ? zuSpringen + : vorlauf + (Number.isFinite(video.currentTime) ? video.currentTime : 0); /* ---------- Aufbau ---------- */