Files
ArtPlayer/packages/artplayer-plugin-dash-control/ARCHITECTURE.md
T

11 KiB

DASH Control implementation

The factory remains artplayerPluginDashControl(option = {})(art). Its result has name and synchronous update(): void. Installation does not require art.dash yet; ready, restart, or explicit update() reads and validates that instance. The caller creates, attaches, replaces, and destroys the SDK. This plugin never destroys it, changes its source, or adds a runtime dependency on dash.js.

Module map

File Responsibility
src/index.ts Deferred installation, media identity, update revisions, event subscription and cleanup
src/sdk.ts 4.x qualityIndex vs 5.x representation ID access and manual/Auto selection
src/sdk-events.ts Owned SDK subscriptions, coalesced refresh, teardown and asynchronous errors
src/types.ts Narrow internal SDK, host, model and cleanup types
src/mapping.ts Names, current item matching, duplicate labels and selector ordering
src/menu.ts Existing control/setting registries, menu ownership and guarded selection callbacks
src/audio.svg, src/quality.svg Existing setting icons
types/artplayer-plugin-dash-control.d.ts Public options, generic SDK inputs and callable result

Dependencies flow from the entry into mapping/menu, and from mapping into the SDK adapter. Mapping has no DOM dependency. Menu receives a model and validity callback; it does not choose SDK versions. The event observer receives active/refresh/reset callbacks and owns no DOM or ArtPlayer object. All six owned modules are checked with strict TypeScript, noUncheckedIndexedAccess, and skipLibCheck=false; there are no remaining owned JavaScript modules in this package.

Selection and lifetime

An update validates the captured media element and starts a new revision. A callback from an older update, replaced SDK, or destroyed player returns its previous label without mutating the SDK or UI. This includes references retained by consumers. Every caller formatter and SDK operation is followed by a validity check before subsequent effects. A nested update wins over its older caller.

Manual quality first disables video ABR, then uses the stored SDK selection key. Auto only enables video ABR. Audio passes the original track object to the SDK. Successful selection retains the existing notice, controls.check, setting.check order and synchronous label return. SDK/getName exceptions remain synchronous with their original identity. Other ABR settings are not overwritten.

Duplicate labels still occupy one row. When a duplicate is current, that row keeps the selected item's key/object so its highlight and subsequent click agree. Numeric zero is a valid representation ID; a representation named auto is still manual because the synthetic Auto row is distinguished by its value. Audio prefers object identity, then a unique match on the current track's available id/index/lang fields. Ambiguous matches remain unselected instead of selecting an arbitrary track.

Only menus actually installed by this instance are removed on empty topology, disabled surfaces, failed rendering, or destruction. Where registry lookup exists, the saved callback identity protects an entry replaced by the caller. Cleanup takes a snapshot so a synchronous restart cannot cause old cleanup to remove new menus. Cleanup attempts all owned surfaces/subscriptions even if one removal fails. Installation failure rolls back subscriptions, including one installed by a throwing host. The primary error is preserved; secondary cleanup errors are reported.

Keep dash-quality/dash-audio, selector shape, right control placement/padding, setting width/icons, defaults and unbound getName calls compatible. Do not use a filtered representation array index or absoluteIndex as the SDK 5 selection key.

SDK-driven refresh

After media identity validation, bind seven listeners only when both SDK on and off exist. Quality requested/rendered, track rendered and stream updated/initialized events coalesce into one Promise microtask. This keeps notice/check ordering within a synchronous menu selection intact. Events emitted by a formatter during refresh do not schedule recursive refreshes. Unchanged playback-time events only read the Auto boolean; they do not call formatters or redraw menus. Pure configuration writes with no subsequent SDK event still need explicit update(), especially while paused. No polling timer or SDK method interception is installed.

Stream teardown invalidates queued work and clears menus without reading detached media. Stream initialized/updated can restore menus on the same caller-owned SDK. Replacing art.dash requires ready/restart or explicit update to bind the new SDK. Release invalidates the record before calling the captured off method and attempts every owned removal. Reentrant replacement wins; caller listeners remain installed.

An asynchronous SDK getter/formatter failure stops observation, clears owned menus, and warns with the original error. Restore the offending formatter/SDK state and call explicit update() to recover. Explicit update and synchronous menu errors keep their original throwing behavior. Never convert the public update API to a Promise or destroy an SDK to handle a plugin rendering failure.

Types and compatibility

src/types.ts describes the narrow SDK and registry surfaces, menu models, event names, labels, and cleanup callbacks. SDK keys may be absent in malformed external data; the internal types retain that possibility instead of asserting every value exists. SDK methods are optional by generation. Non-null assertions in sdk.ts preserve the selected method family's historical synchronous failure when a caller provides an incomplete SDK. They do not validate the whole SDK at runtime.

The entry casts only the externally augmented ArtPlayer boundary to the narrow host. The non-empty track check protects the fallback item. Quality sorting casts apply before the synthetic Auto row is appended, and the manual selection key cast follows the Auto branch check. These assertions do not generate runtime conversions or mask the controlled error/identity tests. Changes to SDK capability detection belong in sdk.ts, not in a global any or ambient module escape.

Public Config, Option, QualityLevel, AudioTrack, and Result are named types. AudioTrack id/index/lang include null as declared by both actual SDK generations; internal AudioFields aliases that public shape. Nullable default labels preserve runtime behavior, while caller formatters must still return strings. Default callbacks infer known SDK fields; explicit generic parameters or callback annotations accept a caller's more detailed SDK interfaces. Original object-based formatters remain valid. The optional overload supports omitted/undefined options; the last required overload preserves historical Parameters<typeof factory>[0]. Formatters still receive exactly one argument and return a string; update returns void synchronously. No global augmentation claims every ArtPlayer has a DASH SDK.

The historical .d.ts path stays available. .d.cts exports a callable CommonJS value with a type namespace; .d.mts bridges ESM default/named types. Runtime root and legacy paths are unchanged. typesVersions supports old Node10 type resolution for the legacy subpath. The online editor declaration is generated by yarn build:ts and checked standalone against both TS 4.3.5 and 5.9.3, then consumed by real Monaco.

Validation and next work

Run at the workspace root with pinned Node and Yarn:

yarn test:dash-control
yarn build artplayer-plugin-dash-control
yarn test:browser test/browser/dash-control.spec.js --trace on
yarn test:browser test/browser/dash-editor-types.spec.js --trace on
yarn build:ts
yarn test:dash-types-package
yarn ci:check

ARTPLAYER_TEST_DASH adds main/legacy/module files to both SDK generations of Node contracts and lifecycle tests; use the operating system's path delimiter. ARTPLAYER_DASH_ARTIFACT selects the browser plugin artifact. Archive browser report and result/trace directory before running another suite because the output is shared.

Tests use frozen npm 1.1.0, a frozen pre-refactor 5.x implementation, and candidates. test/types/dash-source.ts checks source/public factory compatibility; test/types/dash-control.ts covers module modes and rejects invalid calls. The legacy fixture is checked against the actual published declaration and candidate. test:dash-types-package packs the current core and DASH with Yarn, installs the tarballs offline outside the workspace, repeats a frozen install and checks all public type modes without resolving any workspace source. It also verifies that implementation files/config do not leak into the archive. This is type acceptance; the full installed runtime and SDK release matrix still belongs to PKG-DASH-06/05. dash-control.spec.js tests exercise real old/new core DOM, menu clicks, native MP4 and cleanup, with controlled SDK methods. They do not load dash.js or test MPD/ABR decoding. PKG-DASH-05 must verify pinned dash.js 4.5.2 and 5.2.1 with real adaptive media and review SDK-driven refresh/source events and the existing demo lifecycle. Physical devices and installed package acceptance remain separate release gates. dash-sdk.spec.js now separately loads hash-pinned npm dash.js 4.5.2/5.2.1 and local multi-quality/multi-audio DASH segments. It covers decoding, explicit update after external selection, source topology replacement, retained callbacks and SDK ownership. It also includes a native SDK control without an ArtPlayer instance. SDK 4.5.2 paused-seek stalls reproduce independently: its empty buffer-clear path preserves stale buffer metrics and can prevent further scheduling. A diagnostic backport of the SDK 5 buffer-level refresh confirms the cause; no production workaround ships. See the diagnosis. The compatibility risk remains open. Unsupported MSE is recorded as a playback capability gap, not acceptance. SDK event tests also cover external selections without explicit update, same-SDK source replacement, asynchronous formatter error/recovery, and actual setting clicks during SDK refresh. refactor/scripts/dash-sdk-types.test.mjs compares actual SDK-only and plugin consumers across ten compiler/module configurations, retaining upstream-only Node10/old DOM diagnostics. The exact peer dependency closure is hash-pinned in refactor/baselines/dash-type-dependencies.json; no type shim or paths mapping is used. See refactor validation for baseline failures and task plan for remaining type and release work.