„Auto" misst jetzt die Verbindung, statt die Einstellung abzulesen

Der Denkfehler, und er war meiner: `automatic` hiess wörtlich „nimm, was
in den Einstellungen steht". Stand dort „Original", bat die App über eine
Zugverbindung um eine 4K-Datei und wartete vierzig Sekunden auf etwas,
das nie kommen konnte. Das ist keine Automatik, das ist eine Durchreiche
mit einem irreführenden Namen.

**Die Einstellung ist jetzt die Obergrenze, nicht das Ziel** — „bis
hierhin, wenn die Leitung es trägt". Im eigenen Netz bleibt alles wie
bisher: dort gibt es keine Grenze, weil die Leitung das Original trägt
und ein Deckel das Bild ohne Grund verschlechterte. Die Unterscheidung
steht in `isLocalConnection` und ist nicht geraten.

Unterwegs beginnt „Auto" bei acht Megabit — genug für ordentliches
1080p, wenig genug, dass es fast überall anläuft — und tastet sich von
dort hinauf, solange es gut geht, bis höchstens zur eingestellten Decke.
Hinunter geht es sofort, sobald es hakt.

**Und die Auflösung geht mit.** Bitrate allein genügt nicht: 4K mit zwei
Megabit sieht schlechter aus als 720p mit zwei Megabit — dieselbe
Datenmenge, auf viermal so viele Bildpunkte verteilt. Wer die Bitrate
senkt, muss die Auflösung mitnehmen, sonst spart er Daten und
verschenkt das Bild.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-09-27 18:32:33 +02:00
co-authored by Claude Opus 5
parent efed8717a3
commit fd11a9eebe
2 changed files with 62 additions and 7 deletions
+2 -2
View File
@@ -4862,13 +4862,13 @@
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Wird eine Datei größer als die eingestellte Grenze, rechnet der Server sie unterwegs herunter und passt die Bitrate laufend an die Verbindung an."
"value" : "Die Obergrenze, nicht das Ziel. Im eigenen Netz überträgt der Server das Original; unterwegs beginnt Flixy kleiner und tastet sich hinauf, solange die Verbindung es trägt — höchstens bis zu dieser Stufe. Auflösung und Bitrate gehen dabei zusammen."
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "If a file exceeds the limit, the server transcodes it on the fly and adapts the bitrate to your connection."
"value" : "An upper limit, not a target. On your own network the server sends the original; away from home Flixy starts lower and works its way up as long as the connection holds — never above this step. Resolution and bitrate move together."
}
}
}
+60 -5
View File
@@ -227,17 +227,63 @@ final class PlayerModel {
/// als ein Sender, der gar nicht mehr angeht.
private var downgradeBudget: Int { isLive ? 1 : 2 }
/// **Womit „Auto" anfängt.**
///
/// Hier lag der Denkfehler: `automatic` hiess wörtlich „nimm, was in den
/// Einstellungen steht", und stand dort „Original", bat die App über eine
/// Zugverbindung um eine 4K-Datei. Das ist keine Automatik, das ist eine
/// Durchreiche.
///
/// Jetzt ist die Einstellung die **Obergrenze**, nicht das Ziel: „bis
/// hierhin, wenn die Leitung es trägt". Angefangen wird mit einem Wert,
/// der fast überall anläuft; von dort geht es hinauf, solange es gut
/// geht, und hinunter, sobald es hakt.
///
/// **Im eigenen Netz keine Grenze.** Dort trägt die Leitung das Original,
/// und ein Deckel machte das Bild ohne Grund schlechter. Die
/// Unterscheidung ist nicht geraten — sie steht in `isLocalConnection`.
private var startingCeiling: Int? {
guard !isLocalConnection else { return nil }
// Acht Megabit tragen 1080p ordentlich und laufen auch über eine
// mässige Fernverbindung an. Wer mehr will und mehr hat, kommt über
// das Hinaufschalten dorthin.
return 8_000
}
/// Die Auflösung, die zur aktuellen Stufe gehört.
///
/// **Bitrate allein genügt nicht.** 4K mit zwei Megabit sieht schlechter
/// aus als 720p mit zwei Megabit — dieselbe Datenmenge, auf viermal so
/// viele Bildpunkte verteilt. Wer die Bitrate senkt, muss die Auflösung
/// mitnehmen, sonst spart er Daten und verschenkt das Bild.
private var automaticResolution: String? {
guard quality == .automatic, let ceiling = automaticCeiling else { return nil }
let sprossen = PlayerQuality.available(forSourceHeight: sourceHeight)
.compactMap { option -> PlayerQuality.Rung? in
guard case .limited(let rung) = option else { return nil }
return rung
}
// Die Sprosse, die zu dieser Bitrate gehört — oder die nächstgrössere,
// falls die Zahl von woanders kommt.
let passend = sprossen.first { $0.bitrate >= ceiling } ?? sprossen.last
guard let passend else { return nil }
return "\(passend.width)x\(passend.height)"
}
/// Die Richtlinie, wie sie **jetzt** gilt.
///
/// Die eingestellte Obergrenze, überschrieben von dem, was die Automatik
/// Die eingestellte Obergrenze, gedeckelt von dem, was die Automatik
/// unterwegs gelernt hat. Getrennt von `quality`, damit eine schlechte
/// Zugfahrt nicht als gemerkte Stufe das Wohnzimmer verschlechtert.
private var effectivePolicy: PlaybackPolicy {
let base = quality.policy(basedOn: policy)
guard quality == .automatic, let ceiling = automaticCeiling else { return base }
guard quality == .automatic else { return base }
// Die Einstellung ist die Decke, die Automatik der Boden darunter.
guard let ceiling = automaticCeiling ?? startingCeiling else { return base }
return PlaybackPolicy(
allowsTranscoding: true,
// Ohne Ersatzmotor: sonst spielte VLC die Datei unverändert ab,
// Ohne Ersatz-Engine: sonst spielte VLC die Datei unverändert ab,
// und die abgesenkte Grenze bliebe wirkungslos.
allowsFallbackEngine: false,
maximumBitrate: min(ceiling, base.maximumBitrate ?? ceiling),
@@ -578,7 +624,9 @@ final class PlayerModel {
case .transcode(let maximumBitrate):
TranscodeSettings(
maximumBitrate: maximumBitrate,
resolution: quality.resolution,
// Bei „Auto" die Auflösung zur gewählten Stufe, sonst die der
// ausdrücklich gewählten.
resolution: quality.resolution ?? automaticResolution,
// **Nur bei „automatisch".** Eine ausdrücklich gewählte Stufe
// ist eine Ansage, keine Obergrenze: wer „720p · 3 Mbps"
// antippt, bekommt das und nicht „höchstens 3, meistens
@@ -845,7 +893,14 @@ final class PlayerModel {
if lastStepUp == nil { self.lastStepUp = now }
return
}
guard let höher = sprossen.first(where: { $0 > aktuell }) else { return }
// **Bis zur eingestellten Decke, nicht bis zur Startstufe.** Wer
// „Original" eingestellt hat und eine Leitung hat, die es trägt,
// soll dort auch ankommen — das ist die andere Hälfte von
// „richtet sich nach der Verbindung".
let decke = quality.policy(basedOn: policy).maximumBitrate
guard let höher = sprossen.first(where: { $0 > aktuell }),
decke.map({ höher <= $0 }) ?? true
else { return }
ziel = höher
}