Prüfen, ob wir uns die Leiter selbst bauen können

Der Wortlaut der Master-Playlist zeigt, woraus Jellyfins eigene Leiter
bestünde: dieselbe `main.m3u8`-Adresse, mehrfach, mit anderem
`VideoBitrate`. Eine Master-Playlist ist nur ein Menü.

Liefert der Server diese Unter-Playlists auch dann, wenn **wir** die
Zahl ändern, brauchen wir seine Zustimmung gar nicht — dann schreiben
wir das Menü selbst, und die Frage, warum er es verweigert, ist
gegenstandslos.

Die Probe versucht deshalb drei Stufen von Hand: halbe Bitrate, ein
Viertel, und ein Viertel zusammen mit `maxHeight=480`. Das letzte ist
der interessante Fall — Jellyfins eigene Leiter lässt die Auflösung
stehen und dreht nur an der Bitrate. Eine Stufe, die auch kleiner
rechnet, sieht bei wenig Bandbreite deutlich besser aus als dieselbe
Auflösung mit zu wenig Daten.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-09-27 17:34:00 +02:00
co-authored by Claude Opus 5
parent 6e18fda4aa
commit f48eb55f35
@@ -321,7 +321,10 @@ func jellyfinLeiter(host: String) async {
)
print(mitABR)
if mitABR.contains("1 Stufe:") { erklärungWarumKeineLeiter() }
if mitABR.contains("1 Stufe:") {
erklärungWarumKeineLeiter()
await prüfeSelbstgebauteLeiter(master: master, kopfzeilen: kopf)
}
// Aufräumen: die Aushandlung hat am Server eine Sitzung angelegt, und
// ohne dieses Signal rechnet der Transcoder weiter, obwohl niemand
@@ -354,3 +357,59 @@ func erklärungWarumKeineLeiter() {
print(" Kam dieser Aufruf von zuhause, ist Punkt 1 die Erklärung — und dann")
print(" sagt die Messung nichts darüber, was von unterwegs käme.")
}
/// Prüft, ob wir uns die Leiter **selbst** bauen könnten.
///
/// Eine Master-Playlist ist nur ein Menü: eine Liste von Adressen mit Angaben
/// dazu. Jellyfins eigene Leiter besteht aus derselben `main.m3u8`-Adresse,
/// nur mit anderem `VideoBitrate`. Liefert der Server diese Unter-Playlists
/// auch dann, wenn **wir** die Zahl ändern, dann braucht es seine Zustimmung
/// gar nicht — dann schreiben wir das Menü.
///
/// Zusätzlich mit `maxHeight`: Jellyfins eigene Leiter lässt die Auflösung
/// stehen und dreht nur an der Bitrate. Eine Stufe, die auch kleiner rechnet,
/// sieht bei wenig Bandbreite deutlich besser aus.
func prüfeSelbstgebauteLeiter(master: URL, kopfzeilen: [String: String]) async {
print("")
print("=== Ginge eine selbstgebaute Leiter? ===")
guard let (daten, _) = try? await URLSession.shared.data(for: anfrage(master, kopfzeilen)),
let zeile = String(decoding: daten, as: UTF8.self)
.split(separator: "\n")
.first(where: { $0.hasPrefix("main.m3u8") }),
let unter = URL(string: String(zeile), relativeTo: master)?.absoluteURL
else {
print(" Keine Unter-Playlist im Menü gefunden.")
return
}
let versuche: [(String, Int, Int?)] = [
("halbe Bitrate", 3_000_000, nil),
("ein Viertel", 1_500_000, nil),
("ein Viertel + 480p", 1_500_000, 480),
]
for (name, bitrate, höhe) in versuche {
var ziel = setzend("VideoBitrate", auf: String(bitrate), in: unter)
if let höhe { ziel = setzend("maxHeight", auf: String(höhe), in: ziel) }
if let (roh, antwort) = try? await URLSession.shared.data(for: anfrage(ziel, kopfzeilen)) {
let status = (antwort as? HTTPURLResponse)?.statusCode ?? 0
let text = String(decoding: roh, as: UTF8.self)
let schnipsel = text.split(separator: "\n").count { $0.hasPrefix("#EXTINF") }
print(" \(name): HTTP \(status), \(schnipsel) Schnipsel")
} else {
print(" \(name): nicht erreichbar")
}
}
print("")
print(" Kommen hier überall Schnipsel zurück, brauchen wir Jellyfins")
print(" Zustimmung nicht — dann schreiben wir das Menü selbst.")
}
private func anfrage(_ url: URL, _ kopfzeilen: [String: String]) -> URLRequest {
var request = URLRequest(url: url)
for (name, wert) in kopfzeilen { request.setValue(wert, forHTTPHeaderField: name) }
return request
}