feat(companion): retain audio with native media controls

This commit is contained in:
archipelago
2026-10-07 16:10:10 -04:00
parent b9c75b2141
commit 235f6d04d5
17 changed files with 830 additions and 27 deletions
+50 -1
View File
@@ -5,6 +5,8 @@ candidate worktree is `/home/archipelago/Projects/archy-session-key` on branch
`work/post-190-session-key`. Do not use credentials from chat or print private
node, wallet, session or registry data.
Latest active checkpoint: see **Companion56 native-audio qualification** at the end. Earlier receipts below are historical and apply only to their named source/artifacts.
## Current candidate
Recent commits, newest first:
@@ -289,7 +291,7 @@ No percentage is inferred from test counts.
| 17 HTTPS apps | In progress | Exact hostname/trust and companion acceptance |
| 18 Firewall/tunnel | In progress | Read-only UI done; settings persistence/reboot/rollback qualification |
| 19 Media guide/Cloud PiP | Nearly finished | PiP operator-accepted; finish reusable contract and remaining edge cases |
| 20 Native background media | In progress | Cloud PiP done; native audio service/controls and device checks remain |
| 20 Native background media | In progress | Native service/controls implemented and APK56 delivered; matching LAN HTTP UI qualification and physical device checks remain |
| 21 Public files/folders | Queued, before22 | Shared pricing interface with blue tints, unrelated-node discovery and tiny marker |
| 22 Mesh file sharing | Queued, final task | Node/computer choice, tree/search picker, retained multi-selection, delivery design and usual paid-file flow |
@@ -327,3 +329,50 @@ Task22 now follows task21 at the end. Its detailed scope is in
`post-1.9.0-work-backlog.md`. This is a recorded requirement/design task, not a
claim that the picker or transport/payment flow has been implemented. Existing
public-sharing blue pricing and marker requirements are retained unchanged.
## Companion56 native-audio qualification — 2026-10-07
- Operator withdrew the accidental screensaver/Play/Skip prompts. No changes
were made for them, so nothing required reverting.
- Companion0.5.36/build56 is delivered at
`http://192.168.63.240/packages/archipelago-companion-0.5.36.apk`.
SHA256 `6bee2a2a13231525997946926bb47c473e01b81c03737d22d1bce18af9982c6e`.
Canonical signing, clean packaging and HTTP byte verification passed. The
standard fleet APK and live backend/catalogs were preserved.
- 29 Android tests pass (including native bridge, foreground notification and
task-removal retention on SDK28/35). These are automated tests, not phone
acceptance. Durable receipt:
`~/.local/state/archipelago/release-qualification/companion-056/`.
- The native MediaSession/foreground service controls the same retained
authenticated WebView player, with queue controls, bounded thumbnail data,
session/origin checks and logout/stop cleanup. No protected stream URLs or
credentials are sent to Android and no second decoder/payment is started.
- Initial complete dashboard qualification passed1440 tests/175files and its
production build. That UI was deployed on dev with backup and unchanged
backend, catalog/session/intent hashes and container IDs/start times.
- Live LAN HTTP browser inspection then found `crypto.randomUUID` absent. The
new bridge now generates UUIDv4 using `crypto.getRandomValues`, which that
browser supports. A regression covers this exact condition; final full UI
suite/build and replacement deployment are underway. Do not ask the operator
to accept native audio until the matching corrected Yaya UI is deployed.
- Yaya remains at its previous UI until qualification completes. Physical
Android background, lock-screen, swipe-away, reconnect and stop acceptance
remains required. CloudPiP55 remains operator accepted.
- Dedicated IndeeHub agent owns backend changes and the full isolated seven-app
fixture. Its first latest suite had2002pass/1failure/5ignored: the new fixture
incorrectly expected no directory, although an empty directory is valid.
Assertion corrected to require no journal files. Final isolated recheck:
**2,003 passed, zero failed, five ignored**, committed as `b9c75b21`. This
is not a live IndeeHub delivery; optimized binary and full VM cutover still
remain before Yaya activation.
- First HTTP-fix full UI run:1440 passed, one60-second Home wallet-display
timeout and a worker `snapshotSaved` timeout under severe memory/I/O pressure.
Focused rerun:all12 Home wallet-cache tests and all6 companion bridge tests
passed (18/18). Clean full rerun is underway; retain both failed and passing
logs. The initial1440-green full receipt predates the UUID fix.
- Extended real V4V browser qualification supports optional simulated Android
transport while using the actual authenticated iframe and bundled free demo
media. It checks native command routing and JPEG thumbnail delivery; no
physical Android result is implied. The new end-to-end run remains pending.
+71 -5
View File
@@ -7,10 +7,12 @@ reference app; its updated real-node acceptance is tracked in
[v4v-native-player-20261006.md](v4v-native-player-20261006.md). Local tests are not
proof that a particular deployed app or companion version supports every feature.
Cloud video picture-in-picture (PiP) in the Android companion is a separate open
implementation task. Do not advertise the audio bridge as a video/PiP API. Native
fullscreen support alone does not provide Android PiP. A versioned video contract
and examples must be added here when implementation and device tests pass.
Cloud video PiP is implemented in companion0.5.35/build55; the operator accepted
the delivered Cloud PiP flow on2026-10-07. Audio and video use separate channels.
Native background audio is a companion0.5.36/build56 candidate:29 Android tests
and six companion-bridge dashboard tests pass; physical background/lock-screen/task
removal acceptance remains required. Do not infer those phone results from
browser or Robolectric tests.
## Declare the audio integration
@@ -152,7 +154,7 @@ those guards merely to make a test pass.
viewport test cannot prove OS background playback or PiP support. Do not promise
playback after the operating system kills the process.
## Cloud video PiP acceptance scope (not yet implemented)
## Cloud video PiP contract and remaining acceptance
Start with Cloud's existing authorized video viewer and companion fullscreen host.
Provide an explicit PiP control when supported, with clear unavailable behavior.
@@ -165,3 +167,67 @@ logout/session expiry, FIPS disconnect/reconnect, and process recreation. Valida
on a physical companion with ordinary and FIPS-accessed Cloud videos before APK
publication. Document the proven video integration contract for other app authors
only after that implementation is qualified.
The dashboard uses the main-frame-only `ArchipelagoCloudVideo` channel, admitted
only for the paired node's exact HTTP(S) origins. Its version1 capability check
is separate from `archipelago-v1` app audio integration. The Cloud host arms a
random request/session ID with video dimensions and playing state, then enters
fullscreen on the existing video in the same user gesture before requesting PiP.
Native commands and replies carry that session; a different session is ignored.
`state` updates playback controls and `release` retires the session. `restored`
returns to the same video; closing requests pause/cleanup. No video URL, cookie,
bearer token or second player crosses the channel. The entire dashboard must
never be used as the PiP surface. This is currently a Cloud-host contract, not
permission for arbitrary embedded apps to call native PiP directly.
## Companion native audio: build56 candidate
Apps continue using the existing version1 app audio protocol above. They do not
need a second Android stream or a separate queue implementation. The dashboard
owns a main-frame `ArchipelagoAudio` channel restricted to paired node origins;
embedded app frames cannot invoke it directly. The foreground `mediaPlayback`
service owns the retained WebView session after the Activity/task closes and
exposes an Android MediaSession with playback metadata, play/pause, previous/next,
seek, shuffle and Stop. Playback state is confirmed by the existing app player,
not optimistically advanced by native controls.
Dashboard→native messages contain version1, a random session, monotonically
increasing sequence, bounded title/position/duration, playback/control flags and
optional JPEG thumbnail bytes. Artwork is fetched by the already authenticated
page with same-origin credentials only, capped at512KiB, resized to192px and sent
as a bounded data URL. Native code does not fetch artwork URLs or receive auth
credentials. Cross-origin images without CORS may have no notification thumbnail;
missing artwork never blocks playback. Native image decoding bounds dimensions.
Native→dashboard controls carry the same session. Retired sessions, stale sequence
numbers, foreign origins and subframes cannot revive/control playback. A short
heartbeat resynchronizes state; if the dashboard stops responding for90seconds,
the native owner stops instead of advertising a live session indefinitely.
Playback stays in the same authenticated WebView/iframe; app authorization and
entitlement checks remain with that player. App removal, frame replacement,
logout, navigation/disconnect or explicit Stop release the corresponding session.
A non-playing task removed from Recents is released. A playing one is retained;
reopening reattaches the existing document, queue and position. A killed process
is not automatically restarted into an authenticated stream.
The implementation uses the platform MediaSession with the existing WebView
player, rather than adding another decoder. No boot receiver or new storage
permission is involved. Pair this APK with the matching dashboard build: an older
dashboard does not send the native audio protocol merely because the APK changed.
Qualification commands:
```sh
cd neode-ui
./node_modules/.bin/vitest run src/composables/__tests__/useCompanionAudio.test.ts src/composables/__tests__/useAppMediaBridge.test.ts src/components/__tests__/GlobalAudioPlayerExternal.test.ts
cd ../Android
./gradlew :app:testDebugUnitTest
```
Before marking physical acceptance, use V4V on the matching server: play a track,
close its panel, press Home, lock the phone, pause/resume/seek/skip/shuffle from
native controls, remove the companion task while playing, reopen into the same
queue/position, then Stop. Repeat logout, headset disconnect and network loss.
Confirm download, fullscreen and Cloud PiP still work. App force-stop/process
kill is a stop condition, not a promise of uninterrupted playback.