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

9.3 KiB

Ads implementation and maintenance

Ads is a single preroll attached by artplayerPluginAds(option)(art). The factory and returned { name, skip, pause, play } are synchronous. Runtime input remains html/video/url plus timing, mute and i18n. Historical source/type fields are accepted by the compatibility declaration but are not implemented runtime aliases.

Module boundaries

File Responsibility
src/index.ts Public factory, checked legacy constructor capabilities, import-time style injection
src/options.ts Per-attachment defaults and the existing shallow validator schema
src/types.ts Normalized options and narrow core/DOM dependencies, reusing public input/result shapes
src/countdown.ts One owned timeout, whole-second counting, pause/resume and terminal cancellation; no core or DOM dependency
src/view.ts Existing HTML/CSS hooks, labels, ad element, mute and fullscreen icon rendering
src/resources.ts Instance-owned core/DOM subscriptions, guarded callbacks and disposal that continues after cleanup failures
src/session.ts Ready/first-play lifecycle, ad readiness, playback requests, completion, destruction and cross-module ownership

The entry calls options/session. Session composes view/countdown/resources. View receives only a parent, icons, options, three DOM utilities and callbacks, rather than the whole player. Style remains in src/style.less. There are no runtime imports from the ArtPlayer core package; all imports of its declarations are erased. Old cores do not need new lifecycle helpers.

Public declarations and imports

types/artplayer-plugin-ads.d.ts owns the callable namespace and public shapes. CommonJS bridges use .d.cts; ESM bridges use .d.mts. Root and /legacy accept historical inputs. The new /runtime entry resolves to the same JS implementation and restricts options to implemented fields. Option is accurate, LegacyOption preserves the published string duration declaration error, and WorkspaceOption represents the unpublished source/type declaration. CompatOption accepts both families. No runtime duration coercion was added. The existing validator rejects string durations before the single localized normalization assertion in options.ts. Translation objects still replace all four fields together.

The factory has a self-referencing .default, supporting both callable require and the published require(package).default access. This does not emulate the old noncallable CommonJS namespace object's identity or key set. The returned plugin and its lifecycle remain unchanged. Root and /runtime resolve to the same module for each module system.

The editor generator in scripts/plugin-editor-types.mjs accepts this type-only namespace shape explicitly and rejects unknown imports/exports. Generate docs globals with yarn build:ts; test their semantics and actual Monaco emit/run, not only parseability.

ADS-TYPE-01 is accepted with scope by the maintainer on 2026-09-12: Parameters<typeof factory>[0] accepts both old option objects, but reading totalDuration yields number | string | undefined; the published declaration yielded string | undefined. Workspace source/type fields also become optional. Frozen consumers reproduce these differences. This approval covers only these historical inference corrections, not general API changes. Preserve the diagnostic cases and migration examples in the README; do not claim every historical TS program compiles unchanged.

State and ordering

waiting -> active -> ended, with any state able to become destroyed. Ready arms the two existing first-play signals; only one can initialize. Repeated ready/play/metadata cannot create another view or timer. The normalized option belongs to this attachment even when a caller reuses the factory. Emitted options retain their identity and remain live: for example, changing totalDuration in an ad-click listener affects subsequent ticks.

For video, the element is created without a source. Error and loadedmetadata listeners are installed before assigning src. Metadata starts the timer and requests ad playback. HTML starts immediately. The ad video loops independently from the totalDuration countdown. Public pause/play still control only the countdown; they do not pause/resume the ad video. Native document visibility and the newer core visibility bridge both control that same timer; idempotent resume prevents duplicate timer chains. Initially hidden documents pause counting.

Skip first closes the timer and active subscriptions. For an initialized ad it requests main content playback, pauses the ad, hides the overlay and synchronously emits artplayerPluginAds:skip with the normalized option. This keeps the historical successful ordering. Reentrant destruction prevents subsequent dispatch. Repeated completion is inert. Before initialization, play/pause are inert; skip cancels the pending preroll and emits once without starting main playback or creating DOM. These early-call semantics repair historical exceptions and are covered separately from normal old behavior.

Internally requested play Promises are observed. An ad play rejection warns with the original error and completes the ad like a media load failure. Rejected main-content restoration also warns, while completion remains visible and synchronous. Direct consumer calls to art.play are not wrapped or changed. If an ad play settles after its owner ended/died, its own video is paused again; stale rejections cannot restart content.

Resource ownership

The session owns one countdown, active core/native listeners, a separate destroy subscription, and the overlay. Finishing releases active listeners and pauses video, but keeps the hidden overlay and source/error state available until destroy. Destroy cancels the timer, disposes all owned subscriptions, pauses/releases the ad src via load, and removes the overlay even when the core keeps its HTML. Only an actually created, owned template.$ads value is removed; destroy before initialization preserves a caller's own undefined-valued property. Foreign DOM listeners and caller-owned replacement template references are not removed.

Native DOM listeners are locally owned; the plugin does not mutate the core event registry or depend on new resource APIs. Cleanup guards stale callbacks and handles partial view construction/registration failure. A failure while cleaning one subscription does not stop the remaining cleanup. Root ownership is recorded as soon as it is appended.

Keep all artplayer-plugin-ads class names and the template.$ads hook. The full screen button uses the core fullscreen property; its icons reflect both the initial value and the core fullscreen event. Actual device/native fullscreen acceptance remains part of PKG-ADS-05.

Verification and remaining work

Use pinned Yarn/Node. yarn test:ads covers source/frozen releases and candidate lifecycle regressions; yarn test:browser test/browser/ads.spec.js covers real local media and old/new cores. yarn typecheck checks the strict package configuration. yarn build artplayer-plugin-ads generates main/legacy/ESM through the repository build. Do not hand-edit dist or copied docs assets.

The CI installed browser subset also runs ads.spec.js and ads-ui.spec.js. Their shared browser-candidate helper requires a fresh, hash-verified Ads tarball when ARTPLAYER_BROWSER_ARTIFACTS is set; it rejects ARTPLAYER_ADS_ARTIFACT overrides and never falls back to source on an invalid installation. Each candidate case attaches its installed file/archive identity. Without a map, existing explicit artifact or source-build selection remains available. See the repository scripts/browser-validation/README.md for the shared yarn test:package --browser command. This integration does not complete the separate Ads type, visibility or device gates.

Tests distinguish actual media decoding from injected errors/controlled clocks. Baseline and open release gates live in Ads validation. Public declaration reconciliation is recorded in PKG-ADS-04; complete historical distribution and demo validation remain PKG-ADS-06. yarn test:ads-types-package packs and installs outside the workspace, checks all installed bytes, frozen offline install, five compiler modes and Node exports. yarn test:browser test/browser/ads-editor-types.spec.js verifies actual Monaco integration. yarn test:browser test/browser/ads-ui.spec.js covers native desktop fullscreen, two real ad videos, independent mute/destroy and popup/narrow-layout behavior with three core versions. yarn test:ads-native-visibility launches an owned fresh installed Chrome profile and server to verify background-tab countdown pause, foreground resume and hidden-page destruction. It uses CDP noDefaults to avoid Playwright focus emulation; OS-window occlusion is excluded by one browser flag. It does not test minimizing or mobile background lifecycle. Override ARTPLAYER_NATIVE_CHROME for another installed Chrome path; set ARTPLAYER_BROWSER_ARTIFACTS and ARTPLAYER_ADS_ARTIFACT to verify a specific core/plugin build. The script preserves a report and trace under refactor/.cache, never uses the user's daily browser profile, and closes its owned browser and server. Run it sequentially with other port-8084 browser suites. Additional minimized-window/mobile lifecycle behavior and complete media-resource acceptance remain PKG-ADS-05. No claim of compatibility with every historical 4.x core is made.