Watchdog heisst Watchdog, Engine heisst Engine

Eingedeutschte Fachbegriffe in den Kommentaren — „Wachhund" für
Watchdog, „Motor" für Engine. Das liest sich albern und ist unklarer als
das Original; niemand sagt Wachhund, wenn er einen Watchdog meint.

Acht Stellen berichtigt. Gewöhnliche Sprache bleibt deutsch, der Stil
der Kommentare ändert sich nicht.

Dazu die Plex-Leiter messbar gemacht: `--leiter` baut dieselbe Anfrage
zweimal, mit und ohne `autoAdjustQuality=1`, und zählt die Stufen. Die
Zeile steht seit dem Umbau im Code und war nie geprüft — genau die Art
ungeprüfter Annahme über Serververhalten, die bei Jellyfin vier Runden
gekostet hat.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-09-27 17:47:31 +02:00
co-authored by Claude Opus 5
parent 314c1a3bfc
commit e324a474fd
10 changed files with 45 additions and 9 deletions
+1 -1
View File
@@ -847,7 +847,7 @@ final class PlayerModel {
/// Führt Buch über das Stocken — die **zweite** Uhr. /// Führt Buch über das Stocken — die **zweite** Uhr.
/// ///
/// Der Wachhund darunter ist ein Totmann: vierzig Sekunden am Stück ohne /// Der Watchdog darunter ist ein Totmann: vierzig Sekunden am Stück ohne
/// Daten, dann Schluss. Er setzt sich bei jedem kurzen Erholen zurück, /// Daten, dann Schluss. Er setzt sich bei jedem kurzen Erholen zurück,
/// und genau das ist sein blinder Fleck: zehn Rückler à dreißig Sekunden /// und genau das ist sein blinder Fleck: zehn Rückler à dreißig Sekunden
/// lösen ihn nie aus, obwohl die halbe Stunde unbrauchbar war. /// lösen ihn nie aus, obwohl die halbe Stunde unbrauchbar war.
@@ -5,7 +5,7 @@ import Foundation
/// Der billige Hebel ist `preferredPeakBitRate`: er wirkt ohne Neuaufbau. /// Der billige Hebel ist `preferredPeakBitRate`: er wirkt ohne Neuaufbau.
/// Wenn er nichts ausrichtet — weil die Playlist nur eine Stufe hat oder weil /// Wenn er nichts ausrichtet — weil die Playlist nur eine Stufe hat oder weil
/// schon die unterste läuft —, bleibt nur der Neuaufbau mit kleinerer Bitrate: /// schon die unterste läuft —, bleibt nur der Neuaufbau mit kleinerer Bitrate:
/// Transcoder anhalten, Motor abreißen, neu aushandeln, zurückspringen. Das /// Transcoder anhalten, Engine abreißen, neu aushandeln, zurückspringen. Das
/// sind Sekunden mit schwarzem Bild. /// sind Sekunden mit schwarzem Bild.
/// ///
/// **Ein Notnagel, keine Regelschleife.** Zu oft ausgelöst erzeugt er genau /// **Ein Notnagel, keine Regelschleife.** Zu oft ausgelöst erzeugt er genau
@@ -48,7 +48,7 @@ public struct PlaybackDecision: Sendable, Equatable {
switch method { switch method {
case .directPlay(let engine): engine case .directPlay(let engine): engine
// Der Transcoder liefert HLS, und damit ist AVPlayer der richtige // Der Transcoder liefert HLS, und damit ist AVPlayer der richtige
// Motor. **Nicht** aber, wie hier früher stand, „inklusive // Engine. **Nicht** aber, wie hier früher stand, „inklusive
// automatischem Bitratenwechsel": den gibt es nur, wenn die Playlist // automatischem Bitratenwechsel": den gibt es nur, wenn die Playlist
// mehrere Stufen führt, und das muss ausdrücklich bestellt werden. // mehrere Stufen führt, und das muss ausdrücklich bestellt werden.
case .transcode: .avPlayer case .transcode: .avPlayer
@@ -2,7 +2,7 @@ import Foundation
/// Buch über das Stocken, mit gleitendem Fenster. /// Buch über das Stocken, mit gleitendem Fenster.
/// ///
/// **Warum es das gibt.** Der Wachhund im Player kannte nur eine Frage: steht /// **Warum es das gibt.** Der Watchdog im Player kannte nur eine Frage: steht
/// es seit vierzig Sekunden am Stück? Bei jedem kurzen Erholen setzte er sich /// es seit vierzig Sekunden am Stück? Bei jedem kurzen Erholen setzte er sich
/// zurück — zehn Rückler à dreißig Sekunden lösten nichts aus, obwohl die /// zurück — zehn Rückler à dreißig Sekunden lösten nichts aus, obwohl die
/// halbe Stunde unbrauchbar war. Genau dieser Fall ist der, über den geklagt /// halbe Stunde unbrauchbar war. Genau dieser Fall ist der, über den geklagt
@@ -90,7 +90,7 @@ public final class AVPlayerEngine: VideoPlayerEngine {
teardownObservers() teardownObservers()
state = .loading state = .loading
// Die Buchführung gehört zur Quelle, nicht zum Motor: ein neuer Titel // Die Buchführung gehört zur Quelle, nicht zur Engine: ein neuer Titel
// fängt bei null an, sonst schleppte die Zahl den vorigen Film mit. // fängt bei null an, sonst schleppte die Zahl den vorigen Film mit.
variantBitrates = [] variantBitrates = []
wantsToPlay = false wantsToPlay = false
@@ -21,7 +21,7 @@ public struct PlaybackSource: Sendable, Equatable {
/// wackelige Leitung ist es zu wenig: der Systemwert ist auf schnellen /// wackelige Leitung ist es zu wenig: der Systemwert ist auf schnellen
/// Start ausgelegt, nicht darauf, einen Aussetzer zu überstehen. /// Start ausgelegt, nicht darauf, einen Aussetzer zu überstehen.
/// ///
/// **Wer das entscheidet, muss mehr wissen als der Motor.** Ob es ein /// **Wer das entscheidet, muss mehr wissen als die Engine.** Ob es ein
/// Sender ist, ob die Datei lokal liegt — das weiß der Player, nicht die /// Sender ist, ob die Datei lokal liegt — das weiß der Player, nicht die
/// Engine. Deshalb reist die Zahl mit der Quelle, statt dass die Engine /// Engine. Deshalb reist die Zahl mit der Quelle, statt dass die Engine
/// rät. /// rät.
@@ -150,6 +150,42 @@ let firstMovie = await { () async -> PlexItem? in
return try? await server.items(inLibrary: movieLibrary, limit: 1).items.first return try? await server.items(inLibrary: movieLibrary, limit: 1).items.first
}() }()
// MARK: - Die Leiter bei Plex
// Dieselbe Frage wie bei Jellyfin, und aus demselben Grund gestellt: bei Plex
// steht `autoAdjustQuality=1` im Code, und ob sein Transcoder daraufhin
// mehrere Stufen liefert, war nie geprüft. Eine ungeprüfte Annahme über
// Serververhalten hat uns bei Jellyfin vier Runden gekostet.
if CommandLine.arguments.contains("--leiter") {
guard let movie = firstMovie else {
print("Keine Datei zum Prüfen gefunden.")
exit(0)
}
print("Leiter-Probe mit: \(movie.title)\n")
let ohne = TranscodeRequest(
ratingKey: movie.ratingKey,
maximumBitrate: 8_000,
isLocal: connection.local
)
let mit = ohne.askingForLadder()
for (name, anfrage) in [("ohne autoAdjustQuality", ohne), ("mit autoAdjustQuality=1", mit)] {
print("=== \(name) ===")
guard let url = server.transcodeURL(for: anfrage) else {
print(" Keine Adresse zu bauen.\n")
continue
}
print(await prüfePlaylist(url))
print()
}
// Aufräumen: beide Anfragen haben am Server einen Transcoder angeworfen.
await server.stopTranscode(sessionIdentifier: ohne.sessionIdentifier)
print("Transcoder angehalten.")
exit(0)
}
if let movie = firstMovie { if let movie = firstMovie {
let request = TranscodeRequest( let request = TranscodeRequest(
ratingKey: movie.ratingKey, ratingKey: movie.ratingKey,
@@ -65,7 +65,7 @@ public struct TranscodeRequest: Sendable, Equatable {
/// Dieselbe Sitzungskennung: es ist **eine** Wiedergabe, und der Server /// Dieselbe Sitzungskennung: es ist **eine** Wiedergabe, und der Server
/// soll sie nicht für zwei halten — sonst bliebe beim Aufräumen ein /// soll sie nicht für zwei halten — sonst bliebe beim Aufräumen ein
/// Transcoder stehen. /// Transcoder stehen.
func askingForLadder() -> TranscodeRequest { public func askingForLadder() -> TranscodeRequest {
TranscodeRequest( TranscodeRequest(
ratingKey: ratingKey, ratingKey: ratingKey,
mediaIndex: mediaIndex, mediaIndex: mediaIndex,
@@ -6,7 +6,7 @@ import Foundation
struct StallLedgerTests { struct StallLedgerTests {
/// Der Fall, wegen dem es den Typ gibt. /// Der Fall, wegen dem es den Typ gibt.
/// ///
/// Zehn Rückler à dreißig Sekunden lösten beim alten Wachhund nichts aus, /// Zehn Rückler à dreißig Sekunden lösten beim alten Watchdog nichts aus,
/// weil er sich bei jedem Erholen zurücksetzte. Hier muss sichtbar /// weil er sich bei jedem Erholen zurücksetzte. Hier muss sichtbar
/// werden, dass die Wiedergabe unbrauchbar ist. /// werden, dass die Wiedergabe unbrauchbar ist.
@Test("Viele kurze Rückler bleiben sichtbar") @Test("Viele kurze Rückler bleiben sichtbar")
@@ -227,7 +227,7 @@ extension VLCPlayerEngine: VLCMediaPlayerDelegate {
/// Aufzählung führt nur `opening`, `playing`, `paused`, `stopped` und /// Aufzählung führt nur `opening`, `playing`, `paused`, `stopped` und
/// `error`. Weil diese Methode bisher fehlte, ging der Zustand /// `error`. Weil diese Methode bisher fehlte, ging der Zustand
/// `.buffering` auf dem VLC-Weg **nie** an — und damit löste der /// `.buffering` auf dem VLC-Weg **nie** an — und damit löste der
/// Wachhund im Player dort nie aus. Ein hängender Strom lief stumm ins /// Watchdog im Player dort nie aus. Ein hängender Strom lief stumm ins
/// Nichts, ohne Kreisel, ohne Meldung, ohne Abbruch. /// Nichts, ohne Kreisel, ohne Meldung, ohne Abbruch.
/// ///
/// Nur aus dem Spielen heraus gewertet. Die Dokumentation sagt, dass vor /// Nur aus dem Spielen heraus gewertet. Die Dokumentation sagt, dass vor