iOS: episodes kept on the phone play with no connection (#425)

A WKWebView runs no service worker, so the page's Keep button (#412)
was not shown in the app and everything played from the server. Now the
page asks the app to keep the file (keep, unkeep, clearKept), and the
app answers with what it holds (kept, keepFailed) whether or not
anything plays. The app fetches with a background URLSession, so a
download goes on with the app closed, into Application Support (not
backed up; the files can be fetched again), and Playback plays from the
file when there is one. The DeadAir copy is kept under its own path, as
the browser keeps it. Places and finishes reached offline wait in an
outbox, newest per address, and go when the network is back.

Checked in the simulator against a local server: kept /media/42 and
/media/42/deadair, then with the server refusing /media, a fresh launch
played each from the phone and asked the server for nothing. The page
itself still needs a connection to open (#435).

Deploy the app (build 6) before the page: a new page in an old app
shows a Keep button nothing answers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Ray Slakinski
2026-11-08 14:40:01 -05:00
parent 05e522ac93
commit ecc248307e
14 changed files with 466 additions and 10 deletions

View File

@@ -69,6 +69,8 @@ Page to app, on `webkit.messageHandlers.ipx`:
| `{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 |
| `{t:"keep", url}` `{t:"unkeep", url}` | keep a file on the phone, or let it go: the page's path for it, `/media/42` or `/media/42/deadair` (#425) |
| `{t:"clearKept"}` | signing out: everything kept goes |
App to page, as `window.ipxNative.on(...)`:
@@ -79,14 +81,19 @@ App to page, as `window.ipxNative.on(...)`:
| `{t:"state", playing}` | `play` or `pause` |
| `{t:"ended"}` | |
| `{t:"error", message}` | the page says so |
| `{t:"kept", urls, keeping}` | what the phone holds and is fetching, at `ready` and after each change; sent whether or not anything plays |
| `{t:"keepFailed", url, message}` | one that could not be kept, and why |
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.
* Episodes kept on the phone (#425) play with no connection, from files the app fetches with a
background `URLSession` (`Offline.swift`, `EpisodeStore.swift`); a place or a finish reached
offline waits in `Outbox.swift` until the connection is back. But the page itself does not open
without a connection: a `WKWebView` runs no service worker without App-Bound Domains (#435). So
keep what you want, open the app while online, and it plays on with the connection gone.
* Without the player token 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.
* CarPlay's dashboard app needs Apple's entitlement first; then it is a second scene over the same