65 lines
2.3 KiB
Swift
65 lines
2.3 KiB
Swift
// HTTP client for the plum-control-bridge. Phase-1 surface: list shows, and
|
|
// build a raw-file stream URL for VLCKit. Auth (when the bridge has a token set)
|
|
// rides as a Bearer header for JSON and a ?token= query for the stream URL,
|
|
// because VLCKit is handed a bare URL with no header hook.
|
|
|
|
import Foundation
|
|
|
|
enum BridgeError: LocalizedError {
|
|
case badStatus(Int)
|
|
case transport(String)
|
|
|
|
var errorDescription: String? {
|
|
switch self {
|
|
case .badStatus(let code): return "Bridge returned HTTP \(code)."
|
|
case .transport(let msg): return msg
|
|
}
|
|
}
|
|
}
|
|
|
|
struct BridgeClient {
|
|
let baseURL: URL
|
|
let token: String?
|
|
|
|
func fetchShows(refresh: Bool = false) async throws -> [BridgeShow] {
|
|
var comps = URLComponents(
|
|
url: baseURL.appendingPathComponent("library").appendingPathComponent("shows"),
|
|
resolvingAgainstBaseURL: false
|
|
)!
|
|
if refresh { comps.queryItems = [URLQueryItem(name: "refresh", value: "1")] }
|
|
|
|
var request = URLRequest(url: comps.url!)
|
|
request.timeoutInterval = 30
|
|
if let token { request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") }
|
|
|
|
let data: Data
|
|
let response: URLResponse
|
|
do {
|
|
(data, response) = try await URLSession.shared.data(for: request)
|
|
} catch {
|
|
throw BridgeError.transport(error.localizedDescription)
|
|
}
|
|
guard let http = response as? HTTPURLResponse else {
|
|
throw BridgeError.transport("No HTTP response.")
|
|
}
|
|
guard http.statusCode == 200 else {
|
|
throw BridgeError.badStatus(http.statusCode)
|
|
}
|
|
return try JSONDecoder().decode(ShowsResponse.self, from: data).shows
|
|
}
|
|
|
|
/// URL VLCKit (or a download task) reads the raw video from. The episode id
|
|
/// is base64url, hence already path-safe — no further escaping needed.
|
|
func streamURL(episodeId: String) -> URL {
|
|
var comps = URLComponents(
|
|
url: baseURL.appendingPathComponent("stream").appendingPathComponent(episodeId),
|
|
resolvingAgainstBaseURL: false
|
|
)!
|
|
if let token { comps.queryItems = [URLQueryItem(name: "token", value: token)] }
|
|
return comps.url!
|
|
}
|
|
|
|
func healthURL() -> URL {
|
|
baseURL.appendingPathComponent("healthz")
|
|
}
|
|
}
|