Der Vorrat steigt jetzt auch eine Ebene tiefer
Bei „Auto\" bestellen wir eine Liste von Stufen; die Schnipsel stehen eine Ebene darunter. Der Speicher erkannte das zwar, stieg dann aber aus und reichte die Liste mit relativen Adressen durch — die zeigten danach auf den lokalen Server, wo es sie nicht gibt. Es wurde nichts vorgeholt, und beinahe waere auch nichts mehr gelaufen. Jetzt wird auch die Stufenliste umgeschrieben. Holt der Player eine Stufe, kommt er wieder bei uns an, und dort beginnt das Vorauslaufen — bei der Stufe, fuer die er sich entschieden hat. Dazu eine Zeile im Qualitaetsblatt, die sagt, ob der Vorrat laeuft und wie weit er voraus ist. Von aussen ist das nicht zu sehen: laeuft er nicht, spielt es trotzdem, nur ohne Vorsprung — und genau so sieht auch ein Vorrat aus, der laeuft und nichts bekommt. Ohne diese Zeile sind die beiden Faelle nicht zu unterscheiden, und ich haette wieder geraten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
134dc82927
commit
bbd1c3b572
@@ -24,11 +24,28 @@ public actor LocalStreamServer {
|
||||
private static let segmentPrefix = "/s/"
|
||||
/// Der Pfad der Abspielliste selbst.
|
||||
private static let playlistPath = "/playlist.m3u8"
|
||||
/// Der Pfad einer Stufe, die eine Ebene tiefer liegt.
|
||||
///
|
||||
/// Die Endung muss stimmen: AVPlayer entscheidet am Namen, ob er eine
|
||||
/// Abspielliste oder Bild erwartet.
|
||||
private static let levelPrefix = "/level.m3u8"
|
||||
|
||||
/// Wie eine Adresse aus der Abspielliste beim Server heisst.
|
||||
///
|
||||
/// Der ursprüngliche Pfad wandert dabei in die Abfrage, damit Schrägstriche
|
||||
/// und Punkte darin nichts anrichten.
|
||||
/// Wie eine Stufe aus der Stufenliste beim Server heisst.
|
||||
///
|
||||
/// Vollständig, nicht relativ: eine Stufe liegt beim Server unter einem
|
||||
/// ganz anderen Pfad als bei uns, und relativ aufgelöst zeigte sie ins
|
||||
/// Leere.
|
||||
public nonisolated static func localPlaylistPath(for absolute: String) -> String {
|
||||
var teile = URLComponents()
|
||||
teile.path = levelPrefix
|
||||
teile.queryItems = [URLQueryItem(name: "u", value: absolute)]
|
||||
return teile.string ?? levelPrefix
|
||||
}
|
||||
|
||||
public nonisolated static func localPath(for original: String) -> String {
|
||||
var teile = URLComponents()
|
||||
teile.path = segmentPrefix
|
||||
@@ -134,6 +151,18 @@ public actor LocalStreamServer {
|
||||
let pfadTeil = anfrage.components(separatedBy: " ").dropFirst().first
|
||||
else { return finish(verbindung, status: 400, body: Data()) }
|
||||
|
||||
if pfadTeil.hasPrefix(Self.levelPrefix) {
|
||||
guard let teile = URLComponents(string: pfadTeil),
|
||||
let adresse = teile.queryItems?.first(where: { $0.name == "u" })?.value,
|
||||
let inhalt = await cache.playlist(at: adresse)
|
||||
else { return finish(verbindung, status: 404, body: Data()) }
|
||||
|
||||
return finish(
|
||||
verbindung, status: 200, body: Data(inhalt.utf8),
|
||||
type: "application/vnd.apple.mpegurl"
|
||||
)
|
||||
}
|
||||
|
||||
if pfadTeil.hasPrefix(Self.playlistPath) {
|
||||
let inhalt = Data((playlist ?? "").utf8)
|
||||
return finish(
|
||||
|
||||
@@ -42,6 +42,7 @@ public actor StreamCache {
|
||||
|
||||
/// Woher die Schnipsel wirklich kommen.
|
||||
private var upstream: URL?
|
||||
private var headers: [String: String] = [:]
|
||||
/// Die Liste, in der Reihenfolge des Servers.
|
||||
private var segments: [String] = []
|
||||
private var targetDuration: Duration = .seconds(6)
|
||||
@@ -77,23 +78,55 @@ public actor StreamCache {
|
||||
/// Nimmt die Abspielliste entgegen und beginnt vorauszulaufen.
|
||||
///
|
||||
/// Gibt die umgeschriebene Liste zurück — die, die der Player bekommt.
|
||||
/// Ist es eine Liste von Stufen, wird nichts vorgeholt: dort stehen keine
|
||||
/// Schnipsel, sondern weitere Listen.
|
||||
///
|
||||
/// **Zwei Ebenen, und beide müssen durch uns.** Bei „Auto" bestellen wir
|
||||
/// eine Liste von **Stufen**; die Schnipsel stehen eine Ebene tiefer. Wer
|
||||
/// hier aussteigt, reicht relative Adressen durch, die danach auf den
|
||||
/// lokalen Server zeigen — und dort gibt es sie nicht. Genau daran ist
|
||||
/// der erste Anlauf gescheitert: es wurde nichts vorgeholt, und beinahe
|
||||
/// wäre auch nichts mehr gelaufen.
|
||||
///
|
||||
/// Also wird auch die Stufenliste umgeschrieben. Holt der Player dann
|
||||
/// eine Stufe, kommt er wieder bei uns an, und **dort** beginnt das
|
||||
/// Vorauslaufen — bei der Stufe, für die er sich entschieden hat.
|
||||
public func start(upstream url: URL, headers: [String: String] = [:]) async throws -> String {
|
||||
upstream = url
|
||||
self.headers = headers
|
||||
let (liste, _) = try await load(url)
|
||||
|
||||
guard !liste.isMaster else {
|
||||
return liste.rewritingReferences { eintrag in
|
||||
let absolut = URL(string: eintrag, relativeTo: url)?.absoluteString ?? eintrag
|
||||
return LocalStreamServer.localPlaylistPath(for: absolut)
|
||||
}
|
||||
}
|
||||
return adopt(liste, from: url)
|
||||
}
|
||||
|
||||
/// Eine Stufe, die der Player angefordert hat.
|
||||
///
|
||||
/// Ab hier stehen die Schnipsel fest, und der Vorrat beginnt zu laufen.
|
||||
public func playlist(at address: String) async -> String? {
|
||||
guard let url = URL(string: address), let (liste, _) = try? await load(url) else { return nil }
|
||||
guard !liste.isMaster else { return liste.rewritingReferences { $0 } }
|
||||
return adopt(liste, from: url)
|
||||
}
|
||||
|
||||
/// Übernimmt eine Liste von Schnipseln als die, der wir vorauslaufen.
|
||||
private func adopt(_ liste: MediaPlaylist, from url: URL) -> String {
|
||||
upstream = url
|
||||
segments = liste.segments
|
||||
if let dauer = liste.targetDuration { targetDuration = dauer }
|
||||
lastRequested = 0
|
||||
fetched = 0
|
||||
beginPrefetching()
|
||||
return liste.rewritingReferences { LocalStreamServer.localPath(for: $0) }
|
||||
}
|
||||
|
||||
private func load(_ url: URL) async throws -> (MediaPlaylist, Data) {
|
||||
var request = URLRequest(url: url)
|
||||
for (name, wert) in headers { request.setValue(wert, forHTTPHeaderField: name) }
|
||||
let (daten, _) = try await session.data(for: request)
|
||||
|
||||
let liste = MediaPlaylist(String(decoding: daten, as: UTF8.self))
|
||||
guard !liste.isMaster else { return liste.rewritingReferences { $0 } }
|
||||
|
||||
segments = liste.segments
|
||||
if let dauer = liste.targetDuration { targetDuration = dauer }
|
||||
beginPrefetching()
|
||||
|
||||
return liste.rewritingReferences { LocalStreamServer.localPath(for: $0) }
|
||||
return (MediaPlaylist(String(decoding: daten, as: UTF8.self)), daten)
|
||||
}
|
||||
|
||||
// MARK: - Ausliefern
|
||||
|
||||
Reference in New Issue
Block a user