Eine Folge weiter, eine zurück — ohne den Spieler zu verlassen
Wer eine Serie schaut, will blättern können. Bisher gab es genau einen Knopf dafür, und der erschien erst am Ende: „Nächste Folge", wenn der Abspann durchgelaufen war. Zurück ging gar nicht — nur über die Serienseite, und die beendet die Wiedergabe. Jetzt stehen beide Knöpfe in der Bedienleiste, in der App wie im Web, links und rechts vom Abspielknopf. Nur bei Serien: bei einem Film antwortet der Server mit 404, bei der letzten Folge mit 204, und beides heisst hier „kein Knopf". Sie erscheinen also von selbst nur dort, wo sie etwas tun. Der Server kannte nur den Weg vorwärts. Jetzt gibt es `/previous` daneben, und beide Richtungen kommen aus derselben Rechnung — als zwei Endpunkte abgeschrieben liefen sie auseinander, sobald einer etwas dazubekommt. Jellyfin hat kein Gegenstück zu „NextUp"; dort entsteht die vorige Folge aus der Folgenliste der Serie. Beim Wechsel gilt weiter, was für die Serie an Sprache eingestellt ist. Ohne das spränge die nächste Folge auf die erste Tonspur der Datei zurück — bei deutschen Veröffentlichungen also auf Deutsch, mitten in einer Serie, die man im Original schaut. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
bcbe5557d7
commit
acba683c94
@@ -239,9 +239,20 @@ public actor FlaroBackend: MediaBackend {
|
||||
}
|
||||
|
||||
public func nextEpisode(after item: MediaItem) async throws(MediaBackendError) -> MediaItem? {
|
||||
try await nachbar(von: item, weg: "next")
|
||||
}
|
||||
|
||||
public func previousEpisode(before item: MediaItem) async throws(MediaBackendError) -> MediaItem? {
|
||||
try await nachbar(von: item, weg: "previous")
|
||||
}
|
||||
|
||||
/// Die Folge vor oder nach dieser.
|
||||
///
|
||||
/// 204 heisst: es gibt keine. Kein Fehler, nur das Ende der Serie —
|
||||
/// oder ihr Anfang.
|
||||
private func nachbar(von item: MediaItem, weg: String) async throws(MediaBackendError) -> MediaItem? {
|
||||
guard item.kind == .episode else { return nil }
|
||||
// 204 heisst: es war die letzte. Kein Fehler, nur kein Nachfolger.
|
||||
let daten = try await client.send(FlaroRequest(path: "/api/v1/episodes/\(item.id)/next"))
|
||||
let daten = try await client.send(FlaroRequest(path: "/api/v1/episodes/\(item.id)/\(weg)"))
|
||||
guard !daten.isEmpty else { return nil }
|
||||
guard let folge = try? JSONDecoder().decode(Wire.Episode.self, from: daten) else { return nil }
|
||||
return folge.neutral()
|
||||
|
||||
@@ -348,6 +348,20 @@ public func isInLocalNetwork() async -> Bool {
|
||||
return next.first { $0.id != item.id }
|
||||
}
|
||||
|
||||
/// Die Folge davor.
|
||||
///
|
||||
/// **Über die Folgenliste der Serie, nicht über einen eigenen
|
||||
/// Endpunkt.** Jellyfin hat ein „NextUp", aber kein Gegenstück dazu —
|
||||
/// und die Liste gibt es ohnehin, weil die Serienseite sie zeigt. Eine
|
||||
/// Anfrage für einen Knopf, der selten gedrückt wird, ist der bessere
|
||||
/// Tausch gegenüber gar keinem Knopf.
|
||||
public func previousEpisode(before item: MediaItem) async throws(MediaBackendError) -> MediaItem? {
|
||||
guard let seriesID = item.seriesID else { return nil }
|
||||
let folgen = try await episodes(inSeries: seriesID)
|
||||
guard let stelle = folgen.firstIndex(where: { $0.id == item.id }), stelle > 0 else { return nil }
|
||||
return folgen[stelle - 1]
|
||||
}
|
||||
|
||||
// MARK: - Bilder
|
||||
|
||||
public nonisolated func artworkURL(path: String?, width: Int, height: Int) -> URL? {
|
||||
|
||||
@@ -88,6 +88,14 @@ public protocol MediaBackend: Sendable, Fortschrittsempfaenger {
|
||||
/// Die nächste Folge nach dieser, sofern es eine gibt.
|
||||
func nextEpisode(after item: MediaItem) async throws(MediaBackendError) -> MediaItem?
|
||||
|
||||
/// Die Folge davor — für den Rückwärtsknopf im Spieler.
|
||||
///
|
||||
/// **Mit Vorgabe, anders als die nächste.** Die nächste Folge braucht
|
||||
/// jeder Server: ohne sie gibt es kein Weiterlaufen am Ende. Die vorige
|
||||
/// ist eine Bequemlichkeit, und ein Server, der sie nicht kennt, soll
|
||||
/// deshalb nicht unbaubar werden.
|
||||
func previousEpisode(before item: MediaItem) async throws(MediaBackendError) -> MediaItem?
|
||||
|
||||
// MARK: - Bilder
|
||||
|
||||
/// Ein Bild in der gewünschten Größe.
|
||||
@@ -229,6 +237,9 @@ public struct MediaPage: Sendable, Equatable {
|
||||
}
|
||||
|
||||
public extension MediaBackend {
|
||||
/// Vorgabe: kein Weg zurück.
|
||||
func previousEpisode(before item: MediaItem) async throws(MediaBackendError) -> MediaItem? { nil }
|
||||
|
||||
/// Vorgabe: der Server kann es nicht.
|
||||
///
|
||||
/// So muss nicht jeder Server eine Adresse erfinden, die es bei ihm nicht
|
||||
|
||||
Reference in New Issue
Block a user