From 19ec17da19e92140dc84eaf6e92367c72a96b6dd Mon Sep 17 00:00:00 2001 From: Scarriffle Date: Sat, 10 Oct 2026 20:20:16 +0200 Subject: [PATCH] =?UTF-8?q?Zw=C3=B6lf=20Sekunden=20f=C3=BCr=20eine=20H?= =?UTF-8?q?=C3=BClle=20um=20nichts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Die Messung sagt es deutlich: 20:15:53 Umpacken — Schlüsselbildsuche 2348 ms, Vorbereitung 2394 ms. 20:16:05 Umkodieren — Schlüsselbildsuche 0 ms, Vorbereitung 0 ms. 20:16:08 erstes Stück nach 2750 ms. Fünfzehn Sekunden, und zwölf davon liegen zwischen zwei Bestellungen für denselben Film. Der erste Strom wurde angelegt und dann nie abgeholt — keine Medienliste, kein Stück, kein ffmpeg-Fehler. Danach gab die App auf und bestellte neu, diesmal mit Umkodieren, und das lief nach knapp drei Sekunden. Dazwischen lag die Hauptliste. Es gibt sie allein wegen der Untertitelspuren, und bei diesem Film waren die noch gar nicht aus der Datei gezogen — sie war also leer bis auf einen Verweis auf die Medienliste. Eine Hülle um nichts, und keine umsonst: ohne `CODECS` muss ein Spieler sie erst aufmachen, um zu sehen, was darin liegt. Jetzt gibt es sie nur, wenn wenigstens eine Spur abholbar ist. Sonst bekommt die App die Medienliste unmittelbar — denselben Weg wie der Browser, der nie gestolpert ist. Co-Authored-By: Claude Opus 5 --- .../Flaro.Api/Endpoints/StreamEndpoints.cs | 24 ++++++++++++++--- server/tests/Flaro.Api.Tests/StreamTests.cs | 27 +++++++++++-------- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/server/src/Flaro.Api/Endpoints/StreamEndpoints.cs b/server/src/Flaro.Api/Endpoints/StreamEndpoints.cs index e51645a..d6a0ac6 100644 --- a/server/src/Flaro.Api/Endpoints/StreamEndpoints.cs +++ b/server/src/Flaro.Api/Endpoints/StreamEndpoints.cs @@ -118,7 +118,7 @@ public static class StreamEndpoints erzwingen: anfrage.Transcode, tonversatz: Math.Clamp(anfrage.AudioOffsetMs, -5000, 5000) / 1000.0); - return Results.Ok(await Auskunft(db, sitzung)); + return Results.Ok(await Auskunft(db, vorrat, sitzung)); } catch (InvalidOperationException fehler) { @@ -511,17 +511,35 @@ public static class StreamEndpoints new HashSet(anfrage.AudioCodecs, StringComparer.OrdinalIgnoreCase)); } - private static async Task Auskunft(FlaroContext db, Stromsitzung sitzung) + private static async Task Auskunft( + FlaroContext db, Untertitelvorrat vorrat, Stromsitzung sitzung) { var spuren = await db.MediaStreams.AsNoTracking() .Where(s => s.MediaFileId == sitzung.DateiId) .OrderBy(s => s.Index) .ToListAsync(); + // **Die Hauptliste nur, wenn etwas drinsteht.** + // + // Sie gibt es allein wegen der Untertitelspuren; ohne sie ist sie + // eine Hülle um die Medienliste. Und eine Hülle ist nicht umsonst: + // eine Hauptliste ohne `CODECS` muss der Spieler erst aufmachen, um + // zu sehen, was darin liegt. Gemessen an einem Film, dessen Spuren + // noch nicht herausgezogen waren: zwölf Sekunden vergingen, ohne dass + // die Medienliste auch nur abgeholt wurde, dann gab die App auf und + // bestellte neu. + var datei = await db.MediaFiles.AsNoTracking() + .FirstOrDefaultAsync(f => f.Id == sitzung.DateiId); + + var hatText = datei is not null && spuren + .Where(x => x.Kind == MediaStreamKind.Subtitle) + .Where(x => x.Codec is null || !Untertitelvorrat.IstBild(x.Codec)) + .Any(x => vorrat.Liegtvor(datei.Id, datei.ModifiedAt, x.Index)); + return new StreamResponse( SessionId: sitzung.Kennung, Url: $"/api/v1/stream/{sitzung.Kennung}/index.m3u8", - MasterUrl: $"/api/v1/stream/{sitzung.Kennung}/master.m3u8", + MasterUrl: hatText ? $"/api/v1/stream/{sitzung.Kennung}/master.m3u8" : null, Method: sitzung.Art switch { Stromart.Direkt => "direct", diff --git a/server/tests/Flaro.Api.Tests/StreamTests.cs b/server/tests/Flaro.Api.Tests/StreamTests.cs index b4e5500..8ff9f8f 100644 --- a/server/tests/Flaro.Api.Tests/StreamTests.cs +++ b/server/tests/Flaro.Api.Tests/StreamTests.cs @@ -432,11 +432,18 @@ public class StreamTests [Test] public async Task Die_Hauptliste_nennt_die_Bildspur() { - Film("Hauptliste (2020)"); + FilmMitUntertitel("Hauptliste (2020)"); var (client, datei) = await Eingelesen(); var strom = await Strom(client, datei.Id, Browser()); - Assert.That(strom.MasterUrl, Is.Not.Null, "Eine Sitzung hat eine Hauptliste."); + (await client.GetAsync( + $"/api/v1/files/{datei.Id}/subtitles/{strom.SubtitleStreams.Single().Index}.vtt")) + .EnsureSuccessStatusCode(); + + // Noch einmal bestellen: die Hauptliste gibt es erst, wenn eine Spur + // abholbar ist — vorher wäre sie eine Hülle um die Medienliste. + strom = await Strom(client, datei.Id, Browser()); + Assert.That(strom.MasterUrl, Is.Not.Null); var liste = await client.GetStringAsync(strom.MasterUrl!); @@ -465,13 +472,14 @@ public class StreamTests var spur = strom.SubtitleStreams.Single(); // **Erst holen, dann fragen.** In der Hauptliste steht nur, was - // sofort abholbar ist; der Abruf der Spur wartet, bis sie aus der - // Datei gezogen ist. Ohne diesen Schritt hinge die Prüfung daran, - // wie schnell der Rechner gerade ist. + // sofort abholbar ist, und es gibt sie überhaupt nur dann; der Abruf + // der Spur wartet, bis sie aus der Datei gezogen ist. Ohne diesen + // Schritt hinge die Prüfung daran, wie schnell der Rechner ist. (await client.GetAsync( $"/api/v1/files/{datei.Id}/subtitles/{spur.Index}.vtt")).EnsureSuccessStatusCode(); - var liste = await client.GetStringAsync(strom.MasterUrl!); + var zweiter = await Strom(client, datei.Id, Browser()); + var liste = await client.GetStringAsync(zweiter.MasterUrl!); Assert.Multiple(() => { @@ -585,11 +593,8 @@ public class StreamTests var strom = await Strom(client, datei.Id, new StreamRequest( Containers: null, VideoCodecs: null, AudioCodecs: null, StartSeconds: 0)); - Assert.That(strom.MasterUrl, Is.Not.Null, "Eine MKV ist für Apple nichts zum Durchreichen."); - - var haupt = await client.GetAsync(strom.MasterUrl!); - Assert.That(haupt.StatusCode, Is.EqualTo(HttpStatusCode.OK), - await haupt.Content.ReadAsStringAsync()); + Assert.That(strom.Method, Is.Not.EqualTo("direct"), + "Eine MKV ist für Apple nichts zum Durchreichen."); var medien = await client.GetAsync(strom.Url); Assert.That(medien.StatusCode, Is.EqualTo(HttpStatusCode.OK),