docs: complete reusable media integration and acceptance contracts
This commit is contained in:
@@ -64,6 +64,42 @@ original frame/origin/nonce solely so its existing controls and cleanup remain
|
|||||||
usable if catalog freshness expires. App removal, version or frame replacement
|
usable if catalog freshness expires. App removal, version or frame replacement
|
||||||
releases that admission; a new session must qualify again.
|
releases that admission; a new session must qualify again.
|
||||||
|
|
||||||
|
## Integrate a new audio app
|
||||||
|
|
||||||
|
1. Add the manifest opt-in above and install the reviewed app through the normal
|
||||||
|
catalog path. Confirm that the authenticated daemon reports the installed
|
||||||
|
app/version and that its UI opens in the retained dashboard frame. A manually
|
||||||
|
injected iframe or community listing is not an admission test.
|
||||||
|
2. Import the example adapter into the app, configure the exact deployment-owned
|
||||||
|
dashboard origin, and attach it after constructing your player. `snapshot()`
|
||||||
|
must return `title`, `artist`, `playing`, `shuffle`, `position`, `duration` and
|
||||||
|
optional `artwork`; return `null` when no authorized track is available.
|
||||||
|
3. Implement `actions.play/pause/seek/next/previous/shuffle` using the app's existing
|
||||||
|
player and queue. `seek` receives seconds; the adapter clamps to the current
|
||||||
|
duration. `snapshot` is a protocol command handled by the adapter, not a second
|
||||||
|
player action. Unsupported actions should remain unavailable in the app's own
|
||||||
|
interface too; do not report controls that merely appear to succeed.
|
||||||
|
4. Supply `unlocked()` from actual application authentication/entitlement state.
|
||||||
|
The adapter does not authenticate a user, buy a file or authorize playback.
|
||||||
|
Subscribe `publish()` to player events, queue changes and login/logout state.
|
||||||
|
The adapter has no playback polling timer; without these subscriptions the
|
||||||
|
host will keep showing the last published snapshot.
|
||||||
|
5. On logout or entitlement loss, stop the player, clear its authorized source
|
||||||
|
and publish the locked state. `available: false` hides host state but does not
|
||||||
|
itself pause the app's audio element. On permanent teardown, unsubscribe your
|
||||||
|
player callbacks and call `dispose()` (which also requests the app's pause
|
||||||
|
action). Do not dispose simply because the dashboard panel closes.
|
||||||
|
6. Attach again in the document reached after an app-local login redirect. The
|
||||||
|
adapter registers its listener before sending ready, reannounces on
|
||||||
|
`pageshow`, and disposes on non-persisted `pagehide`; a back/forward-cache
|
||||||
|
return keeps the live adapter. Preserve these lifecycle rules when wrapping it
|
||||||
|
in a framework component.
|
||||||
|
|
||||||
|
The adapter serializes commands and checks the current handshake and unlocked
|
||||||
|
state before running each queued action. Authentication can still change while
|
||||||
|
an asynchronous app action is running: the application must abort or reject its
|
||||||
|
own unauthorized media request. Never use the host nonce as a media access token.
|
||||||
|
|
||||||
## Keep a single player and queue
|
## Keep a single player and queue
|
||||||
|
|
||||||
The app continues to own its audio element, queue, authorization and playback
|
The app continues to own its audio element, queue, authorization and playback
|
||||||
@@ -173,8 +209,10 @@ only after that implementation is qualified.
|
|||||||
|
|
||||||
|
|
||||||
The dashboard uses the main-frame-only `ArchipelagoCloudVideo` channel, admitted
|
The dashboard uses the main-frame-only `ArchipelagoCloudVideo` channel, admitted
|
||||||
only for the paired node's exact HTTP(S) origins. Its version1 capability check
|
only for the paired node's exact HTTP(S) origins. Its native `capabilities` action reports version 1 separately from the
|
||||||
is separate from `archipelago-v1` app audio integration. The Cloud host arms a
|
`archipelago-v1` app audio integration. The current dashboard composable detects
|
||||||
|
the injected bridge and checks readiness during entry; a visible button is not
|
||||||
|
a guarantee that Android permits PiP. The Cloud host arms a
|
||||||
random request/session ID with video dimensions and playing state, then enters
|
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.
|
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.
|
Native commands and replies carry that session; a different session is ignored.
|
||||||
@@ -184,6 +222,97 @@ 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
|
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.
|
permission for arbitrary embedded apps to call native PiP directly.
|
||||||
|
|
||||||
|
## Reuse video PiP in a dashboard-owned viewer
|
||||||
|
|
||||||
|
The concrete host reference is
|
||||||
|
[`MediaLightbox.vue`](../neode-ui/src/components/cloud/MediaLightbox.vue).
|
||||||
|
These are dashboard-side integration points, not an exported video protocol for
|
||||||
|
third-party iframe apps. An app author can use the audio adapter above today;
|
||||||
|
embedding a new app's video into native PiP still needs a reviewed host integration
|
||||||
|
and its own qualification.
|
||||||
|
|
||||||
|
For companion entry, create `useCompanionVideoPip(videoRef)` from
|
||||||
|
[`useCompanionVideoPip.ts`](../neode-ui/src/composables/useCompanionVideoPip.ts) in the viewer's
|
||||||
|
Vue setup using the **existing** `HTMLVideoElement`. Wire an explicit button to
|
||||||
|
`enter()`, disable it while `busy.value`, and render `error.value` as an accessible
|
||||||
|
alert. Wait for nonzero video dimensions. Keep `enter()` in the original click
|
||||||
|
handler: it arms native state and requests that video's fullscreen together,
|
||||||
|
then requests native PiP only after both succeed. Do not first await unrelated
|
||||||
|
fetches or fullscreen the containing dashboard.
|
||||||
|
|
||||||
|
The composable retains the element and listens for play/pause/end/error; native
|
||||||
|
commands call that same element. It rejects stale-session events, times out an
|
||||||
|
unconfirmed bridge call after five seconds and shows a failure instead of
|
||||||
|
claiming entry. Normal native return clears the PiP session without pausing the
|
||||||
|
video; native close, end/error, `stop()` and viewer unmount stop it. As Cloud does,
|
||||||
|
call `stop()` before changing the item or hiding the viewer. Keep the viewer
|
||||||
|
mounted during companion PiP: unmount is intentionally a stop condition.
|
||||||
|
|
||||||
|
For browser PiP, check `isPipSupported()` and use `togglePip(video)` from
|
||||||
|
[`utils/pip.ts`](../neode-ui/src/utils/pip.ts). The toggle checks support at call
|
||||||
|
time and treats permission/transient failures as best effort. Ownership transfers
|
||||||
|
only on the actual `enterpictureinpicture` event, not on the button click:
|
||||||
|
[`usePipSession().adopt(video)`](../neode-ui/src/composables/usePipSession.ts) moves the original element into a document-level
|
||||||
|
host **before** closing/unmounting the viewer. The singleton then owns cleanup.
|
||||||
|
Browser `leavepictureinpicture` calls `release()`, pauses and clears/removes the
|
||||||
|
video; it does not implement companion's return-to-viewer behavior. Adopting a
|
||||||
|
second element releases the previous one. Avoid another cleanup path that clears
|
||||||
|
an adopted element's source during handoff.
|
||||||
|
|
||||||
|
Neither path refreshes credentials, reopens expired downloads or retries a paid
|
||||||
|
purchase. The caller remains responsible for the authorized media source and
|
||||||
|
transport. On logout, node switch, revoked access or unrecoverable transport loss,
|
||||||
|
stop companion playback and release any browser-owned session, then clear the
|
||||||
|
source using the normal viewer flow. Browser adoption outlives the component,
|
||||||
|
so component unmount alone is not a logout cleanup mechanism. Re-establish access
|
||||||
|
through the normal application flow before offering playback again. Physical
|
||||||
|
session-expiry and FIPS reconnection behavior remains unverified below.
|
||||||
|
|
||||||
|
The native implementation is
|
||||||
|
[`CloudVideoPip.kt`](../Android/app/src/main/java/com/archipelago/app/ui/screens/CloudVideoPip.kt).
|
||||||
|
It admits only the current paired dashboard origin and main frame; a matching
|
||||||
|
host on another scheme/port or a child iframe is not equivalent. Requests contain
|
||||||
|
an ID and action (`capabilities`, `arm`, `enter`, `state`, `release`); `arm` uses its
|
||||||
|
request ID as the session, and later actions carry it. Native entry additionally
|
||||||
|
requires the actual fullscreen custom view, the device PiP feature and Android
|
||||||
|
permission. The aspect ratio is bounded to Android's supported range. Callers
|
||||||
|
should reuse the composable rather than duplicate this private bridge protocol.
|
||||||
|
|
||||||
|
## Acceptance and reproducible checks
|
||||||
|
|
||||||
|
This table separates reported acceptance from source behavior and test coverage.
|
||||||
|
It is not a new test receipt. This guide update was a source/documentation review;
|
||||||
|
no tests, APK build, deployment or phone checks were run for it.
|
||||||
|
|
||||||
|
| Area | Evidence already recorded | Remaining acceptance |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Reusable audio adapter | Independent example and protocol tests; V4V reference and existing dashboard qualification recorded above | Qualify each new installed app, its authentication and its own queue; the example is not app certification |
|
||||||
|
| Companion Cloud PiP | Delivered 0.5.35/build55 flow accepted by operator on 2026-10-07; source tests cover fullscreen gating, origin/session rejection and unavailable behavior | Disabled/unsupported system PiP, rotation, full close/return/completion matrix, session expiry, FIPS interruption and process recreation on the physical phone |
|
||||||
|
| Companion background audio | Operator reports 0.5.37/build57 background playback works and notification appears | Full controls, headset/network interruption, task removal, retained queue/position and recovery matrix; process-kill continuity is not promised |
|
||||||
|
| Notification artwork | Local/browser/native image checks recorded below; phone still showed no cover | Explicitly deferred to task 23 after other work; do not treat it as passed |
|
||||||
|
| Browser video PiP | Existing source fixtures cover element adoption before viewer close, singleton release and call-time support check | Browser/device and authenticated-source checks for each new viewer; native acceptance does not certify this path |
|
||||||
|
|
||||||
|
For a change to these contracts, run only the relevant existing checks in an
|
||||||
|
available qualification window. From the repository root:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
node --test examples/audio-app/archipelago-audio.test.mjs
|
||||||
|
cd neode-ui
|
||||||
|
./node_modules/.bin/vitest run src/composables/__tests__/useCompanionVideoPip.test.ts src/components/__tests__/MediaLightboxPip.test.ts src/composables/__tests__/usePipSession.test.ts
|
||||||
|
cd ../Android
|
||||||
|
./gradlew :app:testDebugUnitTest --tests 'com.archipelago.app.ui.screens.CloudVideoPipTest'
|
||||||
|
```
|
||||||
|
|
||||||
|
The audio-specific checks are listed below. Record the source commit, selected
|
||||||
|
checks and result separately from server URL/dashboard revision, APK version and
|
||||||
|
physical-device results. Serve the matching reviewed dashboard and signed APK
|
||||||
|
through the established deployment process; verify the delivered artifact rather
|
||||||
|
than assuming a rebuilt source file reached the phone. On the paired server use
|
||||||
|
an already authorized track/video. Neither this guide nor a fixture permits a
|
||||||
|
payment, new signing consent or live catalog change just to obtain test media.
|
||||||
|
If background playback fails, use **Menu → Playback diagnostics → Copy report**
|
||||||
|
on build57; do not request credentials, stream URLs or paid-media details.
|
||||||
|
|
||||||
## Companion native audio: build57 background playback operator-confirmed
|
## Companion native audio: build57 background playback operator-confirmed
|
||||||
|
|
||||||
On 7 October APK0.5.36 failed after locking or switching apps, without a native
|
On 7 October APK0.5.36 failed after locking or switching apps, without a native
|
||||||
|
|||||||
@@ -642,6 +642,11 @@ The reusable audio adapter and developer guide are implemented in
|
|||||||
[app-media-integration.md](app-media-integration.md). Remaining physical edge
|
[app-media-integration.md](app-media-integration.md). Remaining physical edge
|
||||||
checks include disabled/unsupported PiP, session expiry, FIPS interruption and
|
checks include disabled/unsupported PiP, session expiry, FIPS interruption and
|
||||||
process recreation. The accepted flow does not establish that complete matrix.
|
process recreation. The accepted flow does not establish that complete matrix.
|
||||||
|
The task19 guide now also documents
|
||||||
|
reusable adapter callbacks and authentication teardown, dashboard-only companion
|
||||||
|
PiP integration, browser adoption/release differences, and a tested-versus-physical
|
||||||
|
acceptance matrix. This documentation review adds no runtime or phone acceptance;
|
||||||
|
the remaining physical checks above keep task19 nearly finished.
|
||||||
|
|
||||||
## 20. Companion background media for V4V and other apps
|
## 20. Companion background media for V4V and other apps
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user