Files
ArtPlayer/packages/artplayer-plugin-chromecast/ARCHITECTURE.md

169 lines
10 KiB
Markdown

# Chromecast maintenance
The factory remains `artplayerPluginChromecast(option)`. Registration is async and
adds the `chromecast` right control immediately. Its resolved object retains
`name`, `getCastState()` and `isCasting()`. SDK loading remains lazy on first click.
The option object stays live: later changes to `url`, `mimeType`, `sdk` and callback
properties are read at the corresponding operation. Callbacks retain `this === option`.
The control HTML is captured at registration, as before.
## Modules
| File | Responsibility |
| --- | --- |
| `src/index.ts` | Public factory and historical `.default` factory alias |
| `src/controller.ts` | One registration's subscriptions, raw session state, click operation, notices and owned control icon |
| `src/sdk.ts` | Framework readiness, shared in-flight script leases, callback preservation and context configuration |
| `src/media.ts` | Existing URL/extension MIME rules and SDK media request construction |
| `src/types.ts` | Narrow internal SDK, event and callback contracts; not the published declaration entrypoint |
| `src/cast.svg` | Original default Cast artwork imported as inline SVG |
Dependencies point from the factory to the controller, then to the SDK loader and
media builder. The loader and media builder do not receive an ArtPlayer instance.
Public declarations remain in `types/`. The root and legacy entries retain npm
1.1.0's required option argument and synchronous name-only factory type, including
ordinary replacement-function assignment. `/runtime` describes the actual async
registration and callbacks using the same JavaScript files. Internal option and
result contracts reference these precise public types to prevent another drift.
## Initialization and ownership
Each registration has its own session state and subscriptions, even when one
factory is reused for several players. Unclicked instances do not subscribe to
the SDK. An initialized instance observes page-wide CastContext events; this is
the SDK's shared session, not an exclusively owned receiver session.
Within a loaded plugin module, controllers share one pending SDK load per window.
The first pending request's SDK URL wins; another URL can be used on a later retry.
A complete existing Framework is reused immediately. A partial `chrome.cast`
object is insufficient readiness and still permits loading the Framework script.
The original availability callback is invoked with its window receiver and both
arguments. Its thrown exception still escapes to its caller, while the plugin's
own subscribers settle independently. Cleanup restores that handler only while
our handler is still installed, so a later external replacement is preserved.
On availability, each subscriber installs its listeners synchronously before its
loader promise resolves. This preserves the historical boundary where a session
event immediately following `__onGCastApiAvailable(true)` is observable. One
subscriber's setup exception rejects that subscriber without stranding the rest.
The default receiver and ORIGIN_SCOPED policy are configured once per observed
context. This does not negotiate receiver configuration with unrelated Cast apps.
Successful scripts remain on the page. Failed or last-owner-cancelled scripts are
removed and can be retried. Script `load` may precede Framework availability, so
incomplete load does not fail immediately; a 30-second readiness timeout settles
an unavailable Framework even if no error/availability event arrives. The timeout
is cleared on success, failure and last-owner cancellation. Destroying one player
releases its lease without interrupting another pending player. Independently
loaded copies of this plugin do not share their module-local lease registry.
## Session operations and failure semantics
One player has at most one pending click operation; concurrent clicks receive the
same promise. After initialization, the controller reads `getCurrentSession()`.
If absent, it awaits `requestSession()` and reads `getCurrentSession()` again.
The request's completion value is not a CastSession. No active session after a
successful request is a connection error. Media URL selection uses the current
`option.url || art.option.url`; no receiver URL rewriting is performed.
The complete media load is awaited. The click promise rejects with the original
SDK error and reports one stage-specific notice and `onError`. A separate rejection
observer handles ArtPlayer's DOM dispatcher, which ignores returned click promises;
programmatic callers still receive the original rejecting promise. Callback
exceptions retain their normal propagation and are not interpreted as receiver
success evidence.
`getCastState()` retains the last raw SDK SessionState, initially null, and is not
the normalized callback state. `isCasting()` reflects a retained session reference,
including one acquired by a successful session request; it does not prove receiver
playback. `NO_SESSION`, `SESSION_ENDED` and `SESSION_START_FAILED` clear that reference
and notify disconnected. Initial `NO_SESSION` during SDK setup does not cancel the
first click before a session operation begins. Ending/terminal events during a
request or media load, and replacement of the current session, invalidate pending
work. Late success/rejection is observed without stale media starts or notices.
Destroy marks the controller inactive, settles its pending click, releases its
loader lease, attempts both SDK listener removals and removes its ArtPlayer
subscription. All cleanup is attempted even when one SDK removal throws; the first
cleanup error is preserved. Any callback left behind by a broken SDK removal is
inactive. The plugin never ends a page-shared session. ArtPlayer owns control DOM
removal; the plugin only updates the icon under the element supplied to its own
control's `mounted` callback, retaining `.art-icon.art-icon-cast` and the
red/orange/white states. The actual `Control.add` override returns undefined even
though the shared component superclass returns an element; never rely on the
control add return value for DOM ownership. Candidate fixtures replay `mounted`
and preserve this undefined return so the browser-discovered regression remains
covered outside the browser too.
## Artwork provenance
The default SVG now uses original rectangle, circular arc and dot geometry authored
for this refactor under the repository MIT license. It does not copy the former
Font Awesome Pro path. `src/cast.svg` is the single artwork source; custom `icon`
HTML and the existing wrapper hooks remain supported. The previous commercial
notice/source gap is tracked as VENDOR-10; production bundles must be regenerated
from this source rather than edited by hand.
## Validation and next changes
Use the pinned Node and Yarn versions. Scoped commands from the repository root:
```sh
node --test test/chromecast.test.js test/chromecast-failures.test.js test/chromecast-runtime.test.js
yarn exec tsc -p packages/artplayer-plugin-chromecast/tsconfig.json
yarn exec eslint packages/artplayer-plugin-chromecast/src test/chromecast-runtime.test.js
yarn build artplayer-plugin-chromecast
yarn build:ts artplayer-plugin-chromecast
node --test refactor/scripts/chromecast-types.test.mjs
yarn test:chromecast-types-package
```
`ARTPLAYER_CHROMECAST_ARTIFACT` selects a built main or legacy file for the candidate
tests. `ARTPLAYER_CHROMECAST_ESM_ARTIFACT` selects the native ESM artifact for the
default-export identity check. Without these variables tests bundle current source
in memory. Historical tests retain actual frozen npm/source inputs and deliberately
observe the old defects; candidate tests assert their corrections separately.
Change SDK loading in `sdk.ts`, state/cleanup in `controller.ts`, and MIME behavior
in `media.ts`. Always retain synchronous availability-event coverage, ignored DOM
promise rejection coverage, multi-player ownership, partial failure cleanup and
late completion tests when changing those boundaries.
Controlled SDK tests and local browser DOM interaction cannot establish real Cast
receiver playback. PKG-CAST-05 still requires the supported Chrome/HTTPS sender,
actual receiver, network-reachable media, source changes and disconnection evidence.
The default SDK URL is mutable; no remote SDK version, physical receiver or Safari
support is established by this source migration.
## Type and module compatibility
ADR-027 preserves the latest actual npm declaration, not a claim that all past
incompatible declarations can coexist. npm 1.0.0 used `export =` and its CommonJS
module exposed `.default`; npm 1.1.0 uses a default declaration and direct CommonJS
factory. Both legal JavaScript forms work in the candidate through a writable
self `.default`. Root types remain a plain factory without that required member.
The historical types-first root mapping also retains the latest declaration's
NodeNext ESM namespace shape and its existing direct-call diagnostic. Its legal
namespace consumers and full module replacement are tested independently. Choose
`/runtime` for a directly callable modern ESM default and precise async results;
its `.d.mts`, `.d.cts` and Node10 `export =` declarations share eight named types.
The option argument remains required on both type surfaces. Callbacks have the
live option object as `this`; errors are unknown, raw session state is string or
null, and `isCasting()` does not establish receiver playback.
The installed consumer verifier compares both actual historical tarballs and an
isolated candidate package, validates individual negative statement diagnostics,
and checks runtime entry identity. The editor declaration is generated semantically
from the root declaration. Keep internal `tsconfig.json` and source out of npm packs.
## Shared installed browser validation
chromecast-lifecycle.spec.js selects the verified installed bundle. It uses a controlled Cast SDK with real core DOM, clicks and local video; receiver discovery, actual Cast SDK and remote playback remain PKG-CAST-05. The artifact map must not be interpreted as physical device acceptance.
Use `yarn test:package --browser`, then `yarn test:browser:installed`. The common
roster in scripts/browser-validation/scope.ts drives both preparation and required
inputs. Explicit artifact overrides are rejected when an installed map is present;
missing or stale inputs fail without source fallback. Source mode retains deliberate
artifact selection. See ../../refactor/changes/2026-09-15-CI-01-asr-cast-installed.md.