Files
ArtPlayer/packages/artplayer-plugin-chapter/ARCHITECTURE.md
T

7.6 KiB

Chapter implementation and maintenance

The default export remains the synchronous artplayerPluginChapter(option?) factory. It registers artplayerPluginChapter with a synchronous update(option) method; the update object is required. All implementation modules are TypeScript.

Module map

File Responsibility
src/index.ts Public factory, ArtPlayer integration, metadata initialization, update transaction and listener ownership
src/chapters.ts DOM-free validation, sorting, Infinity replacement and gap insertion
src/progress.ts Owned chapter/title elements, progress widths and hover title positioning
src/stylesheet.ts Import-time global stylesheet installation, including deferred document readiness
src/types.ts Internal aliases derived from the existing public declaration; no runtime imports
src/style.less Existing CSS classes and theme variables
types/artplayer-plugin-chapter.d.ts Consumer declaration and compatibility boundary
types/*.d.cts, types/*.d.mts CJS/ESM bridges sharing the same public types

Dependencies flow from the entry to normalization and rendering. Neither of those modules receives an ArtPlayer instance. Rendering accepts a progress container and normalized ranges; only the entry reads player duration or emits/subscribes to player events. No new runtime dependency or new core API is required.

Updates and compatibility

The first video:loadedmetadata initializes chapters from the factory option. Each update clears the previous view/class/title before validating. A valid finite timeline sorts the caller array in place, replaces Infinity ends, validates ranges, then inserts empty-title gaps in that same array. Existing entry references are preserved. Do not replace this with immutable normalization without assessing old callers. Titles remain unmodified in caller objects and are trimmed for display.

The renderer creates the historical classes and data-start/end/duration/title attributes. A successful update adds the historical player class, then emits setBar('loaded', art.loaded || 0) synchronously. Progress events update the three bars; a shared boundary belongs to the later chapter for hover title selection, matching the old traversal order. Titles use textContent, never caller HTML. Title boxes are limited to the progress container width, including their padding, and display an ellipsis for long text. Full textContent and data-title remain intact. The renderer still clamps horizontal position; CSS owns text clipping, not normalization.

update({}) and non-array chapter input clear the view. Invalid types throw TypeError and invalid time ranges throw Error with the existing messages. NaN and non-finite starts/ends are now rejected; an Infinity end is still normalized first. Without a positive finite duration the view stays empty and caller data is untouched. Initialization remains once-only: after changing sources, callers explicitly update with chapters for the new duration. No implicit source ownership or new events were added.

Resource ownership

Each instance owns its progress container children and named setBar, metadata and destroy callbacks. On destroy it removes only those callbacks (including a pending once callback), removes its two owned DOM roots and player class, and releases the render state. This cleanup also applies to destroy(false) while leaving the core's retained player tree and other plugins alone. Retained plugin results become no-ops after destruction; a late metadata callback cannot recreate UI or mutate input.

The stylesheet is document-owned, shared across instances and retained after destroy. Deferred installation rechecks the ID when DOMContentLoaded fires, so two script loads during document parsing cannot create duplicate style elements. Its readiness listener is once-only. SSR import never reads document when it is unavailable.

Tests and changes

From the repository root:

yarn test:unit
yarn typecheck
yarn test:browser
yarn test:package
yarn build artplayer-plugin-chapter

test/chapter.test.js covers normalization and invalid timelines. Browser chapter tests run the historical and candidate plugin on published core 5.4.0, and cover real hover/seek, source changes, empty titles, invalid updates, multiple instances, early destruction and duplicate script loads. Browser playback tests also exercise the current core. For installed artifacts, use the mapping described in test/package/README.md at repository root. The runnable chapter demo uses yarn dev artplayer-plugin-chapter and the normal localhost:8082 example URL.

For time semantics, start at chapters.ts and run normalization plus browser tests. For UI, start at progress.ts/style.less and verify real mouse interaction. For event or cleanup changes, start at index.ts and rerun early destroy/multiple-instance tests. Any public declaration change also needs new/old compiler and tarball consumers.

test/browser/chapter-combinations.spec.js adds old/new core and plugin combinations with quality selection, actual thumbnail hover, narrow titles, element fullscreen, web fullscreen reparenting and trusted taps. Quality menus must finish opening before selection and lose pointer/focus before progress hover. The published core hides its menu with opacity/pointer-events; it does not use the candidate visibility rule. The published core also has a recorded Windows WebKit quality-position reset; keep that historical observation separate from the candidate's position-restoration assertion. The touch cases use a 390px viewport, hasTouch and an iPhone user agent on the desktop engines. They validate the mobile branch and trusted input, not an actual iPhone. See refactor/changes/2026-09-12-PKG-CHAPTER-05-combinations.md for current evidence and gaps.

The public declaration remains available to old compilers and exports Chapters, Option and Result as types. Modern import/require conditions select format-specific bridges; typesVersions provides the legacy subpath to old resolvers. All bridges refer to one contract and runtime import paths stay unchanged. The frozen published baseline is under refactor/baselines; never overwrite it with candidate behavior. Physical device, full editor and final release checks remain separate tasks.

The combination suite uses test/browser/chapter-hover.ts to wait for Playwright actionability/stable geometry before reading progress coordinates. Fullscreen state alone does not mean the bottom controls have finished their CSS transition. chapter-hover.spec.js exercises this boundary with an intentional native Web Animation after entering real fullscreen. It preserves title/opacity/hit-test assertions and uses actual installed artifacts in the installed browser scope. ARTPLAYER_CHAPTER_HOVER_BASELINE=1 restores the old immediate coordinate read for diagnosis only. This helper corrects test synchronization, not player behavior.

For CHAPTER-TIMING-01, ARTPLAYER_CHAPTER_TIMING_DIAGNOSTICS=1 adds a bounded native property/event/heartbeat recorder to the combination suite. The default suite is unchanged. yarn test:chapter-native runs a separate page with no ArtPlayer script to compare metadata restoration and one direct/deferred native correction. The unassisted Windows WebKit control currently fails its position assertion; these diagnostic cases are not ordinary CI source tests. Native five-second gaps remain even with deferred correction, so no timer workaround is in production. See refactor/changes/2026-09-15-PKG-CHAPTER-05-native-timing.md for exact commands, retained failures, measured boundaries and the still-open device/release gates.