Untertitel lassen sich jetzt einstellen
Groesse in vier Stufen, Schrift und Hintergrund als fuenf feste Paare — Weiss, Weiss auf Schwarz, Schwarz auf Weiss, Gelb, Gelb auf Schwarz. Feste Paare statt zweier Regler, weil unabhaengige Achsen unlesbare Mischungen zulassen: Schwarz auf durchsichtig ueber einer Nachtszene ist eine gueltige Einstellung und eine wertlose. Die Uebersetzung nach AVFoundation ist eine Tabelle aus Schluesseln, bei der jeder Fehler lautlos ist — ein falscher Schluessel laesst den Initialisierer `nil` zurueckgeben, und dann gibt es gar keine Regel. Auf dem Bildschirm sieht „keine Regel\" genauso aus wie „Regel ohne Wirkung\". Zehn Tests decken das ab, darunter die drei Fallen: Alpha zuerst in der Farbe, Prozentwert statt Punktgroesse, und der Hintergrund auch fuer „durchsichtig\" ausdruecklich gesetzt statt weggelassen — weggelassen gilt sonst, was Jellyfins WebVTT selbst mitbringt, und das ist oft ein schwarzer Kasten. Als Protokollmethode, nicht als Feld an `PlaybackSource`: die wird einmal beim Oeffnen gelesen, und eine Einstellung, die erst beim naechsten Film wirkt, haelt jeder fuer kaputt. Angewandt wird an zwei Stellen — am laufenden Gegenstand und am frisch angelegten. Nur das erste zu tun hiesse, dass die naechste Folge wieder in der Vorgabe erscheint. VLC erbt die leere Vorgabe. Die Einstellungsseite sagt beides: dass es fuer den Standardplayer gilt, und dass eingebrannte Bilduntertitel von Blu-rays sich nicht gestalten lassen, weil es dort keinen Text gibt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
a1ff5cb7bb
commit
0815432efd
@@ -17,6 +17,7 @@ struct RootView: View {
|
||||
@Environment(PlaybackSettings.self) private var settings
|
||||
@Environment(LanguagePreferences.self) private var languages
|
||||
@Environment(QuickPlay.self) private var quickPlay
|
||||
@Environment(SubtitleAppearance.self) private var subtitleAppearance
|
||||
@Environment(NetworkConditions.self) private var network
|
||||
@Environment(DownloadCoordinator.self) private var downloads
|
||||
@Environment(PlaybackDiagnostics.self) private var diagnostics
|
||||
@@ -201,6 +202,7 @@ struct RootView: View {
|
||||
policy: settings.policy(for: network),
|
||||
settings: settings,
|
||||
languages: languages,
|
||||
subtitles: subtitleAppearance,
|
||||
// **Nicht mehr über `server`.** Der ist ein `PlexServer` und bei
|
||||
// Jellyfin immer `nil` — damit galt dort jede Wiedergabe als
|
||||
// entfernt, auch zuhause am Kabel, und „Auto" hätte ein
|
||||
|
||||
@@ -178,6 +178,7 @@ final class PlayerModel {
|
||||
private let policy: PlaybackPolicy
|
||||
private let settings: PlaybackSettings
|
||||
private let languages: LanguagePreferences
|
||||
private let subtitles: SubtitleAppearance
|
||||
private let isLocalConnection: Bool
|
||||
/// Wie die Verbindung in der Diagnose heißt.
|
||||
///
|
||||
@@ -410,6 +411,7 @@ final class PlayerModel {
|
||||
policy: PlaybackPolicy,
|
||||
settings: PlaybackSettings,
|
||||
languages: LanguagePreferences,
|
||||
subtitles: SubtitleAppearance,
|
||||
isLocalConnection: Bool,
|
||||
connectionDescription: String?,
|
||||
unavailableMessage: String
|
||||
@@ -422,6 +424,7 @@ final class PlayerModel {
|
||||
self.policy = policy
|
||||
self.settings = settings
|
||||
self.languages = languages
|
||||
self.subtitles = subtitles
|
||||
self.isLocalConnection = isLocalConnection
|
||||
self.connectionDescription = connectionDescription
|
||||
self.unavailableMessage = unavailableMessage
|
||||
@@ -665,6 +668,9 @@ final class PlayerModel {
|
||||
self.engine = engine
|
||||
attachSurface(to: engine)
|
||||
observe(engine)
|
||||
// **Vor dem Öffnen.** Ein frisch angelegter Gegenstand ist
|
||||
// ungestaltet, und der erste Untertitel käme sonst in der Vorgabe.
|
||||
engine.applySubtitleStyle(subtitles.style)
|
||||
|
||||
do {
|
||||
diagnostics.note("Engine öffnet Quelle")
|
||||
|
||||
@@ -17,6 +17,7 @@ struct PlayerView: View {
|
||||
@Environment(FlixySession.self) private var session
|
||||
@Environment(PlaybackCoordinator.self) private var playback
|
||||
@Environment(PlaybackSettings.self) private var settings
|
||||
@Environment(SubtitleAppearance.self) private var subtitleAppearance
|
||||
@Environment(NetworkConditions.self) private var network
|
||||
@Environment(DownloadCoordinator.self) private var downloads
|
||||
@Environment(PlaybackDiagnostics.self) private var diagnostics
|
||||
@@ -273,6 +274,13 @@ struct PlayerView: View {
|
||||
showsControls = true
|
||||
scheduleControlsHide()
|
||||
}
|
||||
// **Mitten im Film.** Eine Einstellung, die erst beim nächsten
|
||||
// Titel wirkt, hält jeder für kaputt — man stellt um und sieht
|
||||
// nichts. `SubtitleStyle` ist vergleichbar, das feuert nur bei einer
|
||||
// echten Änderung.
|
||||
.onChange(of: subtitleAppearance.style) { _, neu in
|
||||
model.engine?.applySubtitleStyle(neu)
|
||||
}
|
||||
.task { scheduleControlsHide() }
|
||||
.onDisappear { hideTask?.cancel() }
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ enum SettingsPalette {
|
||||
static let home = Color.green
|
||||
static let playback = Color.pink
|
||||
static let languages = Color.teal
|
||||
static let subtitles = Color.yellow
|
||||
static let downloads = Color.indigo
|
||||
static let diagnostics = Color.orange
|
||||
}
|
||||
|
||||
@@ -51,6 +51,9 @@ struct SettingsView: View {
|
||||
link("languages.title", symbol: "captions.bubble", colour: SettingsPalette.languages) {
|
||||
LanguagesView()
|
||||
}
|
||||
link("subtitleStyle.title", symbol: "textformat.size", colour: SettingsPalette.subtitles) {
|
||||
SubtitleAppearanceView()
|
||||
}
|
||||
link("sidebar.downloads", symbol: "arrow.down.circle", colour: SettingsPalette.downloads) {
|
||||
DownloadSettingsView()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
import SwiftUI
|
||||
import FlixyDesign
|
||||
import FlixyPlayback
|
||||
|
||||
/// Wie Untertitel aussehen sollen.
|
||||
struct SubtitleAppearanceView: View {
|
||||
@Environment(SubtitleAppearance.self) private var appearance
|
||||
@Environment(\.flixyAccent) private var accent
|
||||
|
||||
var body: some View {
|
||||
@Bindable var appearance = appearance
|
||||
|
||||
Form {
|
||||
SettingsHeader(
|
||||
symbol: "textformat.size",
|
||||
colour: SettingsPalette.subtitles,
|
||||
title: "subtitleStyle.title",
|
||||
explanation: "subtitleStyle.headerExplanation"
|
||||
)
|
||||
|
||||
Section {
|
||||
preview
|
||||
} header: {
|
||||
Text("subtitleStyle.previewHeader")
|
||||
}
|
||||
|
||||
Section {
|
||||
Picker("subtitleStyle.size", selection: $appearance.size) {
|
||||
ForEach(SubtitleStyle.Size.allCases) { size in
|
||||
Text(size.label).tag(size)
|
||||
}
|
||||
}
|
||||
} footer: {
|
||||
Text("subtitleStyle.sizeExplanation")
|
||||
}
|
||||
|
||||
Section {
|
||||
Picker("subtitleStyle.palette", selection: $appearance.palette) {
|
||||
ForEach(SubtitleStyle.Palette.allCases) { palette in
|
||||
Text(palette.label).tag(palette)
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
Text("subtitleStyle.paletteHeader")
|
||||
} footer: {
|
||||
// Die Grenze gehört hierher, nicht nur in einen Kommentar:
|
||||
// bei eingebrannten Untertiteln tut jede dieser Einstellungen
|
||||
// sichtbar nichts, und ohne Erklärung sieht das nach einem
|
||||
// Fehler aus.
|
||||
Text("subtitleStyle.paletteExplanation")
|
||||
}
|
||||
}
|
||||
.tint(accent)
|
||||
.flixyForm()
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(Color.flixyCanvas)
|
||||
.navigationTitle(Text("subtitleStyle.title"))
|
||||
.flixyInlineTitle()
|
||||
}
|
||||
|
||||
/// Ein Beispielsatz über grauer Fläche.
|
||||
///
|
||||
/// **Eine Nachbildung, nicht AVFoundations Renderer.** Sie zeigt die
|
||||
/// Wirkung der Einstellung, nicht das Bild auf den Punkt genau — wer
|
||||
/// später zwei Pixel „repariert", repariert die falsche Sache.
|
||||
private var preview: some View {
|
||||
ZStack {
|
||||
// Mittelgrau: auf Weiss wäre weisse Schrift unsichtbar, auf
|
||||
// Schwarz schwarze. Ein Grauton zeigt beide ehrlich.
|
||||
LinearGradient(
|
||||
colors: [Color(white: 0.35), Color(white: 0.18)],
|
||||
startPoint: .top,
|
||||
endPoint: .bottom
|
||||
)
|
||||
|
||||
Text("subtitleStyle.previewText")
|
||||
.font(.system(size: 15 * CGFloat(appearance.size.rawValue) / 100, weight: .medium))
|
||||
.foregroundStyle(appearance.style.foreground.color)
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 3)
|
||||
.background(appearance.style.background.color)
|
||||
.shadow(
|
||||
color: appearance.style.wantsEdgeShadow ? .black.opacity(0.9) : .clear,
|
||||
radius: 2,
|
||||
y: 1
|
||||
)
|
||||
.padding(FlixySpacing.regular)
|
||||
}
|
||||
.frame(height: 96)
|
||||
.clipShape(.rect(cornerRadius: FlixyRadius.card))
|
||||
.listRowInsets(EdgeInsets())
|
||||
.listRowBackground(Color.clear)
|
||||
}
|
||||
}
|
||||
|
||||
extension SubtitleStyle.RGBA {
|
||||
var color: Color {
|
||||
Color(.sRGB, red: red, green: green, blue: blue, opacity: alpha)
|
||||
}
|
||||
}
|
||||
|
||||
/// Die Namen der Stufen stehen neben der Ansicht, nicht im Modell — dasselbe
|
||||
/// Muster wie bei `PlaybackSettings.Quality`.
|
||||
extension SubtitleStyle.Size {
|
||||
var label: LocalizedStringKey {
|
||||
switch self {
|
||||
case .small: "subtitleStyle.size.small"
|
||||
case .normal: "subtitleStyle.size.normal"
|
||||
case .large: "subtitleStyle.size.large"
|
||||
case .huge: "subtitleStyle.size.huge"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension SubtitleStyle.Palette {
|
||||
var label: LocalizedStringKey {
|
||||
switch self {
|
||||
case .whiteOnClear: "subtitleStyle.palette.whiteOnClear"
|
||||
case .whiteOnBlack: "subtitleStyle.palette.whiteOnBlack"
|
||||
case .blackOnWhite: "subtitleStyle.palette.blackOnWhite"
|
||||
case .yellowOnClear: "subtitleStyle.palette.yellowOnClear"
|
||||
case .yellowOnBlack: "subtitleStyle.palette.yellowOnBlack"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user