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

10 KiB

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:

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.