Im Spurenblatt stand eine einzige Zeile: „Unbekannt". Das ist der Name, den AVFoundation einer namenlosen Spur gibt — und bei einem umgerechneten Stream liefert der Server genau eine. Der Player hatte nichts zur Auswahl, weil die Wahl längst gefallen war, bevor er gefragt wurde. Gewählt wird jetzt aus den Spuren, die **in der Datei** stehen, und zwar bevor der Stream ausgehandelt wird. Die Nummer geht als `audioStreamIndex` mit hinaus; wer mitten im Film umstellt, bekommt den Stream neu — ausser bei unveränderter Wiedergabe, wo der Player ohne Unterbrechung umschaltet. Die Abwägung selbst macht `AudioTrackChooser`: gemerkte Spur, dann Wunschsprache, dann Rangliste, dann Standardspur — und Audiodeskription mit deutlichem Abzug statt Ausschluss, damit eine Sprache, die es nur so gibt, immer noch besser ist als Stille. Drei Ebenen in `LanguagePreferences`: Serie, Mediathek, allgemein. Gefragt wird von unten nach oben. Die Mediathek wird nur nachgeschlagen, wenn überhaupt jemand eine Sprache je Mediathek gesetzt hat — sonst kostete jede Wiedergabe eine Anfrage für eine Ebene, die niemand benutzt. Wer von Hand umstellt, dessen Wahl wird für die Serie gemerkt, ohne Nachfrage. Der Dialog „für diese Serie merken?" ist genau die Art, die man nach dem dritten Mal wegklickt. Sichtbar ist es trotzdem: das Spurenblatt zeigt es und lässt es löschen. Dazu die Rangliste in den Einstellungen, ziehbar, und der Schalter „Audiodeskription meiden". Und die Player-Oberfläche blendet endlich als Ganzes über. Die Kopfzeile hatte gar keine Überblendung — sie sprang herein, während die Bedienung darunter verblasste. Jetzt steht die Animation an einer Stelle statt an sechs, und sie läuft über Deckkraft statt über Einfügen und Entfernen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
122 lines
4.4 KiB
Swift
122 lines
4.4 KiB
Swift
import SwiftUI
|
|
import FlixyDesign
|
|
import FlixyPlayback
|
|
import MediaKit
|
|
|
|
/// Auswahl einer Spur — Ton **oder** Untertitel, nicht beides.
|
|
///
|
|
/// Getrennt, weil es zwei verschiedene Fragen sind: die Tonspur wechselt man
|
|
/// einmal am Anfang, die Untertitel mitten im Film. Beide in einem Sheet
|
|
/// bedeutete jedes Mal Scrollen an der anderen Liste vorbei.
|
|
struct TrackPickerSheet: View {
|
|
enum Kind: Identifiable {
|
|
case audio
|
|
case subtitles
|
|
|
|
var id: Self { self }
|
|
|
|
var title: LocalizedStringKey {
|
|
switch self {
|
|
case .audio: "player.audioTracks"
|
|
case .subtitles: "player.subtitleTracks"
|
|
}
|
|
}
|
|
}
|
|
|
|
let model: PlayerModel
|
|
let kind: Kind
|
|
|
|
@Environment(\.dismiss) private var dismiss
|
|
@Environment(\.flixyAccent) private var accent
|
|
|
|
var body: some View {
|
|
NavigationStack {
|
|
List {
|
|
switch kind {
|
|
case .audio:
|
|
// **Die Spuren der Datei, nicht die des Players.** Rechnet
|
|
// der Server um, kennt der Player genau eine — die, für
|
|
// die er sich entschieden hat. Zur Auswahl steht, was in
|
|
// der Datei liegt.
|
|
ForEach(model.audioStreams) { stream in
|
|
row(
|
|
title: stream.name,
|
|
isSelected: model.selectedAudioStream == stream
|
|
) {
|
|
Task { await model.selectAudioStream(stream) }
|
|
dismiss()
|
|
}
|
|
}
|
|
|
|
if let gemerkt = model.rememberedTrackName {
|
|
Section {
|
|
Button(role: .destructive) {
|
|
model.forgetRememberedTrack()
|
|
} label: {
|
|
Label("player.forgetRememberedTrack", systemImage: "arrow.uturn.backward")
|
|
}
|
|
.listRowBackground(Color.flixySurface)
|
|
} header: {
|
|
Text("player.rememberedTrack")
|
|
} footer: {
|
|
// Eine automatische Handlung, die man nicht sehen
|
|
// kann, ist von einem Fehler nicht zu
|
|
// unterscheiden.
|
|
Text("player.rememberedTrackExplanation \(gemerkt)")
|
|
}
|
|
}
|
|
|
|
case .subtitles:
|
|
// „Aus" gehört in dieselbe Liste wie die Spuren, nicht als
|
|
// eigener Schalter daneben: es ist eine Auswahl unter
|
|
// mehreren, keine getrennte Eigenschaft.
|
|
row(
|
|
title: String(localized: "player.subtitlesOff"),
|
|
isSelected: model.selectedSubtitleTrack == nil
|
|
) {
|
|
model.selectSubtitleTrack(nil)
|
|
dismiss()
|
|
}
|
|
|
|
ForEach(model.subtitleTracks) { track in
|
|
row(
|
|
title: track.displayName,
|
|
isSelected: model.selectedSubtitleTrack == track
|
|
) {
|
|
model.selectSubtitleTrack(track)
|
|
dismiss()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.scrollContentBackground(.hidden)
|
|
.background(Color.flixyCanvas)
|
|
.navigationTitle(Text(kind.title))
|
|
.flixyInlineTitle()
|
|
.toolbar {
|
|
ToolbarItem(placement: .confirmationAction) {
|
|
Button("action.close") { dismiss() }
|
|
}
|
|
}
|
|
}
|
|
.flixyMediumSheet()
|
|
}
|
|
|
|
private func row(title: String, isSelected: Bool, action: @escaping () -> Void) -> some View {
|
|
Button(action: action) {
|
|
HStack {
|
|
Text(title)
|
|
.foregroundStyle(Color.flixyTextPrimary)
|
|
Spacer()
|
|
if isSelected {
|
|
Image(systemName: "checkmark").foregroundStyle(accent)
|
|
}
|
|
}
|
|
.contentShape(.rect)
|
|
}
|
|
.buttonStyle(.plain)
|
|
.listRowBackground(Color.flixySurface)
|
|
.accessibilityAddTraits(isSelected ? [.isButton, .isSelected] : .isButton)
|
|
}
|
|
}
|