Sprachwahl in der App — bei Serien und bei Filmen
Auf der Serienseite stand ein Menü für den Ton, gefüllt mit zwanzig Sprachen, von denen die Serie zwei hatte. Für Untertitel gab es keines, und auf der Filmseite überhaupt nichts. Jetzt beides an beiden Orten, und zwar nur mit dem, was es wirklich gibt: Bei einer **Serie** über den neuen Endpunkt — die Schnittmenge über alle Folgen. Was dadurch wegfällt, steht als Satz darunter, sonst sähe eine gekürzte Liste aus wie die ganze Wahrheit. Bei einem **Film** aus den Spuren der Datei. Kein Serverweg nötig: dort ist die Datei der Titel, und ihre Spuren stehen in der Antwort, die die Seite ohnehin geholt hat. Ton und Untertitel teilen sich einen Typ. Sie unterscheiden sich in genau zwei Punkten — woher der Wert kommt und ob es „Keine" gibt; ein zweiter Typ wäre dieselbe Datei mit zwei geänderten Zeilen. „Keine" gibt es nur beim Untertitel: ein Film ohne Tonspur ist kein Wunsch, sondern ein Fehler. Die Untertitelsprache liegt je Titel, in einem Topf für Serien und Filme — eine Filmkennung kollidiert mit keiner Serie, und zwei Wörterbücher für dieselbe Frage liefen auseinander. Der Spieler liest sie jetzt: vorher kannte er nur die allgemeine, und eine Serie im Original mit deutschen Untertiteln liess sich nicht einstellen, ohne sie überall einzuschalten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
95d7eee8c3
commit
84612335ec
@@ -147,6 +147,18 @@ public actor FlaroBackend: MediaBackend {
|
||||
items: titel)
|
||||
}
|
||||
|
||||
public func availableLanguages(inSeries id: String) async -> AvailableLanguages? {
|
||||
guard let roh = try? await client.send(
|
||||
FlaroRequest(path: "/api/v1/series/\(id)/languages"),
|
||||
as: Wire.Sprachauswahl.self)
|
||||
else { return nil }
|
||||
|
||||
return AvailableLanguages(
|
||||
audio: roh.audio.map { LanguageOption(code: $0.code, name: $0.name) },
|
||||
subtitles: roh.subtitles.map { LanguageOption(code: $0.code, name: $0.name) },
|
||||
partial: roh.partial)
|
||||
}
|
||||
|
||||
public func items(
|
||||
inLibrary library: MediaLibrary,
|
||||
query: MediaQuery,
|
||||
|
||||
@@ -212,6 +212,17 @@ enum Wire {
|
||||
let subtitleStreams: [Stromspur]
|
||||
}
|
||||
|
||||
struct Sprachangabe: Decodable, Sendable {
|
||||
let code: String
|
||||
let name: String
|
||||
}
|
||||
|
||||
struct Sprachauswahl: Decodable, Sendable {
|
||||
let audio: [Sprachangabe]
|
||||
let subtitles: [Sprachangabe]
|
||||
let partial: Int
|
||||
}
|
||||
|
||||
struct LoginRequest: Encodable, Sendable {
|
||||
let userName: String
|
||||
let password: String
|
||||
|
||||
@@ -60,6 +60,9 @@ public protocol MediaBackend: Sendable {
|
||||
/// dreizehn Anfragen — für einen Klick auf eine Kachel ist das zu viel.
|
||||
/// Ein Server, der es in einer Anfrage kann, soll das dürfen.
|
||||
func episodes(inSeries id: String) async throws(MediaBackendError) -> [MediaItem]
|
||||
|
||||
/// Welche Sprachen eine Serie durchgehend führt — für die Vorwahl.
|
||||
func availableLanguages(inSeries id: String) async -> AvailableLanguages?
|
||||
/// Die nächste Folge nach dieser, sofern es eine gibt.
|
||||
func nextEpisode(after item: MediaItem) async throws(MediaBackendError) -> MediaItem?
|
||||
|
||||
@@ -227,6 +230,13 @@ public extension MediaBackend {
|
||||
subtitleStreamIndex: Int?
|
||||
) -> URL? { nil }
|
||||
|
||||
/// Welche Sprachen eine Serie durchgehend führt.
|
||||
///
|
||||
/// **`nil` heisst: der Server weiss es nicht.** Dann bleibt es bei der
|
||||
/// vollen Liste — eine leere Auswahl anzubieten wäre schlimmer als eine
|
||||
/// zu lange.
|
||||
func availableLanguages(inSeries id: String) async -> AvailableLanguages? { nil }
|
||||
|
||||
/// Der allgemeine Weg: Staffeln, dann je Staffel die Folgen.
|
||||
///
|
||||
/// Langsam, aber überall gültig — wer es besser kann, überschreibt es.
|
||||
@@ -261,3 +271,41 @@ public struct MediaFilterOption: Sendable, Equatable, Identifiable {
|
||||
self.title = title
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Eine Sprache, wie sie in einem Menü steht.
|
||||
public struct LanguageOption: Sendable, Equatable, Identifiable, Hashable {
|
||||
public let code: String
|
||||
public let name: String
|
||||
|
||||
public var id: String { code }
|
||||
|
||||
public init(code: String, name: String) {
|
||||
self.code = code
|
||||
self.name = name
|
||||
}
|
||||
}
|
||||
|
||||
/// Welche Sprachen ein Titel durchgehend führt.
|
||||
///
|
||||
/// **Durchgehend, nicht irgendwo.** Bei einer Serie ist das die
|
||||
/// Schnittmenge über alle Folgen: eine Sprache anzubieten, die es nur in
|
||||
/// Staffel 1 gibt, hiesse eine Einstellung für die Serie, die ab Staffel 2
|
||||
/// stillschweigend etwas anderes tut.
|
||||
public struct AvailableLanguages: Sendable, Equatable {
|
||||
public let audio: [LanguageOption]
|
||||
public let subtitles: [LanguageOption]
|
||||
|
||||
/// Wie viele Sprachen es nur in einem Teil der Folgen gibt.
|
||||
///
|
||||
/// **Damit die Oberfläche es sagen kann.** Eine gekürzte Liste sieht
|
||||
/// sonst aus wie die ganze Wahrheit, und wer eine Sprache vermisst, die
|
||||
/// er in Staffel 1 gesehen hat, hält es für einen Fehler.
|
||||
public let partial: Int
|
||||
|
||||
public init(audio: [LanguageOption], subtitles: [LanguageOption], partial: Int) {
|
||||
self.audio = audio
|
||||
self.subtitles = subtitles
|
||||
self.partial = partial
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user