Phase 2: Transcoding, adaptive Bitrate, Spuren, Marker, Sync

Der Decider brauchte keine Änderung — nur eine andere Richtlinie.
Genau dafür war er gebaut.

Transcoding
- TranscodeRequest baut die Parameter für /video/:/transcode/universal.
  directStream=1 lässt Plex nur umpacken, wenn der Bildcodec passt,
  statt pauschal neu zu kodieren.
- Vor dem Start wird der Server gefragt (decision); sagt er nein, steht
  der Grund im Klartext da statt eines schwarzen Bildes.
- Beim Verlassen wird der Transcoder angehalten. Ohne dieses Signal
  rechnet der Server minutenlang für niemanden weiter.
- Die adaptive Bitrate kommt mit HLS: AVPlayer wechselt sie selbst.

Qualitätseinstellungen
- Getrennt für WLAN und Mobilfunk, fünf spürbare Stufen.
  Voreinstellung: zuhause Original, unterwegs 4 Mbit.
- NetworkConditions erkennt Mobilfunk über isExpensive — das deckt auch
  den Hotspot ab, der technisch WLAN ist und trotzdem Volumen kostet.

Spuren und Marken
- Ton- und Untertitelwahl im Player, bevorzugte Sprache in den
  Einstellungen, nachsichtiger Sprachvergleich (de/deu/ger/de-DE).
- Intro- und Abspann-Marken mit Überspringen-Knopf, optional
  automatisch. Bereits übersprungene Marken werden gemerkt, sonst käme
  man nach einem Rücksprung nie ins Intro zurück.
- Nächste Folge inkl. Staffelwechsel; Specials in Staffel 0 werden
  nicht vorgezogen.

Fortschritt
- Alle zehn Sekunden an den Server, plus Schlussmeldung beim Verlassen.
  Erst die nimmt den Titel aus „wird gerade abgespielt" heraus.
- markWatched/markUnwatched/rate/refreshLibrary stehen bereit.

86 Tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Guido
2026-09-11 17:24:17 +02:00
co-authored by Claude Opus 5
parent d0a84f35de
commit 55bcfe2fbf
13 changed files with 1512 additions and 53 deletions
+37
View File
@@ -1,5 +1,6 @@
import SwiftUI
import FlixyDesign
import FlixyPlayback
import PlexKit
/// Die Einstellungen.
@@ -9,6 +10,7 @@ import PlexKit
/// es das Geregelte auch gibt.
struct SettingsView: View {
@Environment(FlixySession.self) private var session
@Environment(PlaybackSettings.self) private var settings
@State private var showsSignOutConfirmation = false
var body: some View {
@@ -29,6 +31,29 @@ struct SettingsView: View {
}
}
Section {
Picker("settings.qualityWifi", selection: Bindable(settings).wifiQuality) {
ForEach(PlaybackSettings.Quality.allCases) { quality in
Text(quality.label).tag(quality)
}
}
Picker("settings.qualityCellular", selection: Bindable(settings).cellularQuality) {
ForEach(PlaybackSettings.Quality.allCases) { quality in
Text(quality.label).tag(quality)
}
}
} header: {
Text("settings.quality")
} footer: {
Text("settings.qualityExplanation")
}
Section("settings.playback") {
Toggle("settings.autoSkipIntro", isOn: Bindable(settings).autoSkipIntro)
Toggle("settings.autoSkipCredits", isOn: Bindable(settings).autoSkipCredits)
Toggle("settings.autoPlayNext", isOn: Bindable(settings).autoPlayNext)
}
Section("settings.app") {
LabeledContent("settings.version", value: Branding.version)
}
@@ -63,3 +88,15 @@ struct SettingsView: View {
return String(localized: "settings.connectionRemote")
}
}
extension PlaybackSettings.Quality {
var label: LocalizedStringKey {
switch self {
case .original: "quality.original"
case .high: "quality.high"
case .medium: "quality.medium"
case .low: "quality.low"
case .minimal: "quality.minimal"
}
}
}