All checks were successful
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (push) Successful in 15s
WAI operation catalogue / does this change touch it (push) Successful in 16s
WAI examples / WAI examples · does this change touch them (push) Successful in 15s
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (push) Successful in 50s
wai-sink-swift native sink package / does this change touch it (push) Successful in 2m29s
wai-sink-android native sink library / does this change touch it (push) Successful in 2m39s
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (push) Successful in 12m29s
WAI operation catalogue / the catalogue and every door · linux (push) Successful in 13m50s
WAI examples / WAI examples · portable · linux (push) Successful in 14m26s
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (push) Successful in 5m48s
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (push) Successful in 5m29s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (push) Successful in 12m1s
WAI examples / WAI examples · android-app · linux-arm64 (push) Successful in 13m39s
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (push) Successful in 15m13s
WAI examples / WAI examples · the archive, the readiness check, the browser and Apple · macos (push) Successful in 19m26s
The integer decode's tests (native-sink §13) join the TestHost project with their vectors in the host's test bundle (the staged Resources/int folder), so scripts/test-simulator.sh runs them in fresh phone and television simulators, as a device runs the host; the script refuses to run when the vectors are not staged. Run on 2026-11-11 on an arm64 laptop (Xcode 27.2): the xcframework (desktop and both simulator slices) built, swift test passed (10 tests), and both simulators passed every test, IntDecodeTests among them; in the simulators the decoder's GPU is refused at discovery (their Metal adapter offers 15 inter-stage shader variables, the WebGPU default limits ask 16), so the nine vectors decoded on the CPU there, each the CPU's bytes. native-sink §13.5 records it. Co-Authored-By: Claude Opus 5.5 <[email protected]>
136 lines
8.9 KiB
Markdown
136 lines
8.9 KiB
Markdown
# wai-sink-swift — the native sink for the platform player, in Swift
|
|
|
|
The native sink of [`extensions/native-sink.md`](../extensions/native-sink.md) for phones and
|
|
tablets, televisions and set-top boxes, and desktops whose native player an application reaches
|
|
from Swift. The player is not changed and decodes on the platform's media engine; the sink sits on
|
|
the path of its requests and, for one playback session:
|
|
|
|
- verifies a media playlist against its playlist receipt and the chain of its live revisions
|
|
(http-delivery §8.6), and every object whose name states its content hash against that hash, and
|
|
withholds from the player whatever does not verify;
|
|
- signs, under a fresh key per session, stream-audit's acknowledgements of every object received,
|
|
a presentation claim for each marked ad, and the session record, and posts the bundle to the
|
|
owner's collector (`wai-audit serve`), with its Event-mode CMCD while it plays;
|
|
- ends a session nobody can see, or whose viewer has gone idle, and stops the player's requests.
|
|
|
|
Everything that decides is `libwai`'s C interface (native-sink §9), the same on every native
|
|
platform; this package is the platform's half: the loopback relay, the player, and the signals of
|
|
who can see it.
|
|
|
|
## Layout
|
|
|
|
| path | what |
|
|
|---|---|
|
|
| `Package.swift` | the package: library `WaiSink`, binary target `WaiFFI` (the C interface): a release's framework by URL and checksum, or the one built here |
|
|
| `Sources/WaiSink/Sink.swift` | `WaiSink`: one session, every call of the C interface, JSON in and out |
|
|
| `Sources/WaiSink/Relay.swift` | `WaiRelay`: the loopback server the player is given (native-sink §4.4) |
|
|
| `Sources/WaiSink/PlayerSession.swift` | `WaiPlayerSession`: the platform player through the relay, observed every 200 ms (§5, §6) |
|
|
| `Sources/WaiSink/Attention.swift` | `WaiViewAttention`: whether the application is shown, the player view's area in view, the viewer's input (§6.2); `WaiFixedAttention` for hosts that decide it themselves |
|
|
| `Sources/WaiSink/Collector.swift` | `WaiCollector`: `v1/sessions` and `v1/cmcd` (stream-audit §12) |
|
|
| `Sources/WaiSink/IntDecoder.swift` | `WaiIntDecoder`: WAI's integer capabilities decoded in the application's process, on the device's GPU where it passes the start-up exactness check, with the report naming the unit (native-sink §13) |
|
|
| `scripts/build-xcframework.sh` | builds `build/WaiFFI.xcframework` from `../wai-rs` |
|
|
| `scripts/package-release.sh` | the framework zipped, and the copy of the package that names the zip by URL and checksum, for a release |
|
|
| `scripts/check-release-package.sh` | that copy resolved, built and run as an application uses it, and refused with a wrong checksum |
|
|
| `scripts/test-simulator.sh` | runs the tests hosted in `TestHost/` in a fresh simulator it creates, boots and deletes, every step under a hard limit |
|
|
| `Tests/WaiSinkTests` | the corpus through the wrapper; the relay over the reference package's floor; the player end to end; the integer decode on the GPU and the CPU (`IntDecodeTests`) |
|
|
| `TestHost/` | a minimal application the same tests run inside on a phone or tablet, where no tool-hosted test bundle runs: an Xcode project that depends on this package by its path; consumers never see it |
|
|
|
|
## Use
|
|
|
|
```swift
|
|
import WaiSink
|
|
|
|
let cfg = WaiSink.Config(channel: "news-east", owner: "<the collector's key, 64 hex>")
|
|
let session = try WaiPlayerSession(url: URL(string: "https://media.example/ch/7/v.m3u8")!,
|
|
config: cfg,
|
|
attention: WaiViewAttention(view: playerView),
|
|
collector: WaiCollector(base: URL(string: "https://collector.example/")!))
|
|
playerLayer.player = session.player // the host shows the player as it always has
|
|
session.onEnd = { bundle, ended in /* posted already; kept here if the host wants it */ }
|
|
session.start()
|
|
```
|
|
|
|
For a stream the owner's packager seals (playlist receipts, segments named by their SHA-256,
|
|
packaging §7), set `requireSigned: true` (or `contentAddressed: true`), and nothing the
|
|
publisher did not sign reaches the decoder. For a stream another service delivers, leave both
|
|
off: it is acknowledged and evidenced, never refused for what its names look like.
|
|
|
|
## Decoding WAI's integer capabilities on the GPU
|
|
|
|
A host that plays a WAI capability itself, not through the platform's player, decodes it with
|
|
`WaiIntDecoder` (native-sink §13): `wai.neural.int_hyper`, `wai.neural.int_synth`,
|
|
`wai.neural.int_mlicpp`, `wai.video.int_hyper`, `wai.video.int_cond`, `wai.video.int_motion`,
|
|
`wai.audio.int_codec` and `wai.splat.int_codec`, as whole payloads or `WAI1` envelopes.
|
|
|
|
```swift
|
|
let decoder = try WaiIntDecoder() // finds the GPU and checks it, once per process
|
|
print(try decoder.gpu()) // state "ready", the adapter, the translator
|
|
let out = try decoder.decode(capability: "wai.neural.int_hyper", payload: wih1,
|
|
set: ["entropy.json": e, "g_s.whm": gs, "h_s.whm": hs])
|
|
out.bytes // RGB8 rows, the same bytes on every device and on the CPU
|
|
out.unit // "gpu", or "cpu" where the GPU is not used, with the reason in out.dispatch
|
|
```
|
|
|
|
The GPU runs the integer operator set's WGSL kernels through libwai's Metal backend, integer
|
|
arithmetic only, every value in a width the graph's bounds certificate proves it fits. It runs
|
|
nothing until it has passed the start-up exactness check: WGSL's integer semantics, the
|
|
arithmetic probe, graphs at every width and at the edge of the narrow kernels' bound, and a
|
|
whole decode, each giving the CPU's integers. A GPU that differs anywhere is not used, and every
|
|
decode's report says so (`dispatch_unit_inexact`); the decode then runs on the CPU and gives the
|
|
same bytes. `WaiIntDecoder(gpu: .off)` decodes on the CPU alone. The library links Metal,
|
|
QuartzCore and CoreGraphics for it; `Package.swift` declares them.
|
|
|
|
## Build and test
|
|
|
|
On the build host (Xcode with the phone, television and desktop SDKs; Rust with the targets
|
|
`scripts/build-xcframework.sh` names):
|
|
|
|
```
|
|
cd wai/wai-sink-swift
|
|
scripts/build-xcframework.sh # every slice; WAI_SINK_SLICES="macos ios-sim" for some
|
|
swift test # the desktop: corpus, relay, player end to end, integer decode
|
|
scripts/test-simulator.sh ios # the same tests, hosted in TestHost, in a fresh phone simulator
|
|
scripts/test-simulator.sh tvos # and in a fresh television simulator
|
|
```
|
|
|
|
On a phone or tablet, the tests run hosted in `TestHost/` (signed with the team configured in Xcode):
|
|
|
|
```
|
|
xcodebuild build-for-testing -project TestHost/WaiSinkTestHost.xcodeproj -scheme WaiSinkTestHost \
|
|
-destination 'generic/platform=iOS' -allowProvisioningUpdates DEVELOPMENT_TEAM=<team>
|
|
xcodebuild test-without-building -xctestrun <the .xctestrun it wrote> -destination 'platform=iOS,id=<device>'
|
|
```
|
|
|
|
The player tests make their presentation at run time with the platform's own encoder (AVC, ISO/IEC 14496-10, in
|
|
fragmented MP4, a segment a second), serve it from an origin on loopback, and play it through the
|
|
relay with a video output attached, since no layer is on screen in a test.
|
|
|
|
## Distribution
|
|
|
|
A release attaches `WaiFFI-<version>.xcframework.zip`, the framework with every slice, and
|
|
`wai-sink-swift-<version>.zip`, this package with its `Package.swift` naming that zip by its
|
|
download URL and its SwiftPM checksum (`.binaryTarget(name: "WaiFFI", url: …, checksum: …)`,
|
|
the archive's SHA-256, as `swift package compute-checksum` gives it). `scripts/package-release.sh`
|
|
writes both. An application adds the package from that folder, as a local package or a copy in a
|
|
repository of its own; SwiftPM fetches the framework from the URL and refuses it unless its
|
|
checksum is the one named.
|
|
|
|
Before the release attaches them, `scripts/check-release-package.sh` resolves, builds and runs an
|
|
application that depends on the package, the framework served from the release's own files
|
|
through a mirror of its URL, and requires the package with one digit of its checksum changed to
|
|
be refused; once they are attached, it does both again from the URL itself. In this repository,
|
|
`Package.swift` names `build/WaiFFI.xcframework` while `scripts/build-xcframework.sh` has built it
|
|
there, so the tests run against the libwai of their checkout.
|
|
|
|
The library holds no codec: the platform's player decodes, and its codecs are the platform's,
|
|
licensed with it. The integer decoder is WAI's own (`wai-rs`), with wgpu (MIT or Apache-2.0) for
|
|
the GPU.
|
|
|
|
## Energy
|
|
|
|
The platform's player decodes in a process of the platform's, on its fixed-function decoder, which
|
|
no counter an application can read covers. The session therefore states the sink decode and the
|
|
display as the unmetered marker (native-sink §7), never the application's own counter as either.
|
|
A decode through `WaiIntDecoder` runs in the application's process: on the CPU a counter of the
|
|
process covers it; on the GPU a CPU counter does not, and the sink decode stays the marker
|
|
unless the host holds a figure that covers the GPU's work (native-sink §13.4).
|