wai/wai-sink-swift: the native sink for the platforms whose native player is reached from Swift. WaiSink wraps the C interface (every call JSON in, JSON out); WaiRelay is the loopback server the player is given in place of the origin: it fetches each object, gives the sink a playlist with its receipt and the chain's parents, and answers the player only with bytes the sink presents (502 otherwise), every URI of a playlist rewritten to it; WaiPlayerSession drives the platform's player through it, observes it every 200 ms, logs its first frame, stalls and end, ends a session nobody can see, and posts the bundle to the owner's collector; WaiViewAttention reads the platform's signals of who can see the player. scripts/build-xcframework.sh builds WaiFFI.xcframework from wai-rs. Tests: the corpus through the wrapper, the relay over the reference floor, and the player end to end over a presentation made with the platform's own encoder. A native host's player and its requests run on threads of their own, so an object can reach the sink with a time before the log's last entry. delivery-telemetry §7.1 has a log's times never decrease and nothing after end or abandon: the sink logs such an entry at the last entry's time, and takes no entry after end or abandon, while an object received then is still verified and acknowledged (native-sink §5.2). The corpus gains log-order, and the second implementation checks both rules. Co-Authored-By: Claude Opus 5.5 <[email protected]>
33 lines
1.4 KiB
Swift
33 lines
1.4 KiB
Swift
// The owner's collector (stream-audit §12), as a player reaches it: a session's bundle to
|
|
// `v1/sessions` when it ends, and its Event-mode CMCD batches to `v1/cmcd` while it plays.
|
|
import Foundation
|
|
|
|
public final class WaiCollector: @unchecked Sendable {
|
|
public let base: URL
|
|
private let session: URLSession
|
|
|
|
public init(base: URL, session: URLSession = URLSession(configuration: .ephemeral)) {
|
|
self.base = base
|
|
self.session = session
|
|
}
|
|
|
|
/// Post a bundle; the collector's status and answer.
|
|
public func postSession(_ bundle: Data, completion: @escaping @Sendable (Int, Data?) -> Void = { _, _ in }) {
|
|
post("v1/sessions", type: "text/plain;charset=UTF-8", body: bundle, completion: completion)
|
|
}
|
|
|
|
/// Post an `application/cmcd` batch. Telemetry is advisory: a failure is dropped
|
|
/// (stream-audit §7.2).
|
|
public func postCMCD(_ batch: String) {
|
|
guard !batch.isEmpty else { return }
|
|
post("v1/cmcd", type: "application/cmcd", body: Data(batch.utf8)) { _, _ in }
|
|
}
|
|
|
|
private func post(_ path: String, type: String, body: Data, completion: @escaping @Sendable (Int, Data?) -> Void) {
|
|
var r = URLRequest(url: base.appendingPathComponent(path))
|
|
r.httpMethod = "POST"
|
|
r.setValue(type, forHTTPHeaderField: "Content-Type")
|
|
r.httpBody = body
|
|
session.dataTask(with: r) { d, resp, _ in completion((resp as? HTTPURLResponse)?.statusCode ?? 0, d) }.resume()
|
|
}
|
|
}
|