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),