# iPX for iOS ipx's page in an app, with the audio played by the phone rather than the page: it plays on with the screen locked, and the lock screen, Control Center, headphones and a car over Bluetooth or USB drive it. Everything you see is the page, as on the home screen; the app adds the player behind it. ## Build it On a Mac with Xcode: ```sh brew install xcodegen cd ios && xcodegen generate && open iPX.xcodeproj ``` Set your team under Signing & Capabilities, pick your phone, and run. The project is generated from `project.yml`, so `.xcodeproj` is not in git: edit the yml. Nothing on Tower can build it. A free personal team works, but a build on it expires after seven days and is reinstalled from Xcode. A paid account ($99 a year) makes that a year and adds TestFlight. Playing with the screen locked is a plist key (`UIBackgroundModes: audio`), not an entitlement, so it works on either. CarPlay's own dashboard app does not: Apple grants `com.apple.developer.carplay-audio` on request. ## Which server The first launch asks, with `https://ipodderx.sdf1.net` filled in; a bare host gets `https://`. Shake the phone to change it. Plain `http://` only on your own network (`http://192.168.1.130:8099`), which is all `NSAllowsLocalNetworking` allows. Sign in on the page that follows, through Cloudflare Access or ipx's own form; the cookies that leaves are what the player uses (`CookieBridge`). `-ipx.server ` as a launch argument overrides it for a run. ## How it hangs together | | | |---|---| | `Sources/App.swift` | the app and its one scene | | `Sources/WebViewController.swift` | the `WKWebView`, the server question, a page that will not load | | `Sources/Bridge.swift` | the messages to and from `web/src/native.ts` | | `Sources/Playback.swift` | `AVPlayer`, the audio session, now playing, the remote commands, saving positions | | `Sources/CookieBridge.swift` | the web view's cookies into `HTTPCookieStorage.shared`, for the player | | `Sources/Server.swift` | which server | ## The messages The page's half is `web/src/native.ts`, and `node tests/native-bridge.ts` holds it to these. A change to them is a change to both halves in one commit, and the page has to be deployed before an app that needs it. Page to app, on `webkit.messageHandlers.ipx`: | | | |---|---| | `{t:"ready", version, rate, volume}` | the bridge is in | | `{t:"load", url, enc, feedId, guid, title, feedTitle, artwork, position, duration, rate, volume}` | the element's `src` was set to an audio file | | `{t:"play"}` `{t:"pause"}` `{t:"stop"}` | | | `{t:"seek", to}` `{t:"rate", v}` `{t:"volume", v}` | | | `{t:"position", url}` | save where we are; a path, not a time, since a page frozen in the background holds an old one | App to page, as `window.ipxNative.on(...)`: | | | |---|---| | `{t:"time", cur, dur}` | `timeupdate` | | `{t:"meta", dur}` | `loadedmetadata`: the page seeks to where you left off | | `{t:"state", playing}` | `play` or `pause` | | `{t:"ended"}` | | | `{t:"error", message}` | the page says so | The app writes the position (every ten seconds and on pause) and marks an item read at 90%, from its own clock, so both happen while the page is frozen. Video stays in the page. ## Not yet * Episodes kept on the device (#412) are the page's service worker's, which a `WKWebView` does not run without App-Bound Domains; the app plays them from the server. * The cookie is the only credential: when Cloudflare Access's expires mid-drive, playback stops with "Sign in again", and opening the app signs in. A token in the keychain would end that. * CarPlay's dashboard app needs Apple's entitlement first; then it is a second scene over the same `Playback`.