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

125 lines
7.6 KiB
Markdown

# 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:
```sh
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.