mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-17 10:01:20 -08:00
335 lines
24 KiB
Markdown
335 lines
24 KiB
Markdown
# MediaBunny proxy maintenance
|
|
|
|
The public entry is `src/index.ts`: an optional-options factory returns the existing
|
|
ArtPlayer proxy callback and an HTMLCanvasElement. Public declarations remain in
|
|
`types/artplayer-proxy-mediabunny.d.ts`, with a separate ESM declaration entry.
|
|
The exact optional factory and HTMLCanvasElement result remain unchanged. The media
|
|
view in `types/media.d.ts` is opt-in and checked against the real VideoShim class.
|
|
|
|
## Current module map
|
|
|
|
| Module | Responsibility |
|
|
| --- | --- |
|
|
| `index.ts` | Optional factory, Canvas construction, alias installation and setup rollback |
|
|
| `canvas-bridge.ts` | Historical own/direct-prototype forwarding and native Canvas receiver binding |
|
|
| `entry-lifecycle.ts` | Entry resize/metadata/destroy subscriptions, alias ownership and shim cleanup |
|
|
| `cleanup.ts` | Attempt every release and preserve the first failure |
|
|
| `input.ts` | String/Blob/ReadableStream normalization, SDK Source/SourceRef pass-through, HLS detection and Input construction |
|
|
| `media.ts` | Internal SDK-backed media and track-mode types; not public consumer types |
|
|
| `tracks.ts` | Primary video and pairable audio selection, metadata/computed/live duration |
|
|
| `hls-state.ts` | HLS track snapshots, bitrate/name/language values and actual selected track identity |
|
|
| `preflight.ts` | Optional HEAD Range check, historical warning/error policy, cancellation and stale-result guard |
|
|
| `load-session.ts` | Ownership of the pending/active SDK Input, HEAD AbortController, deferred load events and deadline |
|
|
| `MediaBunnyEngine.ts` | Load/readiness coordinator, source identity, errors and terminal teardown |
|
|
| `playback.ts` | Playback intent and cancellable play/seek/replacement operations |
|
|
| `readiness.ts` | One metadata publication after both participants, guarded event sequences |
|
|
| `hls-selection.ts` | Latest selection intent, stable-media pairing queries and active replacement errors |
|
|
| `VideoEngine.ts` | Video capability/setup, source and seek ownership, first frames and terminal cleanup |
|
|
| `video-frames.ts` | Shared pending iterator reads, idempotent iterator release and existing frame timing policy |
|
|
| `video-renderer.ts` | Owned RAF, stale frame rejection, clock-driven draw/stall/end events |
|
|
| `video-poster.ts` | Per-request image callbacks, source guards and handler cleanup |
|
|
| `AudioEngine.ts` | Audio load/play generation, clock anchors and decoder-error forwarding |
|
|
| `audio-clock.ts` | Existing media-clock and future/partially-late buffer scheduling formulas |
|
|
| `audio-context.ts` | Context/gain setup, sample-rate fallback, shared native resume and terminal close |
|
|
| `audio-nodes.ts` | Scheduled source nodes, rate, ended handlers and complete stop/disconnect |
|
|
| `audio-pump.ts` | SDK buffer iterator, bounded batches, backpressure and stale-result guards |
|
|
| `audio-task.ts` | Cancelable waits and owned batch/backpressure timers |
|
|
| `VideoShim.ts` / `EventTarget.ts` | Video-like descriptors, synchronous/Promise forwarding and owned event lifecycle |
|
|
| `shim-values.ts` | Existing volume coercion and synthetic TimeRanges values |
|
|
| `shim-frames.ts` | Synthetic frame callbacks, per-instance RAF ownership and terminal cleanup |
|
|
| `engine-types.ts` / `engine-ports.ts` | Internal shim, coordinator and decoder interfaces |
|
|
| `m3u8.ts` | HLS refresh/selection revisions, source guards, events and terminal cleanup |
|
|
| `m3u8-model.ts` | Display names, selected IDs, label deduplication and sorted selector models |
|
|
| `m3u8-menu.ts` | Owned control/setting surfaces and stale callback guards |
|
|
| `m3u8-types.ts` | Narrow internal UI/shim contracts without changing public factory declarations |
|
|
|
|
The thirty-four production TypeScript modules use strict checking with `skipLibCheck: false`
|
|
and no ambient Node types. The coordinator and both decoder engines are checked
|
|
implementations; there are no remaining adjacent JavaScript declaration bridges.
|
|
All owned production JavaScript has been migrated. PKG-MB-08 also checks public consumer
|
|
compatibility and distinguishes supported partial media from wholly undecodable inputs.
|
|
|
|
The entry preserves native Canvas method priority, detached createElement invocation,
|
|
two reads of enumerable native method accessors, direct-prototype forwarding, bound shim
|
|
methods and strict assignment errors for getter-only properties. Avoid replacing these
|
|
with generic Reflect forwarding or a broad HTMLVideoElement cast. Entry bookkeeping stays
|
|
off the canvas surface. Destroy unregisters its callbacks and only removes art.mediabunny
|
|
when the alias still belongs to that entry. A custom historical host without off keeps inert
|
|
callbacks; a real Artplayer host removes them. Partial setup attempts all cleanup while
|
|
preserving the original setup error. HLS cleanup cannot strand the remaining entry resources.
|
|
|
|
The shim keeps its existing own-property order and direct prototype surface because
|
|
the entry copies those descriptors onto the canvas. Resource bookkeeping lives in a
|
|
WeakMap rather than adding accidental canvas properties. Frame callback IDs include
|
|
zero; destruction cancels pending callbacks and closes scheduling. The callback metadata
|
|
remains synthetic (`presentedFrames: 0`), not a claim of decoded frame accuracy.
|
|
|
|
EventTarget retains duplicate listeners, first-match removal, live-array `forEach`
|
|
mutation semantics, detail identity and listener exceptions during normal dispatch.
|
|
Its terminal state stops callbacks later in a dispatch when a listener destroys the
|
|
shim. Shim teardown closes events in `finally`, even if engine cleanup throws, and
|
|
subsequent teardown is inert. Engine teardown attempts input, audio and video cleanup
|
|
even when an earlier release throws, then preserves the first failure.
|
|
|
|
## Input and cancellation flow
|
|
|
|
Each `engine.load` invalidates the previous generation and cancels its session.
|
|
The new session owns an Input immediately after construction, before asynchronous
|
|
track selection. `engine.input` and `engine.media` become visible only after selection.
|
|
The session continues to own the Input during playback; these fields are aliases,
|
|
not separate disposal owners. Repeated cancellation/disposal is harmless.
|
|
|
|
Source replacement, timeout and destroy release that Input through SDK `Input.dispose()`.
|
|
MediaBunny controls its media fetch cancellation; do not pass a signal in UrlSource's
|
|
`requestInit` (the SDK explicitly excludes it). The separate HEAD check uses its own
|
|
AbortController. Without that constructor, stale HEAD results/errors are still ignored,
|
|
although the network request cannot be physically aborted by this adapter.
|
|
|
|
Cancellation settles the old load Promise without waiting for a stalled read. Its late
|
|
fulfillment/rejection remains observed but cannot update the current load. Active errors
|
|
retain `code: 4` and the SDK message; active timeout retains `Load timeout` and networkState 3.
|
|
Successful completion clears its deadline. Normal deferred waiting/loadstart ordering
|
|
remains; cancellation clears both callbacks. Destroy makes future loads inert.
|
|
|
|
Range behavior remains opt-in: no/`none` accept-ranges emits an error with a
|
|
`RangeNotSupported` detail and returns false, without setting the engine error property.
|
|
Network failure warns and continues; HLS strings and non-string sources skip HEAD.
|
|
HLS matching preserves the old case-insensitive `.m3u8` suffix/query/hash rule.
|
|
One explicit InputOptions assertion leaves unsupported values to SDK validation, preserving
|
|
the old error owner rather than silently converting arbitrary inputs.
|
|
|
|
## Playback and readiness ownership
|
|
|
|
Playback intent is separate from the temporary paused state during seek. Explicit pause
|
|
cancels pending play and prevents automatic resume; a newer seek settles the old operation
|
|
without allowing its completion or rejection to overwrite current state. Pending play waits
|
|
for the seek completion signal, resolved before optional resume to avoid a cycle.
|
|
A pending AudioContext resume is shared across play-pause-play; only the current operation
|
|
starts video and emits play/playing. Source change and destroy invalidate both operations.
|
|
Cancellation settles the public operation while observing the underlying late result;
|
|
video and audio ownership are detailed below. Native resume coalescing belongs to the
|
|
context owner: each new playback intent calls audio.play(), but shares the same pending
|
|
native resume. Sharing a canceled upper-level play Promise would incorrectly start video
|
|
before the latest audio intent is active.
|
|
|
|
Normal play, pause, seek/resume and HLS replacement event order is retained. Before each
|
|
event the coordinator rechecks ownership, since a listener may synchronously pause, seek,
|
|
load another source or destroy. Active play errors reject their Promise and restore paused
|
|
state; active seek errors additionally publish one media error. The synchronous currentTime
|
|
setter observes its otherwise unreturnable rejection; direct engine.seek still rejects.
|
|
Active replacement errors remain visible even after the media object changes. HLS queries
|
|
and replacement errors also carry a selection intent; obsolete intents cannot replace newer
|
|
results or surface an old failure. A still-current query retries if its media snapshot changed.
|
|
|
|
Metadata publishes once after both audio/video participants report; repeated or obsolete
|
|
callbacks cannot ready the current source. A parsed container with neither track fails with
|
|
code 4 and message `Input has no audio or video tracks.` before decoder/AudioContext setup.
|
|
This corrects the old false-ready state; normal valid input event order is preserved.
|
|
Existing-track unsupported-codec handling remains a decoder capability task, not evidence
|
|
that every capability failure has been fixed.
|
|
|
|
## Video frame ownership
|
|
|
|
VideoEngine keeps source/load generation separate from seek iteration and renderer activity.
|
|
A new source calls cancelPending before track selection starts, so an old seek cannot draw
|
|
while the new source is still parsing. Seek during capability detection waits for that load
|
|
without canceling its metadata; only the latest seek may install or draw its frames.
|
|
Ordinary paused seek still displays its first target frame and queues the second.
|
|
|
|
Iterator detachment is synchronous. The old return Promise is observed without blocking a
|
|
new load on obsolete reads; repeated release calls return the same cleanup Promise. Late
|
|
completion cannot clear the current iterator or its fetching flag. Stop/start shares an
|
|
already pending next() for the same iterator instead of issuing concurrent reads. Pause
|
|
invalidates render work; it does not cancel a legitimate paused seek's target frame.
|
|
|
|
Each seek after the initial iterator creates a fresh CanvasSink with its own two-canvas pool.
|
|
Concurrent obsolete decoders can otherwise overwrite the pixels of a queued frame even when
|
|
the proxy discards their returned objects. Sink identity is internal and may change on seek;
|
|
public canvas identity stays stable. Tests intercept sink prototypes to follow new instances.
|
|
CanvasSink retains poolSize 2, contain fit and track transparency. Its implementation closes
|
|
VideoSample after drawing into the pooled canvas. The proxy releases iterators and references;
|
|
WrappedCanvas is not a VideoFrame and must not receive close(). Default dropLateFrames remains
|
|
false, with the same tolerance/rate formula when enabled. Start preserves the queued second
|
|
frame until it is consumed instead of replacing it through premature prefetch. There is no
|
|
added frame copy.
|
|
|
|
Each renderer owns one RAF, including ID zero. Stop/destroy and event-listener reentry make
|
|
queued callbacks inert; an instance that never scheduled a frame cannot cancel another
|
|
instance's frame. Normal timeupdate, stall recovery and ended ordering is preserved, while
|
|
reentrant pause/source/destroy/restart stops the obsolete event sequence. Poster image
|
|
callbacks have source and request identity and are detached on replacement, draw or destroy.
|
|
|
|
Background decoder failure stops rendering and reports one media error through the coordinator,
|
|
even if audio cleanup fails. Iterator cleanup failures are observed and warned; explicit
|
|
stopIterator still rejects. Missing 2D context fails at use with a descriptive error, rather
|
|
than becoming falsely ready. Existing but undecodable tracks may use the historical
|
|
no-video/no-audio fallback only when at least one selected decoder is usable.
|
|
|
|
`readiness.requireDecoder` checks the actual audio/video sinks after their existing SDK
|
|
capability checks. It does not issue duplicate canDecode queries or change decoder load
|
|
return values. The metadata barrier checks before publishing metadata, and the load
|
|
completion checks before publishing data/canplay. Track replacement checks after decoder
|
|
setup and before seeking or publishing its success sequence. Without either sink, readyState
|
|
returns to zero and the existing code-4 error channel reports
|
|
`Input has no decodable audio or video tracks.` Active SDK query errors retain their own
|
|
messages. Valid audio-only/video-only and mixed inputs with one usable track retain normal
|
|
readiness/playback; canceled queries cannot report against a newer source or destroyed player.
|
|
|
|
## Audio clock and resource ownership
|
|
|
|
AudioEngine keeps source generation separate from pump IDs and cancellable play intent.
|
|
Pause invalidates a suspended resume even when the audio clock has not started; source
|
|
replacement invalidates audio before track parsing. Late resume, capability checks and
|
|
buffer reads cannot install a sink, schedule nodes or resurrect a destroyed instance.
|
|
Native AudioContext.resume is coalesced per context, while canceled callers settle early
|
|
and late rejections stay observed. A valid no-audio-track source retains the Web Audio
|
|
clock fallback; this is distinct from rejecting a container with neither track.
|
|
|
|
The clock retains the existing anchor/rate formula and squared gain with mute. Pause
|
|
captures media time; seek/rate changes cancel the old iterator and stop/disconnect its
|
|
scheduled nodes before rebuilding the queue at the new anchor. The renderer's ended
|
|
event freezes the audio clock and releases remaining nodes. Source load resets position.
|
|
Fully expired buffers do not allocate nodes; partially late buffers retain the old offset
|
|
calculation. Audio playbackRate changes still change pitch through native source playbackRate;
|
|
no new pitch-preservation or acoustic synchronization guarantee is introduced.
|
|
|
|
AudioPump owns 16-buffer batches, a zero-delay yield and the existing one-second/50ms
|
|
backpressure policy. Its timers include ID zero and are canceled with its task. Iterator
|
|
detachment is synchronous and return is observed once; obsolete cleanup cannot clear a
|
|
new iterator. Native AudioBuffers have no close() contract: the SDK closes its AudioSamples,
|
|
while the proxy releases buffer/node/iterator references. Every scheduled source is owned
|
|
before start, cleared on ended, stopped/disconnected on cancellation, and has its callback
|
|
removed. Cleanup attempts all nodes and context resources before throwing the first error;
|
|
background cleanup failures are observed, and do not replace an active decoder failure.
|
|
|
|
ContextOwner installs context/gain references only after successful setup. Setup failure
|
|
releases partial resources, destroy nulls references and observes the asynchronous close,
|
|
and repeated destroy is inert. Real-browser teardown assertions poll the native closed state.
|
|
Tests deliberately delay native resume completion or decoded buffer delivery, without
|
|
substituting fake audio decoding for native acceptance.
|
|
|
|
## HLS selection and UI ownership
|
|
|
|
Quality and audio requests share a per-engine intent token. A newer request supersedes an
|
|
unresolved older query, including cross-kind requests, before it can call replaceTracks.
|
|
Queries capture source generation and Input identity. If a prior replacement installs new
|
|
media while the latest query is awaiting track metadata or pairing, recompute against the
|
|
current media instead of dropping the latest intent or restoring an old counterpart.
|
|
The resolver rechecks cancellation between SDK calls; active failures still reject the caller.
|
|
An already-started replacement remains governed by Playback until the next replacement
|
|
begins. This does not claim that issuing a selection synchronously cancels SDK decoding.
|
|
Pairing preserves the current compatible counterpart and its mode; fallback chooses the
|
|
SDK primary pairable track and resets only the counterpart mode to auto. Invalid IDs retain
|
|
the historical current-track fallback. Media state getters suppress stale source results.
|
|
|
|
The UI has separate refresh revisions, selection intent and source identity. Old reads
|
|
cannot overwrite newer models; callbacks from replaced menus or a previous source are inert.
|
|
The same visible menu accepts a newer click while a prior selection is pending. Its own
|
|
loadedmetadata refresh does not cancel that valid selection's completion. Active failures
|
|
refresh actual track highlights before rejecting; stale failures, including a failure
|
|
superseded during recovery, cannot disturb the current intent. Background event-driven
|
|
refreshes observe failures through a warning instead of unhandled Promise rejections.
|
|
|
|
Models retain the existing menu names, title/Auto/getName defaults, sort order and notice
|
|
format. Auto remains an action, while the actual selected track ID controls the highlighted
|
|
item and displayed track label. Label deduplication is retained; if labels collide, keep the
|
|
selected ID as that label's representative so it is not hidden by the first item. Height
|
|
lookups use a map rather than repeated array searches during sorting. User getName callbacks
|
|
are guarded against synchronous source/destroy reentry between calls and before UI writes.
|
|
|
|
Each menu owns control and setting independently. Disabling either removes that surface;
|
|
video-only/single-audio topology removes audio menus, non-HLS clears both, and loadstart/error/
|
|
destroy invalidate pending work and clear owned UI. Teardown attempts every surface and
|
|
listener even after one failure, and cannot prevent the entry's shim destroy callback.
|
|
Control/setting callbacks retain their Promise and selected-label result; no new public
|
|
methods or declarations are exposed by the internal modules.
|
|
|
|
## Types and dependencies
|
|
|
|
The locked runtime SDK remains MediaBunny 1.56.1. Its pinned WebCodecs declaration
|
|
0.1.13 duplicates types now present in TypeScript 5.9.3's DOM library. The root Yarn
|
|
resolution selects `@types/dom-webcodecs@0.1.19` for both dependency paths; the SDK
|
|
itself is unchanged. This is a build-time declaration correction, not a browser polyfill.
|
|
The package typecheck adds ESNext.Disposable for SDK declarations while keeping the
|
|
ES2020 target; production source does not use `using` or require Symbol.dispose.
|
|
Public declarations do not import SDK types or modern DOM VideoFrame callback names.
|
|
The optional media view names the synthetic frame metadata directly; SDK track objects
|
|
are unknown until consumers narrow them using their own SDK version. `VideoShim implements
|
|
MediaBunnyShim` checks that this view matches implementation rather than masking JS with
|
|
an adjacent declaration. Explicit getter return types retain coercive unknown setters
|
|
internally while giving consumers numeric/boolean reads.
|
|
|
|
Default Result remains exactly HTMLCanvasElement, allowing callers to substitute a plain
|
|
canvas initializer or an entire historical factory. MediaBunnyCanvas combines the opt-in
|
|
media view with native Canvas methods winning collisions. MediaBunnyPlayer has an optional
|
|
mediabunny alias because it is absent before initialization and after destruction; there
|
|
is no global augmentation of Artplayer. Named exports are types only.
|
|
|
|
The .d.mts wrapper fixes NodeNext ESM default resolution. CommonJS keeps the historical
|
|
default declaration facade, and typesVersions resolves /legacy in TS 4.3/classic Node
|
|
resolution. Runtime remains directly callable and adds a nonenumerable, writable default
|
|
self alias for the 1.0.0 CommonJS usage. That property is deliberately not mandatory on
|
|
the default factory type. Whole-factory assignments are tested bidirectionally against
|
|
both frozen published declarations, in an actual isolated install as well as workspace
|
|
consumers. Do not switch to export= or a required default member without those checks.
|
|
|
|
`canPlayType()` still always returns maybe; buffered/played/seekable and RAF metadata remain
|
|
synthetic. None is proof that a codec or browser can play the input. Missing Web Audio or
|
|
WebCodecs in Windows WebKit remains an unsupported-capability control. Existing but
|
|
undecodable tracks no longer produce clock-only false readiness when all selected decoders
|
|
are unusable. Partial playback retains the usable decoder. Native/long-play/device acceptance
|
|
remains MB-09; no synthetic capability probe proves support on a physical Safari device.
|
|
|
|
## Validation and remaining work
|
|
|
|
```sh
|
|
yarn test:mediabunny
|
|
yarn test:mediabunny-types-package
|
|
node node_modules/typescript/bin/tsc -p packages/artplayer-proxy-mediabunny/tsconfig.json
|
|
yarn build artplayer-proxy-mediabunny
|
|
yarn test:browser test/browser/mediabunny-inputs.spec.js test/browser/mediabunny.spec.js test/browser/mediabunny-load.spec.js
|
|
yarn test:browser test/browser/mediabunny-shim.spec.js
|
|
yarn test:browser test/browser/mediabunny-video.spec.js
|
|
yarn test:browser test/browser/mediabunny-audio.spec.js
|
|
yarn test:browser test/browser/mediabunny-hls.spec.js
|
|
yarn test:browser test/browser/mediabunny-entry.spec.js
|
|
yarn test:browser test/browser/mediabunny-capability.spec.js
|
|
```
|
|
|
|
Node tests use actual SDK parsing and controlled lifecycle interleavings. Browser tests
|
|
include native decoding and separate pending ReadableStream cancellation with real timers.
|
|
`ARTPLAYER_MB_BASELINE=1` runs candidate load assertions against the immutable old main
|
|
to reproduce failures. `ARTPLAYER_MB_ARTIFACT` selects a built UMD main/legacy bundle;
|
|
`ARTPLAYER_MB_BROWSER_CANDIDATE=1` restricts browser runs to that candidate. Browser
|
|
reports record the implementation hash; archive the report and results before rerunning.
|
|
|
|
The source of historical truth is `refactor/baselines/mb-release.json` and its Git/npm
|
|
inputs, not a rebuilt dist. Package-specific evidence and remaining tasks are in
|
|
`refactor/mb-validation.md`. The full refactor plan is at the repository root.
|
|
|
|
PKG-MB-04 covers duplicate/trackless readiness, reentrant event sequences and
|
|
pending play/seek coordination. PKG-MB-05/06 cover late frame and audio callbacks,
|
|
including operations already inside decoders at cancellation. These bounded cases do not
|
|
replace long-run synchronization and combination validation. PKG-MB-07 covers HLS UI/selection
|
|
races using the actual new/old cores and SDK with controlled query ordering. PKG-MB-09 owns long-run AV sync, full core/proxy/plugin combinations and device
|
|
validation. PKG-MB-10 owns the demo, installed package and MPL/source notice gate.
|
|
Windows WebKit without WebCodecs/Web Audio is recorded as a capability failure,
|
|
not successful playback or a claim about all Safari installations.
|
|
|
|
## Installed browser validation
|
|
|
|
Run `yarn test:package --browser`, then pass its browser-artifacts.json as
|
|
ARTPLAYER_BROWSER_ARTIFACTS to `yarn test:browser:installed mediabunny`. The shared
|
|
roster includes this proxy and Document PiP. All ten MediaBunny browser files use
|
|
the verified installed UMD, including its bundled SDK; they never silently rebuild
|
|
a candidate when an installed map exists. Baseline and artifact overrides are
|
|
rejected in installed mode; its runner also rejects the candidate-only flag so
|
|
historical controls stay present. Source mode retains these explicit diagnostics.
|
|
|
|
Available-input attachments are an inventory, not proof that every listed input
|
|
ran. Each observation identifies the selected implementation and its provenance;
|
|
PiP combinations identify both packages. Local MP4/WebM/HLS fixtures remain test
|
|
media, not npm assets. Actual decoding, controlled races/unsupported decoder
|
|
branches, missing browser APIs and physical device evidence stay distinct. This
|
|
CI coverage does not close PKG-MB-09/10 or establish long-run/acoustic acceptance.
|