„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:
co-authored by
Claude Opus 5
parent
efed8717a3
commit
fd11a9eebe
@@ -4862,13 +4862,13 @@
|
|||||||
"de" : {
|
"de" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"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" : {
|
"en" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"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."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -227,17 +227,63 @@ final class PlayerModel {
|
|||||||
/// als ein Sender, der gar nicht mehr angeht.
|
/// als ein Sender, der gar nicht mehr angeht.
|
||||||
private var downgradeBudget: Int { isLive ? 1 : 2 }
|
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 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
|
/// unterwegs gelernt hat. Getrennt von `quality`, damit eine schlechte
|
||||||
/// Zugfahrt nicht als gemerkte Stufe das Wohnzimmer verschlechtert.
|
/// Zugfahrt nicht als gemerkte Stufe das Wohnzimmer verschlechtert.
|
||||||
private var effectivePolicy: PlaybackPolicy {
|
private var effectivePolicy: PlaybackPolicy {
|
||||||
let base = quality.policy(basedOn: policy)
|
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(
|
return PlaybackPolicy(
|
||||||
allowsTranscoding: true,
|
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.
|
// und die abgesenkte Grenze bliebe wirkungslos.
|
||||||
allowsFallbackEngine: false,
|
allowsFallbackEngine: false,
|
||||||
maximumBitrate: min(ceiling, base.maximumBitrate ?? ceiling),
|
maximumBitrate: min(ceiling, base.maximumBitrate ?? ceiling),
|
||||||
@@ -578,7 +624,9 @@ final class PlayerModel {
|
|||||||
case .transcode(let maximumBitrate):
|
case .transcode(let maximumBitrate):
|
||||||
TranscodeSettings(
|
TranscodeSettings(
|
||||||
maximumBitrate: maximumBitrate,
|
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
|
// **Nur bei „automatisch".** Eine ausdrücklich gewählte Stufe
|
||||||
// ist eine Ansage, keine Obergrenze: wer „720p · 3 Mbps"
|
// ist eine Ansage, keine Obergrenze: wer „720p · 3 Mbps"
|
||||||
// antippt, bekommt das und nicht „höchstens 3, meistens
|
// antippt, bekommt das und nicht „höchstens 3, meistens
|
||||||
@@ -845,7 +893,14 @@ final class PlayerModel {
|
|||||||
if lastStepUp == nil { self.lastStepUp = now }
|
if lastStepUp == nil { self.lastStepUp = now }
|
||||||
return
|
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
|
ziel = höher
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user