Player: eine gewählte Stufe wurde gar nicht umgerechnet

Der Decider rechnet nur um, wenn die Datei die Grenze überschreitet. Für
den Normalfall ist das richtig — für eine ausdrückliche Wahl im Player
nicht: wer für eine 8-Mbit-Datei „12 Mbit" wählt, bekam DirectPlay, weil
die Datei ja darunter liegt. Gemeint ist aber „rechne es mir auf diese
Stufe um", nicht „gib mir die Datei, falls sie so vorliegt".

Die Richtlinie kann eine Umrechnung jetzt erzwingen, und die Wahl im
Player tut das.

Dazu verschluckte `select(quality:)` den eigentlichen Grund: lehnte der
Server ab und sagte im Klartext warum, wurde diese Meldung danach durch
das allgemeine „keine abspielbare Datei" ersetzt — der eine brauchbare
Hinweis war damit weg. Jetzt bleibt stehen, was der Server gesagt hat.

Und das Menü war zu viel. Vorn steht die kurze Liste — automatisch,
Original, je eine brauchbare Stufe pro Auflösung, nur Ton. Wer eine
bestimmte Bitrate erzwingen muss, klappt „Alle Stufen" auf; im Alltag
will man „eine Stufe kleiner" und nicht die Wahl zwischen zehn und zwölf
Megabit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-09-12 22:05:27 +02:00
co-authored by Claude Opus 5
parent b53fce65b8
commit f628819765
9 changed files with 2928 additions and 2771 deletions
@@ -119,6 +119,8 @@ struct TechnicalDetails: View {
return String(localized: "technical.reason.audioCodec \(codec.uppercased())")
case .streamsUnknown:
return String(localized: "technical.reason.unanalyzed")
case .qualityChosen:
return String(localized: "technical.reason.chosen")
case .nativelySupported, .bitrateExceedsLimit, .transcodingUnavailable:
continue
}
+7 -1
View File
@@ -229,7 +229,13 @@ final class PlayerModel {
server: server,
startingAt: resumeAt
) else {
phase = .failed(message: String(localized: "player.noPlayableFile"))
// Nur dann eine allgemeine Meldung, wenn `source` keine eigene
// hinterlassen hat. Vorher stand hier immer „keine abspielbare
// Datei", auch wenn der Server im Klartext gesagt hatte, warum er
// ablehnt — und damit war der eine brauchbare Hinweis weg.
if case .failed = phase {} else {
phase = .failed(message: String(localized: "player.noPlayableFile"))
}
return
}
+32 -15
View File
@@ -14,6 +14,12 @@ struct QualitySheet: View {
@Environment(\.flixyAccent) private var accent
@Environment(\.dismiss) private var dismiss
@State private var showsEveryRung = false
private var everyday: [PlayerQuality] {
PlayerQuality.everyday(forSourceHeight: model.sourceHeight)
}
private var options: [PlayerQuality] {
PlayerQuality.available(forSourceHeight: model.sourceHeight)
}
@@ -24,23 +30,27 @@ struct QualitySheet: View {
current
Section {
row(for: .automatic)
row(for: .original)
}
ForEach(groupedHeights, id: \.self) { height in
Section("\(height)p") {
ForEach(rungs(height: height), id: \.self) { rung in
row(for: .limited(rung))
}
ForEach(everyday) { option in
row(for: option)
}
}
Section {
row(for: .audioOnly)
} footer: {
Text("quality.audioOnly.hint")
}
// Aufgeklappt nur, wer es wirklich will. Für den Alltag
// reicht „eine Stufe kleiner"; die volle Leiter braucht, wer
// eine bestimmte Bitrate erzwingen muss.
Section {
DisclosureGroup(isExpanded: $showsEveryRung) {
ForEach(groupedHeights, id: \.self) { height in
ForEach(rungs(height: height), id: \.self) { rung in
row(for: .limited(rung), detail: "\(height)p")
}
}
} label: {
Text("quality.everyRung")
}
}
}
.navigationTitle(Text("player.quality"))
.navigationBarTitleDisplayMode(.inline)
@@ -93,7 +103,7 @@ struct QualitySheet: View {
}
}
private func row(for option: PlayerQuality) -> some View {
private func row(for option: PlayerQuality, detail: String? = nil) -> some View {
Button {
Task { await model.select(quality: option) }
dismiss()
@@ -101,6 +111,11 @@ struct QualitySheet: View {
HStack {
Text(label(for: option))
.foregroundStyle(Color.flixyTextPrimary)
if let detail {
Text(detail)
.font(.flixyCaption)
.foregroundStyle(Color.flixyTextMuted)
}
Spacer()
if option == model.quality {
Image(systemName: "checkmark").foregroundStyle(accent)
@@ -116,7 +131,9 @@ struct QualitySheet: View {
case .automatic: String(localized: "quality.automatic")
case .original: String(localized: "quality.original")
case .audioOnly: String(localized: "quality.audioOnly")
case .limited(let rung): bitrateText(rung.bitrate)
// In der kurzen Liste zählt die Auflösung, nicht die Zahl dahinter:
// „720p" sagt mehr als „3 Mbps", wenn man nur kleiner will.
case .limited(let rung): "\(rung.height)p · \(bitrateText(rung.bitrate))"
}
}