web/src/native.ts (#45) has handed playback to a host since September, and there was no host in this repo: the earlier app, rays/ipodderx-app, grew native lists beside the page and is left behind. This one is the page and a player: a WKWebView of ipx-web, AVPlayer behind the bridge with the audio session to play on locked, now playing and the remote commands (15 back, 30 forward, scrubbing), and positions and read written from the player's own clock, with DeadAir's copy named so the server reads the position on its clock. Built with XcodeGen on a Mac; nothing here compiles the Swift. Its icon is web/logo.svg at 1024. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RqkrVDqdiQEMWAkNGQ2tYA
44 lines
1.9 KiB
Swift
44 lines
1.9 KiB
Swift
import Foundation
|
|
|
|
/// Which ipx-web the app shows. Asked for on first launch; shaking the phone asks again.
|
|
/// `-ipx.server <url>` on the launch arguments overrides it for that run (UserDefaults' argument
|
|
/// domain), which is how a test points the app somewhere else.
|
|
enum Server {
|
|
private static let key = "ipx.server"
|
|
static let suggested = "https://ipodderx.sdf1.net"
|
|
|
|
static var url: URL? {
|
|
get { UserDefaults.standard.string(forKey: key).flatMap(normalise) }
|
|
set { UserDefaults.standard.set(newValue?.absoluteString, forKey: key) }
|
|
}
|
|
|
|
/// What someone typed, as an address: a bare host gets https. Plain http is allowed only on
|
|
/// your own network, which is all NSAllowsLocalNetworking lets through, so it is refused here
|
|
/// rather than failing later and looking like the server is down.
|
|
static func normalise(_ text: String) -> URL? {
|
|
var s = text.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
if s.isEmpty { return nil }
|
|
if !s.contains("://") { s = "https://" + s }
|
|
guard let u = URL(string: s), let host = u.host, !host.isEmpty else { return nil }
|
|
switch u.scheme {
|
|
case "https": return u
|
|
case "http": return isLocal(host) ? u : nil
|
|
default: return nil
|
|
}
|
|
}
|
|
|
|
static func isLocal(_ host: String) -> Bool {
|
|
if host == "localhost" || host.hasSuffix(".local") { return true }
|
|
let p = host.split(separator: ".").compactMap { Int($0) }
|
|
guard p.count == 4 else { return false }
|
|
return p[0] == 10 || p[0] == 127 || (p[0] == 192 && p[1] == 168)
|
|
|| (p[0] == 172 && (16...31).contains(p[1]))
|
|
}
|
|
|
|
/// A path the page sent (`/media/42`), against the server.
|
|
static func resolve(_ path: String) -> URL? {
|
|
guard let base = url else { return nil }
|
|
return URL(string: path, relativeTo: base)?.absoluteURL
|
|
}
|
|
}
|