Files
ArtPlayer/docs/llms.txt

14507 lines
531 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
ArtPlayer documentation source bundle
Generated offline by yarn build:llm. Source text is preserved after LF normalization.
These are source references, not proof that every example or documented feature has passed release review.
===== Documentation Summary =====
===== packages/artplayer-vitepress/docs/en/advanced/built-in.md =====
# Advanced Properties
The `Advanced Properties` here refer to the `secondary properties` attached to the `instance`, which are less commonly used.
## `option`
The player's options.
<div className="run-code">▶ Run Code</div>
```js
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
console.info(art.option);
```
:::warning Note
If you directly modify this `option` object, the player will not respond immediately.
:::
## `template`
Manages all `DOM` elements of the player.
<div className="run-code">▶ Run Code</div>
```js
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
console.info(art.template);
console.info(art.template.$video);
```
:::warning Note
To easily distinguish between `DOM` elements and regular objects, all `DOM` elements within the player are named with a `$` prefix.
This is the definition of all `DOM` elements: [artplayer/types/template.d.ts](https://github.com/zhw2590582/ArtPlayer/blob/master/packages/artplayer/types/template.d.ts)
:::
`$container` is the div supplied by the caller; `$player` is the generated player root inside it. They are different elements. These fields retain node references bound during initialization rather than performing a new query on every read:
| Field | Default selector or source |
| --- | --- |
| `$container` | Supplied div container |
| `$player` | `.art-video-player` |
| `$video` | `.art-video` |
| `$track` | `track` |
| `$poster` | `.art-poster` |
| `$subtitle` | `.art-subtitle` |
| `$danmuku` | `.art-danmuku` |
| `$bottom` | `.art-bottom` |
| `$progress` | `.art-progress` |
| `$controls` | `.art-controls` |
| `$controlsLeft` | `.art-controls-left` |
| `$controlsCenter` | `.art-controls-center` |
| `$controlsRight` | `.art-controls-right` |
| `$layer` | `.art-layers` |
| `$loading` | `.art-loading` |
| `$notice` | `.art-notice` |
| `$noticeInner` | `.art-notice-inner` |
| `$mask` | `.art-mask` |
| `$state` | `.art-state` |
| `$setting` | `.art-settings` |
| `$info` | `.art-info` |
| `$infoPanel` | `.art-info-panel` |
| `$infoClose` | `.art-info-close` |
| `$contextmenu` | `.art-contextmenus` |
`art.query(selector)` and `art.template.query(selector)` are the same bound function and can be extracted for later calls. Queries always search descendants of the original $container, excluding the container itself. They do not follow player/media nodes moved outside it. Missing matches return null; invalid selectors retain querySelector errors. `art.video` returns the cached `template.$video`, which can be a canvas with a proxy. The original $track can become detached with a replaced video, so stored references need not remain connected inside the container.
`template.art` references the player. Optional `$mini` can appear after creating a mini window; the default mini node is attached to document.body outside the original container. Do not rebuild the player by replacing these fields. `template.init()` initializes the template: ordinary mode replaces container HTML, then binds nodes and the proxy. Repeating it is not a supported interface-reset workflow. `template.destroy(removeHtml)` only handles template DOM: true empties the container, false adds art-destroy. Use `art.destroy(removeHtml?)` for complete resource cleanup.
Read the template string from static `Artplayer.html`. The old root declaration's `art.template.html` is not an actual instance member and normally reads as undefined. `useSSR: true` preserves and queries supplied markup without filling missing nodes. Keep that markup complete and version-matched; instantiation still requires a browser. Runtime types retain nullable nodes while root types retain historical non-null shapes. A type assertion cannot repair incomplete SSR markup.
## `events`
Manages all `DOM` events for the player. It essentially proxies `addEventListener` and `removeEventListener`. When using the following methods to handle events, the events will also be automatically destroyed when the player is destroyed.
- The `proxy` method is used to proxy `DOM` events.
- The `hover` method is used to proxy custom `hover` events.
<div className="run-code">▶ Run Code</div>
```js
var container = document.querySelector('.artplayer-app');
var art = new Artplayer({
container: container,
url: '/assets/sample/video.mp4',
});
art.events.proxy(container, 'click', event => {
console.info('click', event);
});
art.events.hover(container, (event) => {
console.info('mouseenter', event);
}, (event) => {
console.info('mouseleave', event);
});
```
:::warning Note
If you need `DOM` events that should only exist during the player's lifecycle, it is strongly recommended to use these functions to avoid memory leaks.
:::
This registry manages DOM listeners registered through it, separately from player subscriptions using `art.on/off`. `art.proxy` is the same proxy shortcut. `proxy(target, name, callback, options?)` returns a disposer, or an array of disposers when name is an array. Call each disposer directly or pass it to `art.events.remove(dispose)` for early removal. Options retain native capture/once/passive/signal behavior; a normal listener's this is the native event target, not the player.
`hover` registers mouseenter/mouseleave and returns undefined; it does not create a new player hover event. `destroyEvents` holds cleanup functions and should not be mutated directly. `events.destroy()` clears the current registry, including core listeners; it does not destroy the player. Normally use `art.destroy()`. New proxies on a destroyed player do not register listeners.
`bindGlobalEvents({ window, document })` rebinds global listeners after a cross-document move. A successful replacement releases the old binding; a failed replacement preserves it. Omitted fields use the player node's document/window. Supply both when moving between windows. This method neither moves DOM nodes nor rebinds listeners your application installed itself.
## `storage`
Manages the player's local storage.
- The `name` property is used to set the cache `key`.
- The `set` method is used to set a cache.
- The `get` method is used to retrieve a cache.
- The `del` method is used to delete a cache.
- The `clear` method is used to clear all caches.
<div className="run-code">▶ Run Code</div>
```js
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.storage.set('test', { foo: 'bar' });
const test = art.storage.get('test');
console.info(test);
art.storage.del('test');
art.storage.clear();
```
:::warning Note
By default, all player instances share the same `localStorage`, and the default `key` is `artplayer_settings`.
If you want different players to use different `localStorage`, you can modify `art.storage.name`.
:::
<div className="run-code">▶ Run Code</div>
```js
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.storage.name = 'your-storage-key';
art.storage.set('test', { foo: 'bar' });
```
`name` is the localStorage key containing the entire JSON record; the key passed to set/get/del selects a field inside it. `get()` returns the full data and `get(key)` reads one field. Historical truthy-key selection means an empty string (and numeric zero at runtime) returns the full data; use nonempty string keys. set/del/clear synchronously return undefined.
`clear()` removes only the current name entry, not all localStorage for the origin. Changing name does not migrate old data. Same-origin instances with the same name share persisted data. `settings` is a per-instance error fallback, not a live mirror of persistence. Failed reads or writes use the corresponding fallback operation; restored access does not merge fallback data automatically. Storage uses JSON and does not preserve functions, circular objects or all other non-JSON values.
## `icons`
Manages all `svg` icons for the player.
<div className="run-code">▶ Run Code</div>
```js
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
console.info(art.icons.loading);
```
:::warning This is the definition of all icons:
[artplayer/types/icons.d.ts](https://github.com/zhw2590582/ArtPlayer/blob/master/packages/artplayer/types/icons.d.ts)
:::
These 27 default names share the same read behavior:
```text
loading, state, play, pause, check, volume, volumeClose, screenshot, setting, pip, arrowLeft, arrowRight, playbackRate, aspectRatio, config, lock, flip, unlock, fullscreenOff, fullscreenOn, fullscreenWebOff, fullscreenWebOn, switchOn, switchOff, error, close, airplay
```
Every read creates a new `<i class="art-icon art-icon-NAME">` wrapper, so two reads of `art.icons.play` are different objects. This is not a reference to the icon already mounted in a button; modifying a later wrapper does not change the existing button. Root declarations retain HTMLDivElement, although the wrapper is an i element; runtime types use HTMLElement.
Supply constructor `icons` options to replace default content or add custom names. Strings are parsed as HTML and should contain trusted markup. An HTMLElement is moved into the new wrapper rather than cloned; a later read may move it out of its previous wrapper. Use strings or your own cloned elements when independent copies are needed.
Names and values are shallow-copied at initialization. Later changes to `art.option.icons` do not replace that mapping or the rendered interface. Properties are read-only getters and are non-enumerable by default. An unconfigured ordinary custom name returns undefined; check it before appending.
## `i18n`
Manages the player's `i18n`.
- The `get` method is used to retrieve an `i18n` value.
- The `update` method is used to update the `i18n` object.
<div className="run-code">▶ Run Code</div>
```js
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
console.info(art.i18n.get('Play'));
art.i18n.update({
'zh-cn': {
Play: 'Your Play'
}
});
```
:::warning
Using `art.i18n.update` can only update the `i18n` after instantiation. If you want to update `i18n` before instantiation, please use the `i18n` option in the basic settings.
:::
`languages` holds dictionaries by language code, `language` is the selected dictionary, and `art` references the player. `update({ 'zh-cn': { Play: '播放' } })` deep-merges dictionaries and calls `init()`; both return undefined. init selects `art.option.lang.toLowerCase()`, without lowercasing dictionary keys. Simplified Chinese is built in; an unloaded language falls back to the original key text.
`get(key)` returns a nonempty translation or the key itself; an empty translation also falls back. Updating dictionaries does not redraw existing button, tooltip or menu text. After changing option.lang, init updates future lookups; it is not a complete interface-language switch API. These text keys from the historical declarations share the same lookup rules; runtime lookup also accepts application-defined keys:
```text
Context Menu
Lock
Video Info
Close
Video Load Failed
Volume
Progress
Back
Settings
Play
Pause
Rate
Mute
Video Flip
Horizontal
Vertical
Reconnect
Show Setting
Hide Setting
Screenshot
Play Speed
Aspect Ratio
Default
Normal
Open
Switch Video
Switch Subtitle
Fullscreen
Exit Fullscreen
Web Fullscreen
Exit Web Fullscreen
Mini Player
PIP Mode
Exit PIP Mode
PIP Not Supported
Fullscreen Not Supported
Subtitle Offset
Last Seen
Jump Play
AirPlay
AirPlay Not Available
```
## `notice`
Manages the player's notifications. Assign text through `show` or read its visibility state.
<div className="run-code">▶ Run Code</div>
```js
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', () => {
art.notice.show = 'Video Ready To Play';
})
```
:::warning
If you want to hide the `notice` immediately: `art.notice.show = '';`
:::
Assigning a string or Error displays plain text and restarts the hide timer. Errors use their trimmed message; ordinary strings retain their original text. Reading `notice.show` returns a boolean visibility state, not the last assigned text. Assigning false or an empty string hides immediately, without immediately clearing the text or canceling the old timer.
Each display reads its delay from `Artplayer.NOTICE_TIME`. `timer` is a timer handle, not a countdown. `destroy()` cancels the timer without hiding the node or destroying the player. New notices cannot appear after player destruction. The root entry retains the historical getter type; use `artplayer/runtime` for its accurate boolean type.
## `layers`
Manages the player's layers.
- The `add` method is used to dynamically add a layer.
- The `remove` method is used to dynamically remove a layer.
- The `update` method is used to dynamically update a layer.
- The `show` property is used to set whether all layers are displayed.
- The `toggle` method is used to toggle the display of all layers.
<div className="run-code">▶ Run Code</div>
```js
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', () => {
art.layers.add({
html: 'Some Text',
});
setTimeout(() => {
art.layers.show = false;
}, 1000);
});
```
:::warning For `Component Configuration`, please refer to:
[/component/layers.html](/component/layers.html)
:::
## `controls`
Manages the player's controls.
- The `add` method is used to dynamically add a control.
- The `remove` method is used to dynamically remove a control.
- The `update` method is used to dynamically update controls
- The `show` property is used to set whether to display all controls
- The `toggle` method is used to toggle the display of all controls
<div className="run-code">▶ Run Code</div>
```js
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', () => {
art.controls.add({
html: 'Some Text',
position: 'left',
});
setTimeout(() => {
art.controls.show = false;
}, 1000);
});
```
:::warning For `Component Configuration`, please refer to:
[/component/controls.html](/component/controls.html)
:::
## `contextmenu`
Manages the player's context menu
- The `add` method is used to dynamically add menu items
- The `remove` method is used to dynamically remove menu items
- The `update` method is used to dynamically update menu items
- The `show` property is used to set whether to display all menu items
- The `toggle` method is used to toggle the display of all menu items
<div className="run-code">▶ Run Code</div>
```js
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', () => {
art.contextmenu.add({
html: 'Some Text',
});
art.contextmenu.show = true;
setTimeout(() => {
art.contextmenu.show = false;
}, 1000);
});
```
:::warning For `Component Configuration`, please refer to:
[/component/contextmenu.html](/component/contextmenu.html)
:::
## `subtitle`
Manages the player's subtitle functionality
### Loading and switching {#subtitle-contract}
`switch(url, option?)` shallowly merges this call's options over `art.option.subtitle`, then overrides the URL with its first argument. It does not write the options back to `art.option.subtitle` or inherit options from the previous switch. `subtitle.option` holds the complete options for the most recently started load, which may still be pending or may have failed.
| Option | Default | Behavior |
| --- | --- | --- |
| `url` | `''` | Request URL; an empty value does not clear the existing track |
| `name` | `''` | Switch notice after a successful submission; the track label still uses `art.option.subtitle.name` or `Artplayer` |
| `type` | `''` | Explicit `vtt`, `srt`, or `ass`, otherwise inferred from the URL, not the response MIME type |
| `style` | `{}` | Subtitle container CSS; later assignments do not automatically clear earlier styles |
| `encoding` | `'utf-8'` | TextDecoder encoding |
| `escape` | `true` | Rendering reads `art.option.subtitle.escape`; a switch-only override does not change that setting |
| `onVttLoad` | Return the text unchanged | Synchronously transforms VTT text; an ordinary function receives the complete options as this |
Recognized SRT/ASS is converted to WebVTT before `onVttLoad`; VTT is passed directly to it. The callback must return a string; promises are not awaited. ASS conversion retains basic text and timing, not full ASS layout. Unrecognized types still undergo fetch and decoding, but skip the callback and pass the original URL to the native track.
`switch` returns `Promise<string | null | undefined>`: the submitted URL on success (usually a Blob URL after conversion), null without an available native text track, or undefined for an empty URL, superseded request, or cancellation on destruction. Fulfillment does not mean native cues have loaded. Subscribe to `subtitleLoad(cues, option)` before switching. The `subtitle.url` getter returns the current track URL; its setter starts a switch without exposing a Promise.
A new request cancels its predecessor and prevents late results from replacing current subtitles; destruction also settles pending requests. Active fetch, decoding, or conversion errors reject direct calls and update the notice. Construction and the URL setter observe their internal rejections. A later native track failure only updates the notice; it cannot reject an already fulfilled switch. Errors and empty URLs do not guarantee removal of the old track. Use `subtitle.show = false` to hide subtitles.
### Tracks, rendering, and cleanup {#subtitle-runtime}
`textTrack` reads the video's first TextTrack rather than searching by language or kind; proxy media without that capability may return undefined. `cues` and `activeCues` return fresh arrays containing the original cue objects, or empty arrays when unavailable or disabled. `SubtitleCue.text` contains the caption; `originalStartTime`/`originalEndTime` preserve the original times when adjusting an offset, and the track's optional `offset` holds the current offset. Reading an array does not clone this metadata.
`update()` synchronously redraws active cues. It is not the generic component update method and does not download subtitles again. With no active cues it only clears the view; otherwise it emits `subtitleBeforeUpdate`, creates `.art-subtitle-line[data-group]` elements for nonempty lines, then emits `subtitleAfterUpdate`. Rendering uses the player's escape setting. With escaping disabled, cue contents are inserted as trusted HTML. Switching, redrawing, or destroying inside a listener prevents the stale outer render from committing.
`show`/`toggle()` control the player's `art-subtitle-show` class and emit the boolean `subtitle` event; they do not stop downloads or the track. `style(object)` and `style(key, value)` return the subtitle container. The manager's `name` is `subtitle`. `destroyEvent` cleans up the current cuechange listener; it does not destroy the entire subtitle manager.
Low-level `init(fullOption)` does not fill in missing configuration; normally use `switch`. `createTrack(kind, url)` directly replaces the native track without downloading, converting, or merging options, and returns undefined. The new track uses hidden mode and its load event emits `subtitleLoad`. Replacement releases old listeners. Core-generated Blob URLs are revoked on replacement or player destruction; caller-supplied URLs remain caller-owned. Do not revoke a core-returned Blob URL as soon as switch fulfills. WebKit native fullscreen transitions may recreate the track and trigger another load, so loading is not a one-time event.
The root entry retains historical void style returns, `Promise<string>` switch returns, and the inherited component update declaration. Use `artplayer/runtime` for accurate returns and `update()`. Its `Subtitle` describes the manager; the root `Subtitle` describes configuration. Inherited members do not imply support for adding custom entries as with layers.
```ts
import Artplayer from 'artplayer/runtime';
import type { SubtitleCue } from 'artplayer/runtime';
const art = new Artplayer({ container: '.artplayer-app', url: '/assets/sample/video.mp4' });
art.on('subtitleLoad', (cues: SubtitleCue[]) => console.info(cues.length));
const node: HTMLDivElement = art.subtitle.style({ color: 'red' });
const loading: Promise<string | null | undefined> = art.subtitle.switch('/assets/sample/subtitle.srt');
void loading.catch(console.error);
art.subtitle.update();
void node;
```
- The `url` property sets and returns the current subtitle URL
- The `style` method sets the style of the current subtitle
- The `switch` method sets the current subtitle URL and options
- `textTrack` gets the current text track
- `activeCues` gets the list of currently active cues
- `cues` gets the overall list of cues
<div className="run-code">▶ Run Code</div>
```js
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', () => {
art.subtitle.url = '/assets/sample/subtitle.srt'
art.subtitle.style({
color: 'red',
});
});
```
## `info`
Manages the player's information panel, commonly used to view the current status of the player and video, such as version number, resolution, duration, etc.
- Control the panel's visibility via `art.info.show`
- The triggered event is named `info` (see the event documentation for details)
<div className="run-code">▶ Run Code</div>
```js
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', () => {
art.info.show = true;
setTimeout(() => {
art.info.show = false;
}, 3000);
});
```
## `loading`
Manages the player's loading layer
- The `show` property is used to set whether to display the loading layer
- The `toggle` property is used to toggle the display of the loading layer
<div className="run-code">▶ Run Code</div>
```js
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', () => {
art.loading.show = true;
setTimeout(() => {
art.loading.show = false;
}, 1000);
});
```
## `hotkey`
Manages the player's hotkey functionality
- The `add` method is used to add hotkeys
- The `remove` method is used to remove hotkeys
<div className="run-code">▶ Run Code</div>
```js
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
function hotkeyEvent(event) {
console.info('click', event);
}
art.on('ready', () => {
art.hotkey.add('Space', hotkeyEvent);
setTimeout(() => {
art.hotkey.remove('Space', hotkeyEvent);
}, 5000);
});
```
:::warning Note
These hotkeys only take effect after the player gains focus (e.g., after clicking on the player)
:::
Use `KeyboardEvent.code` strings such as `'Space'`, `'KeyK'` and `'ArrowLeft'`, not numeric keyCode values. add/remove return the hotkey manager. Removal requires the original callback. Different callbacks may share a key; the same callback is not added twice. A callback's this is the player. Adding a custom Space callback does not replace built-in play/pause.
`keys` stores callback arrays by code; `art` references the player. Desktop construction calls init automatically. `hotkey: false` disables built-in keys, but manually added callbacks still work. Mobile keyboard listening is opt-in through the existing init method; this is not physical-device acceptance. Repeated init does not duplicate the same default callbacks or document subscription.
Inputs, textareas, selects, editable content, composition and modified key events are excluded. Native activation keys on buttons/links and keys already handled by player controls do not trigger duplicate shortcuts. Matching callbacks prevent the native default action and are followed by the hotkey event; the player keydown event follows as well.
## `mask`
Manages the player's mask layer
- The `show` property is used to set whether to display the mask layer
- The `toggle` property is used to toggle the display of the mask layer
<div className="run-code">▶ Run Code</div>
```js
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', () => {
art.mask.show = false;
setTimeout(() => {
art.mask.show = true;
}, 1000);
});
```
## `setting`
Manages the player's settings panel
- The `add` method is used to dynamically add settings items
- The `remove` method is used to dynamically remove settings items
- The `update` method is used to dynamically update settings items
- The `show` property is used to set whether to display all settings items
- The `toggle` method is used to toggle the display of all settings items
<div className="run-code">▶ Run Code</div>
```js
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
setting: true,
flip: true,
playbackRate: true,
aspectRatio: true,
subtitleOffset: true,
});
art.on('ready', () => {
art.setting.show = true;
setTimeout(() => {
art.setting.show = false;
}, 1000);
});
```
:::warning For `Settings Panel`, please refer to
[/component/setting.html](/component/setting.html)
:::
## `plugins`
Manages the player's plugin functionality, with only one method `add` for dynamically adding plugins
<div className="run-code">▶ Run Code</div>
```js
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
function myPlugin(art) {
console.info(art);
return {
name: 'myPlugin',
something: 'something',
doSomething: function () {
console.info('doSomething');
},
};
}
art.on('ready', () => {
art.plugins.add(myPlugin);
});
```
## TypeScript service views
The current, unpublished refactor's `artplayer/runtime` entry supplies accurate declarations for the same implementation, including boolean notice.show, EventListener objects and service members. Root and legacy entries preserve historical declaration shapes. Runtime exports `EventRegistry`, `Storage`, `I18n<Host>`, `Hotkey<Host>` and `Notice` describe services; `Dictionary/Languages` describe language data.
```ts
import Artplayer from 'artplayer/runtime';
const art = new Artplayer({ container: '#player', url: '/video.mp4', hotkey: false });
const dispose = art.events.proxy(document, 'click', { handleEvent(event) { console.log(event.type); } });
art.events.remove(dispose);
const onSpace = function (this: Artplayer, event: KeyboardEvent) { console.log(this.id, event.code); };
art.hotkey.add('Space', onSpace);
art.hotkey.remove('Space', onSpace);
art.notice.show = 'Ready';
const visible: boolean = art.notice.show;
art.notice.show = false;
art.i18n.update({ en: { Play: 'Start' } });
console.log(visible, art.i18n.get('Play'));
```
## TypeScript template and icon views
The unpublished refactor's `artplayer/runtime` uses `Template<Host>`, `Icons` and media-capability types for nullable queries, unknown icons and proxy media. This example uses the same implementation as the root entry:
```ts
import Artplayer from 'artplayer/runtime';
const art = new Artplayer({
container: '#player', url: '/video.mp4',
icons: { customMark: '<span aria-hidden="true">*</span>' },
});
const query = art.query;
const player: HTMLDivElement | null = query('.art-video-player');
const icon: HTMLElement | undefined = art.icons.customMark;
if (player && icon) player.append(icon);
console.log(Artplayer.html, art.video === art.template.$video);
```
===== packages/artplayer-vitepress/docs/en/advanced/class.md =====
# Static Properties
Here, `static properties` refer to the `first-level properties` attached to the `constructor`, which are rarely used.
## `instances`
Returns an array of all player instances. This property can be useful when you need to manage multiple players simultaneously.
<div className="run-code">▶ Run Code</div>
```js
console.info([...Artplayer.instances]);
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
console.info([...Artplayer.instances]);
```
## `version`
Returns the version information of the player.
<div className="run-code">▶ Run Code</div>
```js
console.info(Artplayer.version);
```
## `env`
Retained in historical root declarations, but absent at runtime in both the frozen npm 5.4.0 baseline and the current 6.0.0 build. Reading it returns undefined; it is not a reliable environment check.
<div className="run-code">▶ Run Code</div>
```js
console.info(Artplayer.env);
```
## `build`
Retained in historical root declarations, but neither the frozen npm 5.4.0 baseline nor the current 6.0.0 runtime provides a build timestamp here. Reading it returns undefined; use your own release metadata when needed.
<div className="run-code">▶ Run Code</div>
```js
console.info(Artplayer.build);
```
## `config`
Returns the shared media-surface inventory, not the default player options (those are in Artplayer.option).
### Media surface inventory {#config-contract}
The getter returns the same object each time. Its properties, methods, events, and prototypes arrays describe media property names, callable methods, native events, and additional video-specific surface members. They are inventories, not capability guarantees: proxies and browsers may support only part of the surface.
The core reads config.events when installing native event forwarding, then forwards those events as video:eventName. Changing the array later does not add/remove listeners on existing players. Debug logging and proxy adapters also consume this inventory; editing it does not create the corresponding native methods or properties. The root Config type retains historical readonly tuples; runtime Config accurately exposes mutable string arrays. Mutation affects shared consumers, so preserve order and restore temporary test changes.
<div className="run-code">▶ Run Code</div>
```js
console.info(Artplayer.config);
```
```ts
import Artplayer from 'artplayer/runtime';
import type { Config } from 'artplayer/runtime';
const config: Config = Artplayer.config;
const nativeNames: string[] = config.events.slice();
const shared: boolean = Artplayer.config === config;
void [nativeNames, shared];
```
## `utils`
Returns the collection of utility functions for the player.
<div className="run-code">▶ Run Code</div>
```js
console.info(Artplayer.utils);
```
:::warning For all utility functions, please refer to the following address:
[artplayer/types/utils.d.ts](https://github.com/zhw2590582/ArtPlayer/blob/master/packages/artplayer/types/utils.d.ts)
:::
### Environment and types {#utils-contract}
`Artplayer.utils` is a public collection, not bound to a player instance. Text and data helpers work independently; DOM, image, style, and measurement helpers require a browser and the relevant nodes. Resources you create through these utilities are not automatically released when a player is destroyed.
`isBrowser`, `userAgent`, `isMobile`, `isSafari`, `isIOS`, and `isIOS13` are calculated at module load, not updated after window or UA changes. The UA uses `globalThis.CUSTOM_USER_AGENT` if set before loading, otherwise navigator. `isIOS13` also recognizes a touch-capable Macintosh. These are compatibility heuristics, not guarantees of media capabilities or OS versions.
The root `Utils` retains historical signatures; `Utils` from `artplayer/runtime` describes actual returns and broader DOM inputs. Internal helper shapes require only style for `StyledElement`, and target plus optional composedPath for `EventPathSource`; neither is a separately named export from the runtime entry. A generic query provides a static type without checking the actual element tag.
### DOM and styles {#utils-dom}
| Utility | Arguments, returns, and boundaries |
| --- | --- |
| `query(selector, parent?)` / `queryAll(selector, parent?)` | Default to document; return the first element or null / a fresh array. Search descendants, excluding the parent itself; invalid selectors still throw |
| `createElement(tag)` | Creates a native HTML element without attaching it |
| `addClass` / `removeClass` / `hasClass` | Accept a node and one class token; the first two return undefined, the last a boolean. Native classList argument errors are preserved |
| `append(parent, child)` | Moves same-realm Elements; other values are stringified and appended as HTML. Returns lastElementChild, falling back to lastChild, so appended text is not necessarily the return value; an empty parent may yield null |
| `remove(child)` / `replaceElement(newChild, oldChild)` | Return the removed node / new node; missing parents still cause errors |
| `siblings(target)` / `inverseClass(target, name)` | Return other elements under the same parent / remove the class from siblings and add it to target, returning undefined. Require parentElement |
| `setStyle(element, key, value)` / `setStyles(element, styles)` | Assign directly to style and return the original element. setStyles includes inherited enumerable string keys; neither adds units nor clears previous styles |
| `getStyle(element, key, numberType = true)` | Reads getComputedStyle/getPropertyValue; defaults to parseFloat, yielding NaN for nonnumeric values. Pass false for the raw string. Use CSS names such as `font-size` |
| `setStyleText(id, cssText)` | Replaces textContent of an existing element with that id, otherwise creates style. While the document is loading, attachment to head waits for DOMContentLoaded. No Promise or automatic disposal; use a dedicated id |
| `getRect(element)` | Returns the original getBoundingClientRect DOMRect, beyond the four fields in the root type |
| `getIcon(key = '', html = '')` | Returns a new i element with art-icon and art-icon-key classes, using append rules for contents; elements are moved, not cloned |
| `tooltip(target, message, position = 'top')` | On desktop, sets aria-label and hint--rounded / hint--position classes; does nothing on mobile. Later calls do not remove earlier direction classes |
HTML strings are not sanitized. Use textContent for plain text or escape for the intended context; do not pass untrusted content directly to append/getIcon.
### Events and measurement {#utils-measure}
| Utility | Behavior |
| --- | --- |
| `getComposedPath(event)` | Calls composedPath with its original receiver and returns its array directly. Otherwise walks target.parentNode and appends window in a browser. The fallback does not reproduce a complete Shadow DOM path |
| `includeFromEvent(event, target)` | Tests membership in that path, rather than performing a separate DOM contains query |
| `isInViewport(element, offset = 0)` | Tests rectangle/window intersection, including boundaries. It does not prove full visibility or lack of occlusion, and retains the historical offset calculation |
| `getSafeAreaInsets()` | Attaches a temporary invisible node and reads numeric env(safe-area-inset-*) values, using zero for unparseable values. Removes the node on success or failure; requires document.body |
| `supportsFlex()` | Only tests whether an element accepts display:flex, not whether layout works correctly |
### Subtitles, images, and files {#utils-resources}
| Utility | Behavior and ownership |
| --- | --- |
| `srtToVtt(text)` / `assToVtt(text)` | Synchronously return WebVTT text. The former normalizes milliseconds and some style markers; the latter extracts basic Dialogue timing/text. Neither is a full subtitle validator or ASS renderer |
| `vttToBlob(text)` | Returns a text/vtt Blob URL, not a Blob. The caller must revoke the URL when finished |
| `getExt(url)` | Removes query/fragment, trims and lowercases, then takes text after the last dot. Without a dot it returns the remaining string; no resource or MIME check |
| `download(url, name)` | Creates, clicks, and removes a temporary download link. No completion result or guarantee the browser saves a file; does not revoke the input URL |
| `loadImg(url, scale?)` | Resolves with a loaded HTMLImageElement. Falsy scale or1 returns the original image; other values use canvas/toBlob and load the scaled result. Its image.src is a caller-owned Blob URL that must be revoked after use |
`loadImg` does not set crossOrigin and has no public cancellation or timeout option. A cross-origin image can display while still making canvas unreadable. Image loading, canvas, or encoding failures reject the Promise. Successful listeners are removed; failure during scaling releases an already created Blob URL. Revoke a scaled result's src only after the page no longer needs it; the original image URL is not owned by this function.
### Data, errors, and scheduling {#utils-data}
| Utility | Behavior |
| --- | --- |
| `def(object, key, descriptor)` | Object.defineProperty itself, returning the original object. The root string-key overload's historical void return is not the runtime result |
| `has(object, key)` / `get(object, key)` | Test own-property presence / retrieve an own descriptor, returning undefined if missing |
| `mergeDeep(...objects)` | Creates a new top-level object using Object.keys. Two non-array objects merge recursively; two arrays use the historical concat/spread rule, which can flatten nested arrays in the latter array. Not a complete deep clone: unmerged values retain references. Cyclic merging is unsupported; __proto__ is stored as own data |
| `clamp(number, a, b)` | Clamps between either ordering of endpoints; NaN remains NaN |
| `secondToTime(seconds)` | Floors to mm:ss, or hh:mm:ss from one hour; hours can exceed two digits. Falsy inputs yield00:00. Does not additionally validate negative or nonfinite inputs |
| `escape(text)` / `unescape(text)` | Only the five fixed entities for ampersand, angle brackets, and single/double quotes, in one pass. unescape is not a general HTML entity parser |
| `capitalize(text)` | Uppercases only the first character |
| `ArtPlayerError(message?, context?)` / `errorHandle(condition, message?)` | Error subclass named ArtPlayerError, with context used for supported stack capture / throw that error for falsy conditions, otherwise return the original value |
| `silencePromise(value)` | If catch is callable, return the result of catching and consuming a rejection; otherwise return the value unchanged. Uses catch capability, not instanceof Promise; synchronous errors thrown by catch itself still propagate |
| `sleep(milliseconds = 0)` | Resolves with undefined after a timer, without cancellation |
| `debounce(callback, duration)` | Trailing call with the last arguments and call receiver. Ignores the historical context argument; wrapper returns undefined |
| `throttle(callback, duration)` | Synchronous leading call, dropping calls during the wait without a trailing call. Preserves the call receiver; wrapper returns undefined |
Neither wrapper offers cancel/flush or automatic cancellation on player destruction. Throttle enters its waiting period only after the callback returns normally, preserving synchronous reentry and another attempt after a thrown error. Historical root return inference does not change this behavior.
```ts
import Artplayer from 'artplayer/runtime';
import type { Utils } from 'artplayer/runtime';
const utils: Utils = Artplayer.utils;
const fragment = document.createDocumentFragment();
const missing: HTMLVideoElement | null = utils.query<HTMLVideoElement>('video', fragment);
const div = utils.createElement('div');
const styled: HTMLDivElement = utils.setStyle(div, 'fontSize', '20px');
const width: string = utils.getStyle(div, 'width', false);
const invoke = utils.debounce(function (this: { value: number }, step: number) {
this.value += step;
}, 20);
const returned: void = invoke.call({ value: 0 }, 1);
void [missing, styled, width, returned];
```
## `scheme`
Returns the validation schema for player options.
<div className="run-code">▶ Run Code</div>
```js
console.info(Artplayer.scheme);
```
## `Emitter`
Returns the constructor of the event emitter.
<div className="run-code">▶ Run Code</div>
```js
console.info(Artplayer.Emitter);
```
## `validator`
Returns the validation function for options.
<div className="run-code">▶ Run Code</div>
```js
console.info(Artplayer.validator);
```
## `kindOf`
Returns the type detection utility function.
<div className="run-code">▶ Run Code</div>
```js
console.info(Artplayer.kindOf);
```
## `html`
Returns the `html` string required by the player.
<div className="run-code">▶ Run Code</div>
```js
console.info(Artplayer.html);
```
This is the current version's static base markup, including required classes and media/control nodes, not a snapshot of an instance's current DOM. When reusing preinserted markup through `useSSR: true`, keep its complete structure and version aligned. The option does not make construction work outside a browser. Actual template instances have no `html` member; the historical root type retains that member for compatibility, but it does not replace the static entry.
## `option`
Returns the default options of the player.
<div className="run-code">▶ Run Code</div>
```js
console.info(Artplayer.option);
```
===== packages/artplayer-vitepress/docs/en/advanced/event.md =====
# Instance Events
Player events are divided into two types: `native events` of the video (prefixed with `video:`), and `custom events`.
Listening to events:
<div className="run-code">▶ Run Code</div>
```js{6}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('video:canplay', () => {
console.info('video:canplay');
});
```
Listening to an event only once:
<div className="run-code">▶ Run Code</div>
```js{6}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.once('video:canplay', () => {
console.info('video:canplay');
});
```
Manually triggering an event:
<div className="run-code">▶ Run Code</div>
```js{6}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.emit('focus');
```
Removing an event:
<div className="run-code">▶ Run Code</div>
```js{8}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
const onReady = () => {
console.info('ready');
art.off('ready', onReady);
}
art.on('ready', onReady);
```
:::warning For a complete list of events, please refer to:
[artplayer/types/events.d.ts](https://github.com/zhw2590582/ArtPlayer/blob/master/packages/artplayer/types/events.d.ts)
:::
## Subscription and synchronous dispatch {#emitter-contract}
Players inherit `Artplayer.Emitter`. `on(name, callback, ctx?)`, `once`, `off`, and `emit(name, ...args)` all return the current instance. They are neither DOM addEventListener nor a Promise-based message queue.
- on keeps registration order. Registering the same function twice invokes it twice. ctx is passed unchanged as an ordinary function's this; omission means undefined, not an automatic player binding. Arrows retain their lexical this.
- emit synchronously walks a snapshot taken at dispatch start. Added listeners wait until a later dispatch; removed ordinary listeners already in the snapshot still run. Arguments retain their references; callback return values are ignored.
- once removes itself before invoking the callback and prevents nested dispatch from consuming the same registration twice, even if the callback throws. off(name, callback) removes all ordinary/once registrations for that function regardless of ctx; off(name) removes every listener for the name.
- A synchronous throw stops subsequent listeners in that dispatch and propagates through the call stack. Async callbacks are not awaited; handle their failures yourself. The name error has no special Node EventEmitter behavior.
- e is the lazily created registry of fn/ctx records and remains a visible historical interface. A fresh standalone Emitter may not have it yet. Use on/off rather than editing the table. Numeric keys share their string-equivalent channel; symbols have separate keys. The root player's historical overloads accept narrower names than the accurate entry or a generic Emitter.
Manual emit only sends a notification; it does not replace player methods. Emitting built-in names may also trigger internal listeners. Destruction releases core-owned DOM/internal subscriptions but does not clear every user registration; remove unwanted subscriptions if you retain the instance. The advanced-properties guide explains the separate DOM listener manager, art.events.
## Native forwarding {#native-event-contract}
The default media events below are forwarded with a `video:` prefix and the original Event object. The browser or proxy determines when they occur:
`abort`, `canplay`, `canplaythrough`, `durationchange`, `emptied`, `ended`, `error`, `loadeddata`, `loadedmetadata`, `loadstart`, `pause`, `play`, `playing`, `progress`, `ratechange`, `seeked`, `seeking`, `stalled`, `suspend`, `timeupdate`, `volumechange`, `waiting`.
The `video:error` argument is not a MediaError or Error instance; inspect `art.video.error` when needed. Historical types include `video:complete` and `video:encrypted`, but neither appears in the default config.events, so the core does not automatically forward them. An adapter must provide forwarding or configure the event inventory before construction. A declared name does not prove an event is produced at runtime.
Global forwarding uses the player's currently bound document/window and passes the original Event:
| Prefix | Default names |
| --- | --- |
| `document:` | click, mouseup, keydown, touchend, touchcancel, touchmove, mousemove, pointerup, contextmenu, pointermove, visibilitychange, webkitfullscreenchange |
| `window:` | resize, scroll, orientationchange |
These are not restricted to interactions inside the player. events.bindGlobalEvents can rebind them to the owning window. Destruction stops native forwarding. Arguments preserve native subtypes such as KeyboardEvent/MouseEvent; forwarding does not guarantee that a browser produces every event.
## Custom payloads and timing {#custom-event-contract}
Internal and user listeners share synchronous dispatch. Internal listeners registered during construction can emit a custom event before later user listeners receive the native forwarding event. Do not assume one total ordering across all browsers and proxies.
### Media and lifecycle
| Event | Payload and actual stage |
| --- | --- |
| `ready` | No arguments; once in the first successfully handled video:canplay, after isReady is set. Does not wait for async plugins, subtitles, or SDKs |
| `restart` | Submitted URL; for a ready player with an existing URL, the active source change emits at canplay when the actual media URL changed. Assigning the same URL need not emit it |
| `play` | No arguments; art.play emits after the media play call succeeds while its operation remains current. Direct video.play does not generate this custom event, though native video:play can occur |
| `pause` | No arguments; art.pause emits synchronously after calling media pause and updating the notice, even if already paused. Separate from video:pause |
| `destroy` | No arguments; after native resources/template cleanup, instance removal, and setting isDestroy true. Do not assume mounted DOM inside the callback; destroy(false) separately preserves DOM |
| `error` | Original error value and retry count; emitted when reconnection submits a retry after waiting, not on every native error or every async failure |
| `seek` | currentTime after assignment and the original requested time (number or string), not native seeked completion |
| `muted` | Boolean supplied to art.muted, including repeated assignments. Observe video:volumechange for direct video.muted changes |
| `screenshot` | PNG data URI after image capture and an attempted download, not proof a file was saved. getDataURL/getBlobUrl alone do not emit it |
| `airplay` | No arguments; after invoking an available native picker, not confirmation of remote connection or playback |
| `raf` | No arguments; emitted during playback when USE_RAF was enabled before construction. Not a decoded-video-frame callback |
### UI and input
| Event | Payload and actual stage |
| --- | --- |
| `info`, `layer`, `loading`, `mask`, `subtitle`, `contextmenu`, `control`, `setting` | Boolean assigned to show; repeated identical assignments can emit again. Not animation completion |
| `focus` / `blur` | Original document click/contextmenu event, classified by whether its path includes the player. Not DOM focus/blur; can emit even without a state change |
| `click` / `dblclick` | Video click event. Double-click is counted within DBCLICK_TIME; the first click emits immediately, before playback/fullscreen actions |
| `hover` | Enter/leave boolean and original mouseenter/mouseleave event |
| `mousemove` | MouseEvent from the player node, not throttled document coordinates |
| `hotkey` | KeyboardEvent after matching shortcut callbacks, subject to focus/input filtering |
| `keydown` | KeyboardEvent after desktop shortcut dispatch, even without a matching key or player focus. Mobile does not automatically install this shortcut dispatcher; use document:keydown for the raw global event |
| `resize` | No arguments; window debounce, metadata, display-mode changes, or explicit layout paths, not just window:resize |
| `view` | Viewport-intersection boolean after leading scroll throttling, not complete visibility or occlusion detection |
| `lock` | Boolean after the built-in lock plugin updates state, not an observer of direct isLock assignment |
| `setBar` | Type, fraction, and optional original mouse/touch event. Built-in types are loaded/played/hover; programmatic and keyboard updates may omit the third argument. A progress UI protocol, not playback completion |
### Size, display, and subtitles
| Event | Payload and actual stage |
| --- | --- |
| `aspectRatio` / `flip` | String after the setter normalizes an empty value; not necessarily a member of the built-in selector list |
| `autoHeight` / `autoSize` | Height number / {width, height} after valid media dimensions allow layout. No event when calculation is unavailable |
| `fullscreen` / `fullscreenWeb` / `mini` / `pip` | Boolean from the display adapter's observation or transition. Duplicate-assignment behavior varies by mode; not a universal replacement for request completion |
| `fullscreenError` | Owned native fullscreen error event/adapter value. A request Promise rejection may instead only update the notice; this does not collect every fullscreen failure |
| `subtitleOffset` | Original requested offset; stored offset clamps to[-10,10]. No event without cues |
| `subtitleBeforeUpdate` / `subtitleAfterUpdate` | Cue arrays, not individual cues, synchronously before/after rendering. Neither emits without active cues |
| `subtitleLoad` | Current cue array and the subtitle manager's current options (possibly null), after native track load. Not an alias for switch fulfillment |
## TypeScript event views {#event-types}
The root entry retains historical declarations and custom augmentation: video:error was typed as Error and subtitle updates as one VTTCue. It also has array overloads through SubtitleUpdateEvents. Use `artplayer/runtime` for accurate contextual inference: Event for native media payloads, SubtitleCue arrays for updates, unknown for error/fullscreenError, and a string-capable second seek argument. Unknown custom events retain unknown arrays without runtime payload validation. A generic Emitter can declare its own event tuples:
```ts
import Artplayer from 'artplayer/runtime';
import type { Events, SubtitleCue } from 'artplayer/runtime';
const bus = new Artplayer.Emitter<{ progress: [value: number] }>();
const context = { total: 0 };
bus.on('progress', function (value) { this.total += value; }, context);
bus.emit('progress', 2);
const art = new Artplayer({ container: '.artplayer-app', url: '/assets/sample/video.mp4' });
art.on('video:error', (event: Event) => console.info(event.type));
art.on('subtitleBeforeUpdate', (cues: SubtitleCue[]) => console.info(cues.length));
const seekArgs: Events['seek'] = [0, '0'];
void seekArgs;
```
## `ready`
Triggered when the player is ready for the first time.
<div className="run-code">▶ Run Code</div>
```js{6}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', () => {
console.info('ready');
});
```
## `restart`
Triggered when the player switches URLs and becomes ready to play.
<div className="run-code">▶ Run Code</div>
```js{10}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', () => {
art.url = '/assets/sample/video.mp4'
});
art.on('restart', (url) => {
console.info('restart', url);
});
```
## `pause`
Triggered when the player is paused.
<div className="run-code">▶ Run Code</div>
```js{6}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('pause', () => {
console.info('pause');
});
```
## `play`
Triggered when the player starts playing.
<div className="run-code">▶ Run Code</div>
```js{6}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('play', () => {
console.info('play');
});
```
## `hotkey`
Triggered when a player hotkey is pressed.
<div className="run-code">▶ Run Code</div>
```js{6}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('hotkey', (event) => {
console.info('hotkey', event);
});
```
## `destroy`
Triggered when the player is destroyed.
<div className="run-code">▶ Run Code</div>
```js{10}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', () => {
art.destroy();
});
art.on('destroy', () => {
console.info('destroy');
});
```
## `focus`
Triggered when the player gains focus.
<div className="run-code">▶ Run Code</div>
```js{6}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('focus', (event) => {
console.info('focus', event);
});
```
## `blur`
Triggered when the player loses focus.
<div className="run-code">▶ Run Code</div>
```js{6}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('blur', (event) => {
console.info('blur', event);
});
```
## `dblclick`
Triggered when the player is double-clicked.
<div className="run-code">▶ Run Code</div>
```js{6}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('dblclick', (event) => {
console.info('dblclick', event);
});
```
## `click`
Triggered when the player is clicked.
<div className="run-code">▶ Run Code</div>
```js{6}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('click', (event) => {
console.info('click', event);
});
```
## `error`
Triggered when an error occurs while the player is loading a video.
<div className="run-code">▶ Run Code</div>
```js{6}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/404.mp4',
});
art.on('error', (error, reconnectTime) => {
console.info(error, reconnectTime);
});
```
## `hover`
Triggered when the mouse enters or leaves the player.
<div className="run-code">▶ Run Code</div>
```js{6}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('hover', (state, event) => {
console.info('hover', state, event);
});
```
## `mousemove`
Triggered when the mouse moves over the player.
<div className="run-code">▶ Run Code</div>
```js{6}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('mousemove', (event) => {
console.info('mousemove', event);
});
```
## `resize`
Triggered when the player's dimensions change.
<div className="run-code">▶ Run Code</div>
```js{6}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('resize', () => {
console.info('resize');
});
```
## `view`
Triggered when the player enters the viewport.
<div className="run-code">▶ Run Code</div>
```js{6}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('view', (state) => {
console.info('view', state);
});
```
## `lock`
Triggered when the lock state changes on mobile devices.
<div className="run-code">▶ Run Code</div>
```js{6}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
lock: true,
});
art.on('lock', (state) => {
console.info('lock', state);
});
```
## `aspectRatio`
Triggered when the player's aspect ratio changes.
<div className="run-code">▶ Run Code</div>
```js{8}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
aspectRatio: true,
setting: true,
});
art.on('aspectRatio', (aspectRatio) => {
console.info('aspectRatio', aspectRatio);
});
```
## `autoHeight`
Triggered when the player's height is automatically set.
<div className="run-code">▶ Run Code</div>
```js{10}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', () => {
art.autoHeight();
});
art.on('autoHeight', (height) => {
console.info('autoHeight', height);
});
```
## `autoSize`
Triggered when the player's size is automatically set.
<div className="run-code">▶ Run Code</div>
```js{7}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
autoSize: true,
});
art.on('autoSize', () => {
console.info('autoSize');
});
```
## `flip`
Triggered when the player is flipped.
<div className="run-code">▶ Run Code</div>
```js{8}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
flip: true,
setting: true,
});
art.on('flip', (flip) => {
console.info('flip', flip);
});
```
## `fullscreen`
Triggered when the player enters or exits windowed fullscreen mode.
<div className="run-code">▶ Run Code</div>
```js{7}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
fullscreen: true,
});
art.on('fullscreen', (state) => {
console.info('fullscreen', state);
});
```
## `fullscreenError`
Triggered when a windowed fullscreen error occurs.
<div className="run-code">▶ Run Code</div>
```js
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', () => {
art.fullscreen = true;
});
art.on('fullscreenError', (event) => {
console.info('fullscreenError', event);
});
```
## `fullscreenWeb`
Triggered when the player enters or exits web fullscreen mode.
<div className="run-code">▶ Run Code</div>
```js{7}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
fullscreenWeb: true,
});
art.on('fullscreenWeb', (state) => {
console.info('fullscreenWeb', state);
});
```
## `mini`
Triggered when the player enters or exits mini mode.
<div className="run-code">▶ Run Code</div>
```js{10}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', () => {
art.mini = true;
});
art.on('mini', (state) => {
console.info('mini', state);
});
```
## `pip`
Triggered when the player enters or exits Picture-in-Picture mode.
<div className="run-code">▶ Run Code</div>
```js{7}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
pip: true,
});
art.on('pip', (state) => {
console.info('pip', state);
});
```
## `screenshot`
Triggered when the player takes a screenshot.
<div className="run-code">▶ Run Code</div>
```js{7}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
screenshot: true,
});
art.on('screenshot', (dataUri) => {
console.info('screenshot', dataUri);
});
```
## `seek`
Triggered when the player performs a time seek.
<div className="run-code">▶ Run Code</div>
```js{6}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('seek', (currentTime) => {
console.info('seek', currentTime);
});
```
## `subtitleOffset`
Triggered when the subtitle offset changes in the player.
<div className="run-code">▶ Run Code</div>
```js{11}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
subtitleOffset: true,
subtitle: {
url: '/assets/sample/subtitle.srt',
},
setting: true,
});
art.on('subtitleOffset', (offset) => {
console.info('subtitleOffset', offset);
});
```
## `subtitleBeforeUpdate`
Triggered before subtitles are updated.
<div className="run-code">▶ Run Code</div>
```js{9}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
subtitle: {
url: '/assets/sample/subtitle.srt',
},
});
art.on('subtitleBeforeUpdate', (cues) => {
console.info('subtitleBeforeUpdate', cues);
});
```
## `subtitleAfterUpdate`
Triggered after subtitles are updated.
<div className="run-code">▶ Run Code</div>
```js{9}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
subtitle: {
url: '/assets/sample/subtitle.srt',
},
});
art.on('subtitleAfterUpdate', (cues) => {
console.info('subtitleAfterUpdate', cues);
});
```
## `subtitleLoad`
Triggered when subtitles are loaded.
<div className="run-code">▶ Run Code</div>
```js{9}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
subtitle: {
url: '/assets/sample/subtitle.srt',
},
});
art.on('subtitleLoad', (option, cues) => {
console.info('subtitleLoad', cues, option);
});
```
## `info`
Triggered when the info panel is shown or hidden.
<div className="run-code">▶ Run Code</div>
```js{6}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('info', (state) => {
console.log(state);
});
```
## `layer`
Triggered when a custom layer is shown or hidden.
<div className="run-code">▶ Run Code</div>
```js{6}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('layer', (state) => {
console.log(state);
});
```
## `loading`
Triggered when the loader is shown or hidden.
<div className="run-code">▶ Run Code</div>
```js{6}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('loading', (state) => {
console.log(state);
});
```
## `mask`
Triggered when the mask layer is shown or hidden.
<div className="run-code">▶ Run Code</div>
```js{6}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('mask', (state) => {
console.log(state);
});
```
## `subtitle`
Triggered when the subtitle layer is shown or hidden.
<div className="run-code">▶ Run Code</div>
```js{6}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('subtitle', (state) => {
console.log(state);
});
```
## `contextmenu`
Triggered when the context menu is shown or hidden.
<div className="run-code">▶ Run Code</div>
```js{6}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('contextmenu', (state) => {
console.log(state);
});
```
## `control`
Triggered when the control bar is shown or hidden.
<div className="run-code">▶ Run Code</div>
```js{6}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('control', (state) => {
console.log(state);
});
```
## `setting`
Triggered when the settings panel is shown or hidden.
<div className="run-code">▶ Run Code</div>
```js{6}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
setting: true,
});
art.on('setting', (state) => {
console.log(state);
});
```
## `muted`
Triggered when the muted state changes.
<div className="run-code">▶ Run Code</div>
```js{6}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('muted', (state) => {
console.log(state);
});
```
## `keydown`
Listens for the `keydown` event from the `document`.
<div className="run-code">▶ Run Code</div>
```js{6}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('keydown', (event) => {
console.log(event.code);
});
```
## `video:canplay`
The browser can start playing the media, but estimates there is not enough data to play through to the end without stopping for further buffering.
## `video:canplaythrough`
The browser estimates it can play the media through to the end without stopping for buffering.
## `video:complete`
Historical declared name; not forwarded by the default video event inventory. Use `video:ended` for media reaching its end.
## `video:durationchange`
Triggered when the value of the `duration` property changes.
## `video:emptied`
The media has become empty; for example, this event is sent when the media has already been loaded (or partially loaded), and the `load()` method is called to reload it.
## `video:ended`
Playback has stopped because the media has reached its end.
## `video:error`
An error occurred while fetching the media data, or the resource type is not a supported media format.
## `video:loadeddata`
The first frame of the media has finished loading.
## `video:loadedmetadata`
Metadata has been loaded.
## `video:pause`
Playback has been paused.
## `video:play`
Playback has begun.
## `video:playing`
Playback is ready to start after having been paused or delayed due to lack of data.
## `video:progress`
Fired periodically as the browser loads the resource.
## `video:ratechange`
The playback rate has changed.
## `video:seeked`
A seek operation has completed.
## `video:seeking`
A seek operation has begun.
## `video:stalled`
The user agent is trying to fetch media data, but data is unexpectedly not forthcoming.
## `video:suspend`
Media data loading has been suspended.
## `video:timeupdate`
The time indicated by the `currentTime` property has changed.
## `video:volumechange`
The volume has changed.
## `video:waiting`
Playback has stopped because of a temporary lack of data.
===== packages/artplayer-vitepress/docs/en/advanced/global.md =====
# Global Properties
The `global properties` here refer to the `top-level properties` mounted on the `constructor`. All property names are in uppercase. These are subject to change in the future and are rarely used.
## Scope and effective timing {#global-contract}
These fields belong to the constructor and are shared by instances from that Artplayer module. They are not per-instance options and assignments are not automatically validated. Configure them before constructing players when possible. Some consumers read them at initialization, others at event time; changing a field does not rebuild existing menus, listeners, or pending timers. Separate module copies have separate constructors and fields.
### Initialization and UI {#global-initialization}
| Field | Default and reading point |
| --- | --- |
| `STYLE` | Embedded CSS text. Import has already injected artplayer-style; assigning STYLE later does not update that node |
| `DEBUG` | false; constructor decides whether to install logging listeners. Setting false later does not remove them |
| `CONTEXTMENU` | true; checked when opening the desktop menu. Does not remove entries or prevent setting contextmenu.show manually |
| `PLAYBACK_RATE` / `ASPECT_RATIO` / `FLIP` | Default arrays are listed below. Read when constructing setting/context-menu entries; changing them does not refresh existing entries |
| `SETTING_ITEM_WIDTH` | 200px; read when constructing built-in submenu options. Explicit item widths can override it |
| `SETTING_ITEM_HEIGHT` | 35px; read when creating items/back rows and laying out panels. Existing node heights are not rewritten merely by assigning this field |
| `SETTING_WIDTH` | 250px; root width read during panel layout, still constrained by the container |
| `USE_RAF` | false; initialization selects the raf loop and progress listener branch. Not a live mode toggle; raf emits during playback and does not replace native media events |
| `LOG_VERSION` | true; read by the callback about 100ms after module import, not once per player construction |
| `REMOVE_SRC_WHEN_DESTROY` | true; read by each destroy call. false only skips removeAttribute('src')/load(); listeners, requests, UI, and plugin lifecycle still clean up. Preserving DOM uses the separate destroy(false) argument |
### Scheduling and interaction {#global-timing}
Time values below use milliseconds unless stated otherwise. Already queued work retains the delay used when scheduled.
| Field | Default and reading point |
| --- | --- |
| `NOTICE_TIME` | 2000; read when showing a notice, without rescheduling an existing one |
| `RESIZE_TIME` | 200; read when resize/orientation queues a new task, canceling the previous task. This is trailing debounce |
| `SCROLL_TIME` / `SCROLL_GAP` | 200 / 50px. The delay is captured when the event system initializes; the gap is read on accepted scroll events. Leading throttle emits a boolean view event; the raw event is window:scroll |
| `CONTROL_HIDE_TIME` | 3000; checked against last-show time in video:timeupdate, while playing and without active setting/input/control interaction. Not a standalone timer |
| `DBCLICK_TIME` | 300; click-count window read on each video click. The first single click is not delayed while awaiting another |
| `DBCLICK_FULLSCREEN` / `MOBILE_DBCLICK_PLAY` / `MOBILE_CLICK_PLAY` | true / true / false; read on each video click for desktop double-click fullscreen and mobile double/single-click playback. Mobile playback remains subject to the lock state |
| `FAST_FORWARD_TIME` / `FAST_FORWARD_VALUE` | 1000 / 3x; delay read when a long press is queued, rate when it activates. Requires the plugin to be enabled, playback active, and the player unlocked |
| `TOUCH_MOVE_RATIO` | 0.5; read during video gestures. Progress-bar dragging does not apply this video multiplier |
| `VOLUME_STEP` / `SEEK_STEP` | 0.1 / 5 seconds; read by shortcuts and accessible volume/progress slider actions |
| `FULLSCREEN_WEB_IN_BODY` | true; read when entering web fullscreen. Exit restores that session's saved placement |
| `AUTO_ORIENTATION_TIME` | 200; read when a required web-fullscreen rotation is queued. Does not force device rotation capability |
| `INFO_LOOP_TIME` | 1000; read each time the visible info panel queues its next update |
### Resume records and reconnection {#global-recovery}
| Field | Default and reading point |
| --- | --- |
| `AUTO_PLAYBACK_MAX` | 10; read when playing timeupdate writes a record. Historical logic removes one first-enumerated key only if the count already exceeds the threshold before writing; not a strict 10-entry cap or LRU |
| `AUTO_PLAYBACK_MIN` | 5 seconds; decides whether saved progress merits a resume prompt, rather than preventing storage below 5 seconds |
| `AUTO_PLAYBACK_TIMEOUT` | 3000; read on the first timeupdate after a resume prompt is created, when hiding is scheduled |
| `RECONNECT_TIME_MAX` / `RECONNECT_SLEEP_TIME` | 5 attempts / 1000; read when handling a media error to decide retry and queue its delay. Retries belong to the current source/lifecycle; these do not configure HLS/DASH SDK retry policies |
## DEBUG
Whether to enable `debug` mode, which can print all built-in video events. Default is off.
<div className="run-code">▶ Run Code</div>
```js
Artplayer.DEBUG = true;
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
```
## STYLE
Returns the player style text.
<div className="run-code">▶ Run Code</div>
```js
console.log(Artplayer.STYLE);
```
## CONTEXTMENU
Whether to enable the context menu. Default is on.
<div className="run-code">▶ Run Code</div>
```js
Artplayer.CONTEXTMENU = false;
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
```
## NOTICE_TIME
The display duration of notification messages, in milliseconds. Default is `2000`.
<div className="run-code">▶ Run Code</div>
```js
Artplayer.NOTICE_TIME = 5000;
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
```
## SETTING_WIDTH
The default width of the settings panel, in pixels. Default is `250`.
<div className="run-code">▶ Run Code</div>
```js
Artplayer.SETTING_WIDTH = 300;
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
setting: true,
loop: true,
flip: true,
playbackRate: true,
aspectRatio: true,
});
```
## SETTING_ITEM_WIDTH
The default width of a setting item in the settings panel, in pixels. Default is `200`.
<div className="run-code">▶ Run Code</div>
```js
Artplayer.SETTING_ITEM_WIDTH = 300;
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
setting: true,
loop: true,
flip: true,
playbackRate: true,
aspectRatio: true,
});
```
## SETTING_ITEM_HEIGHT
The default height of a setting item in the settings panel, in pixels. Default is `35`.
<div className="run-code">▶ Run Code</div>
```js
Artplayer.SETTING_ITEM_HEIGHT = 40;
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
setting: true,
loop: true,
flip: true,
playbackRate: true,
aspectRatio: true,
});
```
## RESIZE_TIME
The debounce delay for the `resize` event, in milliseconds. Default is `200`.
<div className="run-code">▶ Run Code</div>
```js
Artplayer.RESIZE_TIME = 500;
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('resize', () => {
console.log('resize');
});
```
## SCROLL_TIME
The throttle time for the `scroll` event, in milliseconds. Default is `200`.
<div className="run-code">▶ Run Code</div>
```js
Artplayer.SCROLL_TIME = 500;
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('view', (visible) => {
console.log('view', visible);
});
```
## SCROLL_GAP
The boundary tolerance distance for the `view` event, in pixels. Default is `50`.
<div className="run-code">▶ Run Code</div>
```js
Artplayer.SCROLL_GAP = 100;
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('view', (visible) => {
console.log('view', visible);
});
```
## AUTO_PLAYBACK_MAX
The maximum record count for the auto-playback feature. Default is `10`.
<div className="run-code">▶ Run Code</div>
```js
Artplayer.AUTO_PLAYBACK_MAX = 20;
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
autoPlayback: true,
});
```
## AUTO_PLAYBACK_MIN
The minimum saved progress for showing the auto-playback resume prompt, in seconds. Default is `5`.
<div className="run-code">▶ Run Code</div>
```js
Artplayer.AUTO_PLAYBACK_MIN = 10;
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
autoPlayback: true,
});
```
## AUTO_PLAYBACK_TIMEOUT
The hide delay duration for the auto-playback feature, in milliseconds. Default is `3000`.
<div className="run-code">▶ Run Code</div>
```js
Artplayer.AUTO_PLAYBACK_TIMEOUT = 5000;
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
autoPlayback: true,
});
```
## RECONNECT_TIME_MAX
The maximum number of automatic reconnection attempts when a connection error occurs. Default is `5`.
<div className="run-code">▶ Run Code</div>
```js
Artplayer.RECONNECT_TIME_MAX = 10;
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/404.mp4',
});
```
## RECONNECT_SLEEP_TIME
The delay time for automatic reconnection when a connection error occurs, in milliseconds. Default is `1000`.
<div className="run-code">▶ Run Code</div>
```js
Artplayer.RECONNECT_SLEEP_TIME = 3000;
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/404.mp4',
});
```
## CONTROL_HIDE_TIME
The auto-hide delay time for the bottom control bar, in milliseconds. Default is `3000`.
<div className="run-code">▶ Run Code</div>
```js
Artplayer.CONTROL_HIDE_TIME = 5000;
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
```
## DBCLICK_TIME
The delay time for the double-click event, in milliseconds. Default is `300`.
<div className="run-code">▶ Run Code</div>
```js
Artplayer.DBCLICK_TIME = 500;
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('dblclick', () => {
console.log('dblclick');
});
```
## DBCLICK_FULLSCREEN
On desktop, whether double-click toggles fullscreen. Default is `true`.
<div className="run-code">▶ Run Code</div>
```js
Artplayer.DBCLICK_FULLSCREEN = false;
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
```
## MOBILE_DBCLICK_PLAY
On mobile, whether double-click toggles play/pause. Default is `true`.
<div className="run-code">▶ Run Code</div>
```js
Artplayer.MOBILE_DBCLICK_PLAY = false;
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
```
## MOBILE_CLICK_PLAY
On mobile, whether single-click toggles play/pause. Default is `false`.
<div className="run-code">▶ Run Code</div>
```js
Artplayer.MOBILE_CLICK_PLAY = true;
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
```
## AUTO_ORIENTATION_TIME
On mobile, the delay time for auto-rotation, in milliseconds. Default is `200`.
<div className="run-code">▶ Run Code</div>
```js
Artplayer.AUTO_ORIENTATION_TIME = 500;
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
autoOrientation: true,
});
```
## INFO_LOOP_TIME
The refresh interval for the info panel, in milliseconds. Default is `1000`.
<div className="run-code">▶ Run Code</div>
```js
Artplayer.INFO_LOOP_TIME = 2000;
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.info.show = true;
```
## FAST_FORWARD_VALUE
On mobile, the speed multiplier for long-press fast-forward. Default is `3`.
<div className="run-code">▶ Run Code</div>
```js
Artplayer.FAST_FORWARD_VALUE = 5;
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
fastForward: true,
});
```
## FAST_FORWARD_TIME
On mobile, the delay time for long-press fast-forward, in milliseconds. Default is `1000`.
<div className="run-code">▶ Run Code</div>
```js
Artplayer.FAST_FORWARD_TIME = 2000;
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
fastForward: true,
});
```
## TOUCH_MOVE_RATIO
On mobile, the speed multiplier for left/right swipe to seek. Default is `0.5`.
<div className="run-code">▶ Run Code</div>
```js
Artplayer.TOUCH_MOVE_RATIO = 1;
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
```
## VOLUME_STEP
The step size for volume adjustment via keyboard shortcuts. Default is `0.1`.
<div className="run-code">▶ Run Code</div>
```js
Artplayer.VOLUME_STEP = 0.2;
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
```
## SEEK_STEP
The step size for seeking via keyboard shortcuts, in seconds. Default is `5`.
<div className="run-code">▶ Run Code</div>
```js
Artplayer.SEEK_STEP = 10;
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
```
## PLAYBACK_RATE
The built-in list of playback rates. Default is `[0.5, 0.75, 1, 1.25, 1.5, 2]`.
<div className="run-code">▶ Run Code</div>
```js
Artplayer.PLAYBACK_RATE = [0.5, 1, 2, 3, 4, 5];
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
setting: true,
playbackRate: true,
});
art.contextmenu.show = true;
art.setting.show = true;
```
## ASPECT_RATIO
The built-in list of video aspect ratios. Default is `['default', '4:3', '16:9']`.
<div className="run-code">▶ Run Code</div>
```js
Artplayer.ASPECT_RATIO = ['default', '1:1', '2:1', '4:3', '6:5'];
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
setting: true,
aspectRatio: true,
});
art.contextmenu.show = true;
art.setting.show = true;
```
## FLIP
The built-in list of video flip options. Default is `['normal', 'horizontal', 'vertical']`.
<div className="run-code">▶ Run Code</div>
```js
Artplayer.FLIP = ['normal', 'horizontal'];
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
setting: true,
flip: true,
});
art.contextmenu.show = true;
art.setting.show = true;
```
## FULLSCREEN_WEB_IN_BODY
Whether to mount the player under the `body` element during web fullscreen mode. Default is `true`.
<div className="run-code">▶ Run Code</div>
```js
Artplayer.FULLSCREEN_WEB_IN_BODY = false;
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
fullscreenWeb: true,
});
```
## LOG_VERSION
Sets whether to print the player version. Default is `true`.
<div className="run-code">▶ Run Code</div>
```js
Artplayer.LOG_VERSION = false;
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
```
## USE_RAF
Sets whether to use `requestAnimationFrame`. Default is `false`. Currently, it is primarily used for smooth progress bar effects.
<div className="run-code">▶ Run Code</div>
```js
Artplayer.USE_RAF = true;
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
miniProgressBar: true,
});
```
## REMOVE_SRC_WHEN_DESTROY
Whether to remove the video's `src` attribute and call `load()` to actively release media resources when destroying the player. Default is `true`.
Enabling this can reduce video resource usage in single-page applications or scenarios where players are frequently created/destroyed. Setting this to `false` skips this explicit media reset; the other destroy cleanup still runs.
<div className="run-code">▶ Run Code</div>
```js
Artplayer.REMOVE_SRC_WHEN_DESTROY = false;
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
// Run destroy cleanup without explicitly resetting src
art.destroy();
```
===== packages/artplayer-vitepress/docs/en/advanced/plugin.md =====
# Writing Plugins
Once you are familiar with the player's properties, methods, and events, you can write a plugin.
## Registration and lifecycle {#plugin-contract}
The `plugins` option accepts factory functions. A factory receives the player as its only argument and, for ordinary functions, as `this`; arrow functions retain their lexical `this`. A configurable plugin usually returns this factory from an outer call, such as `plugins: [adsPlugin(options)]`. The returned object is stored directly, without cloning.
A synchronous factory is registered before `add` returns, and `art.plugins.add(factory)` returns the registry itself. Only a `Promise` from the player's JavaScript realm is awaited; the returned Promise then fulfills with the registry, not the plugin result. Plain thenables and promises from another window remain synchronous results. To wait for an asynchronous plugin, call `await art.plugins.add(factory)` after construction, then read its named result.
Factories in the constructor option start in array order without waiting for earlier asynchronous factories. Player `ready` does not wait for them either. During these factory calls, `art.plugins` has not yet been assigned, so a factory must not use it to access an earlier plugin. Synchronous errors fail construction or the direct `add` call. Callers handle rejections from direct `add`; rejections from constructor registrations are logged as warnings.
The name is the first truthy value among the result's `name`, the factory's function name, and `plugin` followed by the current registration counter. An anonymous asynchronous factory uses the counter at completion, so return a stable string name explicitly. `id` increments before invoking a factory, including failed attempts and built-in registrations. `art` references the host. The low-level `next(factory, result)` submits a result directly: it does not invoke the factory, increment the counter, or await promises. Normal plugins do not need it.
Results are stored as non-writable, non-configurable, non-enumerable own properties. `Object.keys(art.plugins)` therefore does not list plugins; duplicate names throw. Avoid registry member names such as `art`, `id`, `add`, and `next`; a name matching a prototype method can shadow it. There is no generic remove/replace operation or automatic call to a result's `destroy` method.
Listen for the player's `destroy` event to release requests, timers, workers, and external resources. An asynchronous factory should register cleanup before its first wait and check for destruction after waiting. Late results are neither registered nor automatically cleaned up. An already pending `add` retains its original Promise and fulfills with the registry if the factory succeeds. New calls during or after destruction throw before invoking the factory.
## TypeScript {#plugin-types}
The root entry retains the historical Promise return declaration for `Plugins.add`; this does not make synchronous execution asynchronous. `PluginRegistration` from `artplayer/runtime` distinguishes the registry from a Promise according to the factory return type, preserving their union for `unknown`. TypeScript cannot determine a Promise's realm; the runtime rules above still apply.
Module augmentation describes a result without installing a plugin. The accurate entry's `Plugins` also carries named augmentations from the root entry. The constructor option's `PluginFactory` uses the construction-phase `PluginHost`, rather than assuming the player is already fully initialized. A post-construction `add` can use the complete player.
```ts
import Artplayer from 'artplayer/runtime';
import type { Plugins } from 'artplayer/runtime';
declare module 'artplayer/runtime' {
interface Plugins {
exampleCounter: { name: string; value: number };
}
}
const art = new Artplayer({ container: '.artplayer-app', url: '/assets/sample/video.mp4' });
const registry: Plugins = art.plugins.add(function (host) {
const samePlayer: boolean = this === host;
return { name: 'exampleCounter', value: samePlayer ? 1 : 0 };
});
const value: number = registry.exampleCounter.value;
const pending: Promise<Plugins> = art.plugins.add(async () => ({ name: 'exampleAsync' }));
void pending.catch(console.error);
void value;
```
## Examples
You can load a plugin function during instantiation.
<div className="run-code">▶ Run Code</div>
```js{15}
function myPlugin(art) {
console.info(art);
return {
name: 'myPlugin',
something: 'something',
doSomething: function () {
console.info('doSomething');
},
};
}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
plugins: [myPlugin],
});
art.on('ready', () => {
console.info(art.plugins.myPlugin);
});
```
You can also load a plugin function after instantiation.
<div className="run-code">▶ Run Code</div>
```js{17}
function myPlugin(art) {
console.info(art);
return {
name: 'myPlugin',
something: 'something',
doSomething: function () {
console.info('doSomething');
},
};
}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.plugins.add(myPlugin);
art.on('ready', () => {
console.info(art.plugins.myPlugin);
});
```
For example, let's say I want to write a plugin that displays an image ad when the video is paused.
<div className="run-code">▶ Run Code</div>
```js
function adsPlugin(option) {
return (art) => {
art.layers.add({
name: 'ads',
html: `<img style="width: 100px" src="${option.url}">`,
style: {
display: 'none',
position: 'absolute',
top: '20px',
right: '20px',
},
});
function show() {
art.layers.ads.style.display = 'block';
}
function hide() {
art.layers.ads.style.display = 'none';
}
art.controls.add({
name: 'hide-ads',
position: 'right',
html: 'Hide Ads',
tooltip: 'Hide Ads',
click: hide,
style: {
marginRight: '20px'
}
});
art.controls.add({
name: 'show-ads',
position: 'right',
html: 'Show Ads',
tooltip: 'Show Ads',
click: show,
});
art.on('play', hide);
art.on('pause', show);
return {
name: 'adsPlugin',
show,
hide
};
}
}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
plugins: [
adsPlugin({
url: '/assets/sample/layer.png'
})
],
});
```
===== packages/artplayer-vitepress/docs/en/advanced/property.md =====
# Instance Properties
Here, `Instance Properties` refer to the `first-level properties` mounted on the `instance`, which are commonly used.
## Instance identity and lifecycle {#instance-lifecycle}
Artplayer.instances returns the shared array of successfully constructed, still-registered instances. The constructor adds an instance only after synchronous initialization finishes; a constructor plugin does not yet find its own instance there. It is not a copy or a list of ready players. Read or copy it rather than editing it: registration, duplicate-container checks and mutex playback use this array. Destruction removes only that instance.
art.constructor is the same Artplayer constructor and its prototype is the instance prototype. Artplayer.version is the package version string, not a capability test. The historical root declarations still name Artplayer.env and Artplayer.build, but neither is supplied by the frozen npm 5.4.0 runtime or the current 6.0.0 runtime; their reads return undefined. The runtime declaration view omits them. The build process's NODE_ENV replacement does not create these public properties.
art.id is an incrementing numeric identifier within one loaded constructor, allocated before option validation. Failed construction can leave gaps; independent bundle copies have independent counters/registries. It is unrelated to option.id, which is a playback-memory key. Do not use it as a globally unique persistent identifier.
### State fields and owned services {#instance-state}
These six fields start as false and are ordinary writable fields, not commands or capability promises. Assigning them does not run the corresponding feature or perform cleanup.
| Field | Core meaning |
| --- | --- |
| isReady | Set before the first ready event after canplay. Not reset to false by reset, source changes or normal destruction, so it does not prove the current URL is ready |
| isDestroy | Set after core teardown and instance removal, before the destroy event. Internal closing guards start earlier; it can still be false while cleanup is executing |
| isFocus | Updated by player focusin/focusout and inside/outside document click/contextmenu paths. Not a direct alias of document.activeElement |
| isInput | Tracks INPUT targets in those paths, not every editable target. Keyboard filtering separately checks editable content |
| isLock | Updated by the mobile lock helper alongside its CSS state. Assigning this field alone does not create the lock UI or emit lock |
| isRotate | Tracks the CSS rotation used by web auto-orientation. Native screen-orientation locking does not make this a general device-orientation flag |
Optional flv/m3u8/hls/ts/mpd/torrent fields are integration slots for caller-installed adapters. The core does not initialize those SDKs or automatically call arbitrary objects' destroy methods. Follow each adapter's ownership contract and register its cleanup. runtime describes unknown integration values so that consumers check them before use.
art.player is the descriptor installer object, with no public operational methods; playback APIs are installed on art itself. info/loading/mask are existing component services with show and toggle(). Their show setters update CSS state and synchronously emit their named event, including identical assignments. They are not promises and do not fetch media or control buffering. Native media handlers may subsequently change their visibility.
The info service initializes on desktop, polls data-video fields at INFO_LOOP_TIME even while hidden, formats numbers to two decimals, and updates textContent. Its runtime init() restarts the owned polling/listener scope rather than creating an independent extra loop. Loading mounts the configured loading icon. Mask mounts state/error icons, requests play on state-button click, and switches to its terminal error presentation on destruction; a thrown user destroy listener does not skip its final cleanup. These services are not custom-entry containers like layers or controls.
plugins.add remains the registry method documented in the plugin guide: a synchronous result is immediately installed, a same-realm Promise returns a Promise of the registry, and neither shape is converted into a universal async API. The runtime Plugins.add type accepts accurate and legacy factories without wrapping the actual function. Plugin result objects are not automatically disposed merely because they contain a destroy method.
### Reset, destruction, and failure {#instance-cleanup}
reset() only calls video.removeAttribute('src') followed by video.load(). It returns undefined, keeps the UI, registration, option.url, readiness flag and user subscriptions, and does not revoke caller URLs, destroy SDKs or rebuild plugins. Native media events may still follow. Use the source-switch APIs for coordinated source changes; reset is not a full player restart or an asynchronous cancellation receipt.
destroy(removeHtml = true) synchronously starts teardown. When REMOVE_SRC_WHEN_DESTROY is enabled and a media node is available it first calls reset, then releases owned scopes, handles the template, removes the instance, marks isDestroy, emits destroy, and finalizes remaining resources. Browser promises already in flight are not synchronously made complete by its return. Both reset and destroy use the method receiver; keep them bound to the instance when passing them elsewhere.
With true, destruction empties the container; it does not remove the caller's container node or restore its pre-mount content. With false, it keeps the generated DOM and marks the player art-destroy while still stopping core resources. A repeated/reentrant destroy is a no-op, so destroy(false) followed by destroy(true) does not later remove the retained DOM. Reusing the released container requires a new player. The old instance must not be treated as revived, and later property access is not uniformly guaranteed to be a no-op.
Cleanup continues after a failure, then throws the first caught value and logs additional failures. A failing synchronous constructor instead restores its captured DOM/attributes and rethrows the original initialization error. User event registrations are not all erased by destroy: retaining an instance also retains those callbacks until removed. Caller-created timers, native listeners and external SDK resources need their own cleanup; the core-owned listener manager and scopes only release resources registered with them.
```ts
import Artplayer from 'artplayer/runtime';
const art = new Artplayer({ container: '#player' });
const instances: Artplayer[] = [...Artplayer.instances];
const identifier: number = art.id;
art.info.show = true;
art.loading.toggle();
const dispose = art.destroy.bind(art);
const retained: void = dispose(false);
void [instances, identifier, retained];
```
## Playback, progress and source contracts {#playback-contract}
### Method results and media state {#playback-results}
play() returns a Promise that waits for the current media play() result. Native rejection still reaches the caller. Only after success does it show the play notice, emit the custom play event and pause other instances when mutex is enabled. If source replacement or destruction makes the request obsolete, the caller still receives the media result, but obsolete notice, play event and mutex effects stop. Errors from later logic, including custom listeners, can also reject this Promise; rejection does not always mean decoding failed.
pause() calls media pause() synchronously, then shows the notice, emits the custom pause event and returns the media method result. Native media normally returns undefined; a proxy may differ. toggle() selects play() or pause() from playing at call time and returns that branch's result directly, so it does not always return a Promise. Root Player.toggle keeps its historical void signature. PlaybackControls exported from the root is a type-only view for accurate native playback results, not a runtime object. The runtime entry also preserves proxy results through its media generic.
These three methods capture their owning instance and can be called after extraction. Custom play/pause events differ from video:play/video:pause: the browser dispatches native events, whose order cannot be inferred from method calls alone. playing uses a proxy's boolean playing value when available; otherwise it requires currentTime > 0, paused false, ended false and readyState > 2. It is not simply !paused and can be false at time zero or while buffering.
### Time, volume and buffer values {#playback-values}
| Interface | Actual behavior |
| --- | --- |
| currentTime | Reads media time with a falsy fallback of 0; writes use parseFloat, clamp to 0 through art.duration and ignore NaN input. The runtime entry accepts number/string; the root keeps number |
| duration | Reads media duration with Infinity and falsy values reported as 0; infinite live duration is not exposed as a finite seek endpoint |
| seek | Write-only; writes currentTime, shows a progress notice and synchronously emits seek(actual time, original input). It does not return a Promise waiting for native seeked |
| forward / backward | Write-only; forward currentTime + seconds or currentTime - seconds to seek. Pass numbers: historical JavaScript addition and subtraction coercion differ |
| volume | Clamps writes to [0, 1] and uses the accepted media value in its notice; only nonzero values update the storage volume key. Zero retains the previous nonzero stored value and does not change muted |
| muted | Writes the media field and synchronously emits muted, including repeated equal writes. Pass a boolean; native coercion does not change the original input carried by the event |
| playbackRate | Writes a truthy value only when it differs from the current rate, then shows a notice; falsy input restores 1. Unsupported native rates can still throw; menu configuration is not a browser capability guarantee |
| loadedTime | The end of the last buffered range, or 0 with no ranges; neither the sum of ranges nor a guarantee that the buffer is continuous |
| loaded | loadedTime divided by raw media duration, without clamping or invalid-value normalization |
| played | currentTime divided by art.duration; a position ratio, not accumulated viewing time or native played ranges |
loaded/played can be NaN/Infinity before readiness, for zero duration or with unusual proxy state. Check Number.isFinite before displaying a percentage; these ratios do not prove playability. seek, forward, backward, switch and quality have no getters and read as undefined. Root declarations retain historical read types; runtime describes the write-only behavior accurately. Direct currentTime assignment does not synchronously emit custom seek, though native media events still occur.
attr(key, value) directly accesses properties on the media object captured during construction; it is not an HTML attribute API. Omitted or explicit undefined means read; writes return undefined. Symbol keys work, and underlying property errors propagate. Direct media writes bypass the player facade's notices, storage and custom events. Do not replace template.$video to switch sources: most descriptors capture the original node while duration reads the current template, which would split their state.
### URL assignment, completion and cancellation {#source-transitions}
url reads media src, normally a browser-resolved absolute URL for native media; proxies can return null. option.url stores caller input, so these values need not match. type reads/writes option.type; a nonempty value overrides extension detection when selecting customType. Changing type alone does not reload media.
Direct url assignment supersedes the current source operation. Native sources write src; customType runs on a later turn with the instance as this and video, input URL and art as arguments. A returned Promise is not a media readiness signal, but its rejection is handled by the owning operation. option.url changes only when actual src differs from its previous value. With an already-ready player and a previous URL, canplay for the current operation triggers restart. Adapters must still update media and dispatch events correctly; the core cannot infer external SDK readiness.
switchUrl(url) starts at 0; switchQuality(url) captures the current position. Both pause, assign the URL, wait for readiness and any required seek, restore rate and aspect ratio, and attempt playback according to current intent. An explicit pause during loading cancels automatic resume; newer public seek/currentTime writes take precedence over automatic position restoration. Both return `Promise<void>`. Normal completion means the source operation has finished, but internal play recovery is best effort: its rejection does not reject the source-switch Promise.
A later switch, direct url assignment or destroy ends the old operation and resolves its Promise normally; late callbacks cannot restore obsolete state. A request strictly equal to current art.url also completes as a no-op; relative input need not equal a resolved absolute URL. An empty URL switch completes and shows loading without clearing the old src, so it is not a substitute for reset. Media errors, adapter failures and state-restoration errors can still reject; handle them. Resolution alone does not prove the requested source is playing: inspect current source and media state as required by the application.
switch is a write-only compatibility entry for switchUrl. Its assignment expression does not return the switch Promise; use the method to observe completion or errors. Direct url setter failures are logged, and assignment does not provide an awaitable Promise either. URL/Blob URL ownership remains with the provider; switching or destroying the player does not revoke caller-owned URLs.
### Quality lists and selection {#quality-contract}
quality accepts an array of html, url and optional default fields; html can be a string or HTMLElement. Assignment updates the right-side control named quality, using the first default item's label or the first item. This updates UI only: it neither switches to that URL nor infers highlighting from the current source. Arrays and entries follow the control's reference and default-mutation semantics rather than becoming immutable copies.
A selector click updates default flags and the label before calling switchQuality. Completion updates the notice and returned label only for a still-active selection. Replacing the control, selecting a newer item or destroying the player makes older asynchronous updates obsolete. An empty array leaves an empty quality control; it is not controls.remove('quality'). Reading art.quality does not return the list, so retain application configuration separately. A static URL list is not HLS/DASH adaptive-track discovery; use the corresponding SDK plugins.
```ts
import Artplayer from 'artplayer/runtime';
import type LegacyArtplayer from 'artplayer';
import type { PlaybackControls } from 'artplayer';
const art = new Artplayer({ container: '#player', url: '/assets/sample/video.mp4' });
function nativeCommands(player: LegacyArtplayer): PlaybackControls {
return player;
}
async function togglePlayback(): Promise<void> {
await art.toggle(); // May reject when the branch requests play.
}
async function changeSource(url: string): Promise<void> {
await art.switchUrl(url);
// Completion includes cancellation; inspect art.url and media state as needed.
}
const progress: number | null = Number.isFinite(art.played) ? art.played : null;
art.currentTime = '12.5';
const unreadable: undefined = art.seek;
void [nativeCommands, togglePlayback, changeSource, progress, unreadable];
```
## Display, sizing and image contracts {#display-contract}
### Display modes and browser capabilities {#display-modes}
state returns the first truthy mode in mini, pip, fullscreen, fullscreenWeb order, or standard. Assignment closes active modes with a different name; **it does not enable the named mode**. Enter mini with mini = true. standard or an unknown name requests all active modes to close, and native exit may complete asynchronously. This is not an atomic mode transition or a guarantee of immediate completion.
fullscreen is installed on the first video:loadedmetadata event using capabilities available then; it may be undefined beforehand. It prefers document fullscreen, then WebKit video fullscreen, otherwise reads false and shows an unsupported notice. Root types retain boolean; runtime marks it optional. Native requests remain subject to user activation and permissions, and a boolean assignment expression is not an awaitable request result. Native state events drive fullscreen emission; the document path then updates exclusive state, CSS and resize. Errors may show a notice and produce fullscreenError. Property presence or desktop WebKit execution does not prove mobile support.
fullscreenWeb is a CSS page mode, read from the art-fullscreen-web class. Entry saves the player node's placement and inline style, optionally moves it to its owner document's body according to FULLSCREEN_WEB_IN_BODY, then changes dimensions and class. Exit restores the original snapshot, so temporary inline style changes made during the mode can be overwritten on exit. It synchronously emits fullscreenWeb then resize; repeated assignments can emit again. Destruction restores ownership and cleans up. This is not native browser fullscreen.
mini is a draggable page overlay, not operating-system PiP. The core moves the same media node into it and restores its original parent and sibling position on exit; subsequent entries reuse the overlay. Position uses storage left/top and is constrained to the viewport. Hiding cancels dragging; destruction removes only core-created overlays, while caller-provided nodes remain caller-owned. mini events describe overlay state, not successful playback.
pip prefers standard native PiP and reads as the media element when owned by this instance, otherwise null. WebKit presentation mode reads as a boolean; unsupported implementations read false. Root types retain boolean; runtime exposes Element/null/boolean. Assignment still takes a boolean and provides no observable Promise: preserve the user-click call stack and observe notices/pip events instead of treating assignment as a returned window. Cancellation/destruction exits only owned or late requests, not another player's window. This is video PiP; use the Document PiP plugin for a document window. Synchronous native errors may throw; asynchronous rejection is shown through the notice.
airplay() checks WebKit availability events and the picker method to request a target picker. It normally returns undefined; the subsequent airplay event does not prove a receiver connection. Unavailability only shows a notice. Call from appropriate user interaction and allow for method exceptions. Safari/iOS, receiver devices, iframe permissions and proxy support require separate verification.
### Geometry, aspect ratio and poster {#display-sizing}
rect reads the player node's getBoundingClientRect each time; bottom/top/left/right/width/height use this viewport rectangle. x/y add page scroll offsets to left/top, giving page coordinates rather than simple aliases of rect.x/rect.y. These are not decoded media dimensions or a fixed snapshot; separate reads may span layout changes.
autoSize() contains the player within its caller container using valid video dimensions, writes player percentage width/height and emits autoSize({width, height}). autoHeight() keeps container clientWidth, writes a proportional pixel height on the container and emits autoHeight(height). Both return undefined and support extracted calls. Missing finite positive dimensions or hidden containers produce no invalid sizing writes/events; call again when visible or ready. These methods do not install a persistent automatic observer.
aspectRatio computes media width, height and margin from a ratio string and stores data-aspect-ratio. default or falsy input clears those three inline properties and the dataset. Valid ratios use current player dimensions; malformed/nonpositive ratios avoid invalid geometry but still retain the input dataset, notice and event, so the getter is not validation. flip uses data-flip; normal or falsy input clears it, and built-in CSS handles horizontal/vertical. Other strings can be retained and emitted without having a corresponding transform. Neither property changes source pixels. Both show the notice before synchronously emitting their named event, including repeated values.
poster reads/writes the poster layer's inline background-image, not video.poster. It neither loads a new media source nor automatically shows the poster again. Reading depends on the browser's quoted URL serialization and returns an empty string if parsing fails; it does not read backgrounds supplied by external stylesheets.
### Capture results and ownership {#capture-contract}
getDataURL() and getBlobUrl() synchronously draw the current media frame into an internal canvas at call time, then return Promises. The first resolves to a PNG data URL; the second encodes asynchronously and creates a Blob URL. Dimensions come from media videoWidth/videoHeight. Capture contains the media frame, not player controls, CSS flip/aspect effects or DOM subtitles. Missing decoded frames, unavailable canvas or cross-origin media tainting can fail; successful playback does not imply readable pixels, and the core does not bypass CORS.
The getBlobUrl caller must use URL.revokeObjectURL when finished. Destroying the player does not revoke that URL or invalidate an encoding result already in progress. Data URLs need no revocation. Encoding failures reject, and a still-current instance/source shows an error notice. Do not treat an empty image from unavailable dimensions as a successful capture.
screenshot(name?) waits for getDataURL, requests a download, emits screenshot(dataURL) and returns the same string. The filename uses the supplied name or artplayer_ plus formatted time, and **always appends .png**: chosen.png becomes chosen.png.png. Actual download storage remains browser-controlled. Source replacement or destruction during the wait suppresses obsolete download/event effects, but the Promise can still return the captured frame. All three methods support extracted calls; handle rejection.
### Sprite previews and subtitle offset {#preview-offset-contract}
The thumbnails getter returns the actual option.thumbnails object. Its setter replaces the whole object and resets image loading only while the instance is open, url is truthy and the player is not live. It does not merge settings; an empty URL is not a clearing command. Loading/rendering starts only with an existing control and hover, or an event-bearing played update on mobile. Replacing configuration/control or destroying releases internal scaled Blob URLs and prevents stale writes. Failures log warnings and a later hover can retry.
number is the total cell count and column is the column count, with zero-based rows/cells. Cell width prefers width times scale, otherwise loaded image width divided by column; height prefers height times scale, otherwise uses the video ratio. Provide a valid positive grid and usable video dimensions. Preview updates require progress strictly between the endpoints; placement is constrained by progress-bar width. scale uses canvas resampling, so a cross-origin image being displayable does not prove it can be scaled. Configuration does not generate a sprite; use the relevant tool or plugin.
subtitleOffset writes only when a track and cues exist. Stored offset is clamped to [-10, 10]; each write recalculates from retained original cue times, then clamps cue boundaries to [0, duration], rather than accumulating offsets. Paused subtitles also refresh. The notice and subtitleOffset event carry the original input; the getter returns the stored clamped offset. A new track supplies its own state, and missing cues do not store an offset for future loading. The remaining reset declaration refers to the earlier [lifecycle contract](#instance-cleanup), not restoration of these display settings.
```ts
import Artplayer from 'artplayer/runtime';
const art = new Artplayer({ container: '#player' });
const fullscreen: boolean | undefined = art.fullscreen;
const pip: Element | null | boolean = art.pip;
const viewportRect: DOMRect = art.rect;
const pagePosition = { x: art.x, y: art.y };
function showMini(): void { art.mini = true; }
function closeModes(): void { art.state = 'standard'; }
async function inspectFrame(): Promise<number> {
const url = await art.getBlobUrl();
try {
return (await (await fetch(url)).blob()).size;
} finally {
URL.revokeObjectURL(url);
}
}
void [fullscreen, pip, viewportRect, pagePosition, showMini, closeModes, inspectFrame];
```
## `play`
- Type: `Function`
Play the video.
<div className="run-code">▶ Run Code</div>
```js{8}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
muted: true,
});
art.on('ready', () => {
art.play();
});
```
## `pause`
- Type: `Function`
Pause the video.
<div className="run-code">▶ Run Code</div>
```js{11}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
muted: true,
});
art.on('ready', () => {
art.play();
setTimeout(() => {
art.pause();
}, 3000);
});
```
## `toggle`
- Type: `Function`
Toggle video play and pause.
<div className="run-code">▶ Run Code</div>
```js{11}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
muted: true,
});
art.on('ready', () => {
art.toggle();
setTimeout(() => {
art.toggle();
}, 3000);
});
```
## `destroy`
- Type: `Function`
- Parameter: `Boolean`
Destroy the player. Accepts a parameter indicating whether to also remove the player's `html` after destruction. Defaults to `true`.
<div className="run-code">▶ Run Code</div>
```js{7}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', () => {
art.destroy();
});
```
## `reset`
- Type: `Function`
Reset the player's video element: removes the current `src` and calls `load()` once. Commonly used to manually release media resources or reinitialize the video tag in single-page applications.
> Note: The global configuration `Artplayer.REMOVE_SRC_WHEN_DESTROY` will also automatically execute similar logic when `destroy()` is called.
<div className="run-code">▶ Run Code</div>
```js{9}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', () => {
// Only reset the video, do not remove the interface
art.reset();
});
```
## `seek`
- Type: `Setter`
- Parameter: `Number`
Seek to a specific time in the video, in seconds.
<div className="run-code">▶ Run Code</div>
```js{7}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', () => {
art.seek = 5;
});
```
## `forward`
- Type: `Setter`
- Parameter: `Number`
Fast-forward the video time, in seconds.
<div className="run-code">▶ Run Code</div>
```js{7}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', () => {
art.forward = 5;
});
```
## `backward`
- Type: `Setter`
- Parameter: `Number`
Rewind the video time, in seconds.
<div className="run-code">▶ Run Code</div>
```js{10}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', () => {
art.seek = 5;
setTimeout(() => {
art.backward = 2;
}, 3000);
});
```
## `volume`
- Type: `Setter/Getter`
- Parameter: `Number`
Set and get the video volume, range: `[0, 1]`.
<div className="run-code">▶ Run Code</div>
```js{8}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', () => {
console.info(art.volume);
art.volume = 0.5;
console.info(art.volume);
});
```
## `url`
- Type: `Setter/Getter`
- Parameter: `String`
Set and get the video URL.
<div className="run-code">▶ Run Code</div>
```js{7}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', () => {
art.url = '/assets/sample/video.mp4?t=0';
});
```
## `switch`
- Type: `Setter`
- Parameter: `String`
Set the video URL. Similar to `art.url` when setting, but performs some optimization operations.
<div className="run-code">▶ Run Code</div>
```js{9}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', () => {
art.seek = 10;
setTimeout(() => {
art.switch = '/assets/sample/video.mp4?t=0';
}, 3000);
});
```
## `switchUrl`
- Type: `Function`
- Parameter: `String`
Set the video URL. Similar to `art.url` when setting, but performs some optimization operations.
<div className="run-code">▶ Run Code</div>
```js{9}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', () => {
art.seek = 10;
setTimeout(() => {
art.switchUrl('/assets/sample/video.mp4?t=0');
}, 3000);
});
```
:::warning Note
`art.switch` and `art.switchUrl` use the same operation, but only the method returns an observable Promise. Normal completion, replacement, destruction, an equal URL or an empty URL can all resolve; failed automatic playback recovery does not itself reject the switch. Handle rejection and inspect current source/media state as needed; see [source transitions](#source-transitions).
:::
## `switchQuality`
- Type: `Function`
- Parameter: `String`
Sets the video quality URL. Similar to `art.switchUrl`, but retains the previous playback progress.
<div className="run-code">▶ Run Code</div>
```js{9}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', () => {
art.seek = 10;
setTimeout(() => {
art.switchQuality('/assets/sample/video.mp4?t=0');
}, 3000);
});
```
## `muted`
- Type: `Setter/Getter`
- Parameter: `Boolean`
Sets or gets whether the video is muted.
<div className="run-code">▶ Run Code</div>
```js{8}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', () => {
console.info(art.muted);
art.muted = true;
console.info(art.muted);
});
```
## `currentTime`
- Type: `Setter/Getter`
- Parameter: `Number`
Sets or gets the current playback time of the video. Setting the time is similar to `seek`, but it does not trigger additional events.
<div className="run-code">▶ Run Code</div>
```js{8}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', () => {
console.info(art.currentTime);
art.currentTime = 5;
console.info(art.currentTime);
});
```
## `duration`
- Type: `Getter`
Gets the duration of the video.
<div className="run-code">▶ Run Code</div>
```js{7}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', () => {
console.info(art.duration);
});
```
:::warning Note
Some videos may not have a duration, such as live streams or videos that have not been fully decoded. In such cases, the obtained duration will be `0`.
:::
## `screenshot`
- Type: `Function`
Downloads a screenshot of the current video frame. An optional parameter specifies the screenshot filename.
<div className="run-code">▶ Run Code</div>
```js{7}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', () => {
art.screenshot('your-name');
});
```
## `getDataURL`
- Type: `Function`
Gets the `base64` URL of a screenshot of the current video frame. Returns a `Promise`.
<div className="run-code">▶ Run Code</div>
```js{7}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', async () => {
const url = await art.getDataURL();
console.info(url)
});
```
## `getBlobUrl`
- Type: `Function`
Gets the `blob` URL of a screenshot of the current video frame. Returns a `Promise`.
<div className="run-code">▶ Run Code</div>
```js{7}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', async () => {
const url = await art.getBlobUrl();
console.info(url);
});
```
## `fullscreen`
- Type: `Setter/Getter`
- Parameter: `Boolean`
Sets or gets the player's window fullscreen state.
<div className="run-code">▶ Run Code</div>
```js{9}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
controls: [
{
position: 'right',
html: 'Fullscreen Switch',
click: function () {
art.fullscreen = !art.fullscreen;
},
},
],
});
```
:::warning Note
Due to browser security mechanisms, a user interaction (e.g., a click on the page) must occur before triggering window fullscreen.
:::
## `fullscreenWeb`
- Type: `Setter/Getter`
- Parameter: `Boolean`
Sets or gets the player's web page fullscreen state.
<div className="run-code">▶ Run Code</div>
```js{8,11}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
fullscreenWeb: true,
});
art.on('ready', () => {
art.fullscreenWeb = true;
setTimeout(() => {
art.fullscreenWeb = false;
}, 3000);
});
```
## `pip`
- Type: `Setter/Getter`
- Parameter: `Boolean`
Sets or gets the player's Picture-in-Picture (PIP) mode.
<div className="run-code">▶ Run Code</div>
```js{9}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
controls: [
{
position: 'right',
html: 'PIP',
click: function () {
art.pip = !art.pip;
},
},
],
});
```
:::warning Note
Due to browser security mechanisms, a user interaction (e.g., a click on the page) must occur before triggering Picture-in-Picture.
:::
## `poster`
- Type: `Setter/Getter`
- Parameter: `String`
Sets and gets the video poster. The poster effect is only visible before the video starts playing.
<div className="run-code">▶ Run Code</div>
```js{9}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
poster: '/assets/sample/poster.jpg',
});
art.on('ready', () => {
console.info(art.poster);
art.poster = '/assets/sample/poster.jpg?t=0';
console.info(art.poster);
});
```
## `mini`
- Type: `Setter/Getter`
- Parameter: `Boolean`
Sets and gets the player's mini mode.
<div className="run-code">▶ Run Code</div>
```js{7}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', () => {
art.mini = true;
});
```
## `playing`
- Type: `Getter`
- Parameter: `Boolean`
Gets whether the video is currently playing.
<div className="run-code">▶ Run Code</div>
```js{8}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
muted: true,
});
art.on('ready', () => {
console.info(art.playing);
});
```
## `state`
- Type: `Setter/Getter`
- Parameter: `String`
Gets or sets the player's current state. Supported values: `standard` (normal), `mini` (mini window), `pip` (picture-in-picture), `fullscreen` (window fullscreen), `fullscreenWeb` (webpage fullscreen).
<div className="run-code">▶ Run Code</div>
```js{8}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', () => {
console.info(art.state); // Default is 'standard'
art.mini = true;
console.info(art.state); // mini
art.state = 'standard';
});
```
## `autoSize`
- Type: `Function`
Sets whether the video adapts its size automatically.
<div className="run-code">▶ Run Code</div>
```js{8}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', () => {
art.autoSize();
});
```
## `rect`
- Type: `Getter`
Gets the player's dimensions and coordinate information.
<div className="run-code">▶ Run Code</div>
```js{7}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', () => {
console.info(JSON.stringify(art.rect));
});
```
:::warning Note
The dimension and coordinate information is obtained via `getBoundingClientRect`.
:::
## `bottom` / `top` / `left` / `right` / `x` / `y` / `width` / `height`
- Type: `Getter`
These properties provide quick access to `rect`:
- `bottom`, `top`, `left`, `right`, `x`, `y`: Correspond to the fields of the same name in `DOMRect`.
- `width`, `height`: The player's current visible width and height.
<div className="run-code">▶ Run Code</div>
```js{7}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', () => {
console.info(art.width, art.height, art.left, art.top);
});
```
## `flip`
- Type: `Setter/Getter`
- Parameter: `String`
Sets and gets the player's flip state. Supported values: `normal`, `horizontal`, `vertical`.
<div className="run-code">▶ Run Code</div>
```js{8}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', () => {
console.info(art.flip);
art.flip = 'horizontal';
console.info(art.flip);
});
```
## `playbackRate`
- Type: `Setter/Getter`
- Parameter: `Number`
Sets and gets the player's playback speed.
<div className="run-code">▶ Run Code</div>
```js{8}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', () => {
console.info(art.playbackRate);
art.playbackRate = 2;
console.info(art.playbackRate);
});
```
## `aspectRatio`
- Type: `Setter/Getter`
- Parameter: `String`
Sets and gets the player's aspect ratio.
<div className="run-code">▶ Run Code</div>
```js{8}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', () => {
console.info(art.aspectRatio);
art.aspectRatio = '16:9';
console.info(art.aspectRatio);
});
```
## `autoHeight`
- Type: `Function`
When the container only has a defined width, this property can automatically calculate and set the video's height.
<div className="run-code">▶ Run Code</div>
```js{7,11}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', () => {
art.autoHeight();
});
art.on('resize', () => {
art.autoHeight();
});
```
:::warning Note
This property is useful when your container has only a width but the exact height is unknown. It can automatically calculate the video height, but you need to determine the timing for setting this property.
:::
## `attr`
- Type: `Function`
- Parameter: `String`
Dynamically get and set attributes of the video element.
<div className="run-code">▶ Run Code</div>
```js{8}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', () => {
console.info(art.attr('playsInline'));
art.attr('playsInline', true);
console.info(art.attr('playsInline'));
});
```
## `type`
- Type: `Setter/Getter`
- Parameter: `String`
Dynamically get and set the video type.
<div className="run-code">▶ Run Code</div>
```js{8}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', () => {
console.info(art.type);
art.type = 'm3u8';
console.info(art.type);
});
```
## `theme`
- Type: `Setter/Getter`
- Parameter: `String`
Dynamically get and set the player's theme color.
<div className="run-code">▶ Run Code</div>
```js{8}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', () => {
console.info(art.theme);
art.theme = '#000';
console.info(art.theme);
});
```
## `airplay`
- Type: `Function`
Initiate AirPlay.
<div className="run-code">▶ Run Code</div>
```js{9}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
controls: [
{
position: 'right',
html: 'AirPlay',
click: function () {
art.airplay();
},
},
],
});
```
## `loaded`
- Type: `Getter`
The proportion of video buffered, ranging from `[0, 1]`. Often used with the `video:timeupdate` event.
<div className="run-code">▶ Run Code</div>
```js{7}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('video:timeupdate', () => {
console.info(art.loaded);
});
```
## `loadedTime`
- Type: `Getter`
The buffered media duration in seconds. Typically used alongside `loaded` to display detailed buffering progress.
<div className="run-code">▶ Run Code</div>
```js{7}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('video:timeupdate', () => {
console.info(art.loadedTime);
});
```
## `played`
- Type: `Getter`
The proportion of video played, ranging from `[0, 1]`. Often used with the `video:timeupdate` event.
<div className="run-code">▶ Run Code</div>
```js{7}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('video:timeupdate', () => {
console.info(art.played);
});
```
## `proxy`
- Type: `Function`
A proxy function for `DOM` events, essentially proxying `addEventListener` and `removeEventListener`. When using `proxy` to handle events, the event is automatically cleaned up when the player is destroyed.
<div className="run-code">▶ Run Code</div>
```js{8-10}
var container = document.querySelector('.artplayer-app');
var art = new Artplayer({
container: container,
url: '/assets/sample/video.mp4',
});
art.proxy(container, 'click', event => {
console.info(event);
});
```
:::warning Note
If you need certain `DOM` events to exist only for the player's lifecycle, it is strongly recommended to use this function to avoid memory leaks.
:::
## `query`
- Type: `Function`
A `DOM` query function, similar to `document.querySelector`, but the search is scoped to the current player, preventing errors with duplicate class names.
<div className="run-code">▶ Run Code</div>
```js{6}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
console.info(art.query('.art-video'));
```
## `video`
- Type: `Element`
Quickly returns the player's `video` element.
<div className="run-code">▶ Run Code</div>
```js{6}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
console.info(art.video);
```
### Native and proxy media capabilities {#media-capabilities}
art.video is the current template media node, identical to art.template.$video; it may be an adapted canvas rather than an HTMLVideoElement. The root entry retains its historical video type. runtime exports MediaSurface as NativeMedia | CanvasMedia: CanvasMedia describes an actual canvas plus media state, source, dimensions, buffered ranges, volume/rate, load and playback methods supplied by an adapter. A bare canvas does not satisfy that contract, and the type does not install an adapter.
PlaybackMethods allows an adapter's own play/pause return types. Calling art.video.play/pause acts directly on that surface, with its native or adapter result; the Artplayer method facade separately applies notices, custom events, operation ownership and mutex behavior. MediaState describes currentTime/duration/paused/ended/readyState and an optional boolean playing hint; when that hint is absent, the core derives playing from time greater than zero, not paused/ended, and readyState greater than two. These state fields do not prove decoded frames are being presented.
| Optional capability | Meaning and check |
| --- | --- |
| textTracks, error | Track-list-like data / native or adapter error value. Proxies may omit either; error is unknown, not guaranteed to be an Error instance |
| requestVideoFrameCallback, cancelVideoFrameCallback | Optional paired frame-callback methods. Detect each function and preserve the media receiver; no timer fallback is promised by these types |
| requestPictureInPicture | Optional native PiP request returning a Promise; availability does not remove user-activation, policy or media requirements |
| webkitEnterFullscreen, webkitExitFullscreen, webkitSupportsFullscreen | WebKit media-fullscreen methods and capability flag; detect before use |
| webkitSupportsPresentationMode, webkitSetPresentationMode, webkitPresentationMode | WebKit presentation-mode capability, request and observed mode; a supported method does not guarantee the requested mode succeeds |
| webkitDisplayingFullscreen | Optional observed media-fullscreen state, not a request |
| webkitShowPlaybackTargetPicker | Optional AirPlay picker. The core additionally checks availability events; calling it is not proof of a receiver connection |
Keep optional native calls bound to the media object, handle Promise failures, and use the public display APIs for normal player integration. Capability detection or Windows WebKit execution is not physical Safari/iOS/AirPlay acceptance. The following type-only consumer keeps access optional without issuing a display request:
```ts
import Artplayer from 'artplayer/runtime';
import type { MediaSurface, NativeMedia, CanvasMedia } from 'artplayer/runtime';
const art = new Artplayer({ container: '#player' });
const media: MediaSurface = art.video;
const error: unknown = media.error;
const tracks: ArrayLike<TextTrack> | undefined = media.textTracks;
const supportsFrames = typeof media.requestVideoFrameCallback === 'function'
&& typeof media.cancelVideoFrameCallback === 'function';
const surface: NativeMedia | CanvasMedia = media;
void [error, tracks, supportsFrames, surface];
```
## `cssVar`
- Type: `Function`
Dynamically get or set `CSS` variables.
<div className="run-code">▶ Run Code</div>
```js{8}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', () => {
console.log(art.cssVar('--art-theme'));
art.cssVar('--art-theme', 'green');
console.log(art.cssVar('--art-theme'));
});
```
### Values, writes, and cascade {#css-variable-contract}
art.cssVar(name) reads getComputedStyle from art.template.$player and returns a **string**, including for opacity, scale and z-index. It returns the computed custom-property text, not a parsed number or necessarily a normalized color. A missing variable returns an empty string. art.theme delegates to '--art-theme'.
The second argument uses a historical truthiness check. A truthy value calls style.setProperty and returns undefined; numeric 0, an empty string, false, null, undefined or NaN instead read the current value. Use the string '0' to write zero. To remove an inline override, use art.template.$player.style.removeProperty(name); cssVar(name, '') does not remove it. Values are passed to CSS rather than validated against CssVar; invalid CSS tokens may be stored while the consuming property falls back or becomes invalid.
Writes affect this player's inline style and its descendants, not other instances. They do not update art.option.cssVar or art.option.theme, emit a theme event, or install a plugin. At construction, nonempty option.theme takes precedence over the initial '--art-theme' option. External styles follow normal CSS cascade rules: scope overrides to the actual .art-video-player, because the built-in defaults declared on that node can override values merely inherited from its container. Inline overrides generally win ordinary stylesheet declarations; !important declarations can still change the result.
The root cssVar signature retains historical numeric/literal types, including the 9999 literal for '--art-fullscreen-web-index'. Runtime values are not limited to that literal. The runtime entry exposes string reads and string-or-void writes without changing behavior; the constructor's legacy cssVar option shape remains distinct.
### Built-in defaults and consumers {#css-variable-defaults}
These are the base stylesheet values, before constructor overrides, mobile/fullscreen classes, user CSS and inline styles. Length values need CSS units where applicable. Naming a variable here does not prove support for a browser-specific pseudo-element or feature.
| Variable | Base value | Used for |
| --- | --- | --- |
| `--art-theme` | `#f00` | Accent color for progress and selected items |
| `--art-font-color` | `#fff` | Base text, links and SVG fill |
| `--art-background-color` | `#000` | Player background |
| `--art-text-shadow-color` | `rgba(0, 0, 0, 0.5)` | Base text shadow color |
| `--art-transition-duration` | `0.2s` | Duration for transitions that consume it, not every animation |
| `--art-padding` | `10px` | Spacing for the bottom area, menus and info |
| `--art-border-radius` | `3px` | Corner radius for panels and tips |
| `--art-progress-height` | `6px` | Progress control height; inner track starts at half height |
| `--art-progress-color` | `rgba(255, 255, 255, 0.25)` | Progress track background |
| `--art-progress-top-gap` | `10px` | Top interaction padding above the progress track |
| `--art-hover-color` | `rgba(255, 255, 255, 0.25)` | Progress hover range color |
| `--art-loaded-color` | `rgba(255, 255, 255, 0.25)` | Buffered range color |
| `--art-state-size` | `80px` | Central playback-state button size |
| `--art-state-opacity` | `0.8` | State-button opacity when shown |
| `--art-bottom-height` | `100px` | Bottom gradient background height, not total control layout height |
| `--art-bottom-offset` | `20px` | Translation of bottom controls while hidden |
| `--art-bottom-gap` | `5px` | Gap below progress and around related overlays |
| `--art-highlight-width` | `8px` | Timestamp marker width |
| `--art-highlight-color` | `rgba(255, 255, 255, 0.5)` | Timestamp marker color |
| `--art-control-height` | `46px` | Control-item minimum height/width and layout fallback |
| `--art-control-opacity` | `0.75` | Control-item opacity outside hover |
| `--art-control-icon-size` | `36px` | Control icon width and height |
| `--art-control-icon-scale` | `1.1` | Control icon scale, with a further pressed-state factor |
| `--art-volume-height` | `120px` | Volume panel height |
| `--art-volume-handle-size` | `14px` | Volume slider handle size |
| `--art-lock-size` | `36px` | Mobile lock-button size |
| `--art-indicator-scale` | `0` | Base progress indicator scale; hover/active rules can override it |
| `--art-indicator-size` | `16px` | Progress indicator width and height |
| `--art-fullscreen-web-index` | `9999` | Web-fullscreen stacking level, not native fullscreen permission |
| `--art-settings-icon-size` | `24px` | Left-side setting icon size |
| `--art-settings-max-height` | `300px` | CSS setting-panel maximum; JS also constrains available space |
| `--art-selector-max-height` | `300px` | Control selector maximum height |
| `--art-contextmenus-min-width` | `250px` | Context-menu minimum width |
| `--art-subtitle-font-size` | `20px` | Subtitle font size |
| `--art-subtitle-gap` | `5px` | Gap between subtitle lines |
| `--art-subtitle-bottom` | `15px` | Base subtitle bottom offset; controls can add layout height |
| `--art-subtitle-border` | `#000` | Subtitle outline text-shadow color, not border width |
| `--art-widget-background` | `rgba(0, 0, 0, 0.85)` | Menu, setting and thumbnail panel background |
| `--art-tip-background` | `rgba(0, 0, 0, 0.7)` | Progress tip, notice and lock-button background |
| `--art-scrollbar-size` | `4px` | Width/height of WebKit scrollbar pseudo-elements |
| `--art-scrollbar-background` | `rgba(255, 255, 255, 0.25)` | WebKit scrollbar thumb color |
| `--art-scrollbar-background-hover` | `rgba(255, 255, 255, 0.5)` | Hovered WebKit scrollbar thumb color |
| `--art-mini-progress-height` | `2px` | Retained historical value; current core styles do not consume it |
### Mode overrides and measured layout {#css-variable-modes}
The mobile class changes bottom-gap to 10px, control-height to 38px, control-icon-scale to 1, state-size to 60px, settings/selector-max-height to 180px, indicator-scale to 1, and control-opacity to 1. Fullscreen styles change progress-height to 8px, indicator-size to 20px, control-height to 60px and control-icon-scale to 1.3; web fullscreen reuses those styles. When classes overlap, specificity and stylesheet order decide the result, and explicit inline values can suppress these mode defaults. These are style changes, not device or fullscreen-capability detection.
The control-layout observer additionally writes '--art-controls-height' from the rendered control area's offsetHeight. Subtitle and panel positioning use that measurement, falling back to '--art-control-height'. It is an internal measured value, not one of the 43 declared input variables; manual writes can be replaced by a later resize observation. Changing CSS dimensions does not change layout constants such as SETTING_ITEM_HEIGHT or configure player features.
'--art-mini-progress-height' remains declared with a 2px default for compatibility, but has no current core consumer. The mini progress presentation uses the normal progress/control geometry; changing that unused variable alone has no effect.
```ts
import Artplayer from 'artplayer/runtime';
const art = new Artplayer({ container: '#player' });
const opacity: string = art.cssVar('--art-control-opacity');
const result: string | void = art.cssVar('--art-control-opacity', '0');
art.cssVar('--art-fullscreen-web-index', '10001');
art.theme = 'green';
const theme: string = art.theme;
art.template.$player?.style.removeProperty('--art-control-opacity');
void [opacity, result, theme];
```
## `quality`
- Type: `Setter`
- Parameter: `Array`
Dynamically set the quality list.
<div className="run-code">▶ Run Code</div>
```js{19-29}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
quality: [
{
default: true,
html: 'SD 480P',
url: '/assets/sample/video.mp4',
},
{
html: 'HD 720P',
url: '/assets/sample/video.mp4',
},
],
});
art.on('ready', () => {
setTimeout(() => {
art.quality = [
{
default: true,
html: '1080P',
url: '/assets/sample/video.mp4',
},
{
html: '4K',
url: '/assets/sample/video.mp4',
},
];
}, 3000);
})
```
## `thumbnails`
- Type: `Setter/Getter`
- Parameter: `Object`
Dynamically set thumbnails.
<div className="run-code">▶ Run Code</div>
```js
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.on('ready', () => {
art.thumbnails = {
url: '/assets/sample/thumbnails.png',
number: 60,
column: 10,
};
});
```
## `subtitleOffset`
- Type: `Setter/Getter`
- Parameter: `Number`
Dynamically set subtitle offset.
<div className="run-code">▶ Run Code</div>
```js
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
subtitle: {
url: '/assets/sample/subtitle.srt',
},
});
art.on('ready', () => {
art.subtitleOffset = 1;
});
```
===== packages/artplayer-vitepress/docs/en/component/contextmenu.md =====
# Context Menu
## Configuration
| Property | Type | Description |
| --------- | ------------------- | ------------------------------------ |
| `disable` | `Boolean` | Whether to disable the component |
| `name` | `String` | Unique component name for CSS class |
| `index` | `Number` | Component index for display priority |
| `html` | `String`, `Element`, `Number` | DOM element of the component |
| `style` | `Object` | Component style object |
| `click` | `Function` | Component click event |
| `mounted` | `Function` | Triggered after component mount |
| `beforeUnmount` | `Function` | Hook before explicit remove/update |
| `tooltip` | `String`, `Number` | Tooltip text for the component |
## Creation
<div className="run-code">▶ Run Code</div>
```js{4-13}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
contextmenu: [
{
name: 'your-menu',
html: 'Your Menu',
click: function (...args) {
console.info(args);
art.contextmenu.show = false;
},
},
],
});
art.contextmenu.show = true;
// Get the Element of contextmenu by name
console.info(art.contextmenu['your-menu']);
```
## Addition
<div className="run-code">▶ Run Code</div>
```js{6-13}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.contextmenu.add({
name: 'your-menu',
html: 'Your Menu',
click: function (...args) {
console.info(args);
art.contextmenu.show = false;
},
});
art.contextmenu.show = true;
// Get the Element of contextmenu by name
console.info(art.contextmenu['your-menu']);
```
## Deletion
<div className="run-code">▶ Run Code</div>
```js{21}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
contextmenu: [
{
name: 'your-menu',
html: 'Your Menu',
click: function (...args) {
console.info(args);
art.contextmenu.show = false;
},
},
],
});
art.contextmenu.show = true;
art.on('ready', () => {
setTimeout(() => {
// Delete the contextmenu by name
art.contextmenu.remove('your-menu')
}, 3000);
});
```
## Update
<div className="run-code">▶ Run Code</div>
```js{21-24}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
contextmenu: [
{
name: 'your-menu',
html: 'Your Menu',
click: function (...args) {
console.info(args);
art.contextmenu.show = false;
},
},
],
});
art.contextmenu.show = true;
art.on('ready', () => {
setTimeout(() => {
// Update the contextmenu by name
art.contextmenu.update({
name: 'your-menu',
html: 'Your New Menu',
})
}, 3000);
});
```
## Menu management and lifecycle {#contextmenu-contract}
See [shared component behavior](./layers#component-contract) for options, callbacks, return values, name conflicts and update/cleanup rules. The parent is template.$contextmenu; the manager name is contextmenu, with art-contextmenu and art-contextmenu-NAME item classes. show/toggle control the whole menu, not individual availability. Clicking a custom entry does not automatically close it; set art.contextmenu.show = false in your callback when needed.
Desktop initialization installs built-in/configured entries and handles the context-menu event, keyboard, outside clicks and player blur. Mobile does not automatically run that initialization. Direct add can still register entries, but does not install the complete desktop context-menu flow. Use add/update/remove rather than repeating initialization to refresh entries.
## TypeScript menu example
```ts
import Artplayer from 'artplayer/runtime';
const art = new Artplayer({ container: '#player', url: '/video.mp4' });
const menu: HTMLDivElement | undefined = art.contextmenu.add({
name: 'custom-menu', html: 'Close menu',
click() { this.contextmenu.show = false; },
});
if (menu) art.contextmenu.remove('custom-menu');
```
===== packages/artplayer-vitepress/docs/en/component/controls.md =====
# Controls
## Configuration
| Property | Type | Description |
| ---------- | ------------------- | ------------------------------------------------ |
| `disable` | `Boolean` | Whether to disable the control |
| `name` | `String` | Unique name of the control, used for class marking |
| `index` | `Number` | Control index, determines display priority |
| `html` | `String`, `Element`, `Number` | DOM element of the control |
| `style` | `Object` | Style object for the control |
| `click` | `Function` | Click event handler for the control |
| `mounted` | `Function` | Triggered after the control is mounted |
| `beforeUnmount` | `Function` | Hook before explicit remove/update |
| `tooltip` | `String`, `Number` | Tooltip text for the control |
| `position` | `String` | Required: top, left or right |
| `selector` | `Array` | Array of objects for selection list |
| `onSelect` | `Function` | Function triggered when a selection list item is clicked |
## Creation
<div className="run-code">▶ Run Code</div>
```js
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
controls: [
{
name: 'your-button',
index: 10,
position: 'left',
html: 'Your Button',
tooltip: 'Your Button',
style: {
color: 'red',
},
click: function (...args) {
console.info('click', args);
},
mounted: function (...args) {
console.info('mounted', args);
},
},
{
name: 'subtitle',
position: 'right',
html: 'Subtitle',
selector: [
{
default: true,
html: '<span style="color:red">subtitle 01</span>',
},
{
html: '<span style="color:yellow">subtitle 02</span>',
},
],
onSelect: function (item, $dom) {
console.info(item, $dom);
return 'Your ' + item.html;
},
},
],
});
// Get the Element of control by name
console.info(art.controls['your-button']);
console.info(art.controls['subtitle']);
```
## Adding
<div className="run-code">▶ Run Code</div>
```js{6-21}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.controls.add({
name: 'button1',
index: 10,
position: 'left',
html: 'Your Button',
tooltip: 'Your Button',
style: {
color: 'red',
},
click: function (...args) {
console.info('click', args);
},
mounted: function (...args) {
console.info('mounted', args);
},
});
// Get the Element of control by name
console.info(art.controls['button1']);
```
## Removal
<div className="run-code">▶ Run Code</div>
```js{21}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
controls: [
{
name: 'button1',
index: 10,
position: 'right',
html: 'Your Button',
tooltip: 'Your Button',
style: {
color: 'red',
},
}
]
});
art.on('ready', () => {
setTimeout(() => {
// Delete the control by name
art.controls.remove('button1');
}, 3000);
});
```
## Updating
<div className="run-code">▶ Run Code</div>
```js{26-40}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
controls: [
{
name: 'button1',
index: 10,
position: 'right',
html: 'Subtitle',
selector: [
{
default: true,
html: 'subtitle 01',
},
{
html: 'subtitle 02',
},
],
}
]
});
art.on('ready', () => {
setTimeout(() => {
// Update the control by name
art.controls.update({
name: 'button1',
index: 10,
position: 'right',
html: 'New Subtitle',
selector: [
{
default: true,
html: 'new subtitle 01',
},
{
html: 'new subtitle 02',
},
],
});
}, 3000);
});
```
## Manager and selector behavior {#control-contract}
See [shared component behavior](./layers#component-contract) for options, callbacks, shallow updates and cleanup. position must be top, left or right, using template.$progress, $controlsLeft or $controlsRight respectively. Missing/other values throw. $parent records the latest add position, not a shared parent for all controls. The manager name is control; classes are art-control/art-control-NAME. There is no center option.
add/update return undefined. Obtain a node through mounted, cache.get(name)?.$ref or its known name property. setting and thumbnails are optional built-in node references, not the setting manager or thumbnail data. init installs built-in and configured entries; it is not a repeatable reset API and can fail on duplicate names. isHover tracks the pointer's relation to the bottom area. timer is the last-show timestamp, not a timer handle. Automatic hiding depends on playback timeupdate, CONTROL_HIDE_TIME, settings, input, pointer and keyboard focus; it is not an exact timer.
selector creates a list only in left/right positions. Item html is display content, value is custom string/number data, and default is the selection flag; value does not automatically switch media. Initial button content comes from the control's html. Initial default flags mark list items without replacing the button label. List and subsequent button content use innerHTML: use trusted strings. Although historical types allow HTMLElement, this path does not move that element as ordinary component html does.
A click first updates default flags, button content and highlighting, then calls onSelect(item, itemElement, event) with the player as this. Its return value, including a Promise result, becomes the button's new innerHTML. Undefined does not automatically fall back to the previous content; return the intended text or HTML. Async results update the label only while this selection is current and the entry remains active. Results from earlier selections or removed/replaced entries are ignored. Errors produce a warning and do not roll back the selected item.
controls.check(item) updates selection using a bound item. No argument is a no-op; an arbitrary unbound object is not supported. Items receive readonly nonenumerable $control_option (original array), $control_item (list node) and $control_value (button-value node) accessors. One item object cannot belong to two active selectors at once; it can be reused after the old control releases it. controls.selector is a renderer requiring managed nodes and cleanup arrays; applications should use the selector option of add/update.
## TypeScript control example
```ts
import Artplayer from 'artplayer/runtime';
import type { SelectorItem } from 'artplayer/runtime';
const art = new Artplayer({ container: '#player', url: '/video.mp4' });
const items: SelectorItem[] = [{ html: 'One', value: 1, default: true }, { html: 'Two', value: 2 }];
const result: undefined = art.controls.add({
name: 'choices', position: 'right', html: 'Choose', selector: items,
async onSelect(item) { return String(item.html); },
});
art.controls.check(items[1]);
console.log(result, art.controls.cache.get('choices')?.$ref);
art.controls.remove('choices');
```
===== packages/artplayer-vitepress/docs/en/component/layers.md =====
# Layers
## Configuration
| Property | Type | Description |
| --------- | ------------------- | ------------------------------------ |
| `disable` | `Boolean` | Whether to disable the component |
| `name` | `String` | Unique component name for CSS class |
| `index` | `Number` | Component index for display priority |
| `html` | `String`, `Element`, `Number` | Component DOM element |
| `style` | `Object` | Component style object |
| `click` | `Function` | Component click event |
| `mounted` | `Function` | Triggered after component mount |
| `beforeUnmount` | `Function` | Hook before explicit remove/update |
| `tooltip` | `String`, `Number` | Component tooltip text |
## Creation
<div className="run-code">▶ Run Code</div>
```js{5-22}
var img = '/assets/sample/layer.png';
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
layers: [
{
name: 'potser',
html: `<img style="width: 100px" src="${img}">`,
tooltip: 'Potser Tip',
style: {
position: 'absolute',
top: '50px',
right: '50px',
},
click: function (...args) {
console.info('click', args);
},
mounted: function (...args) {
console.info('mounted', args);
},
},
],
});
// Get the Element of layer by name
console.info(art.layers['potser']);
```
## Addition
<div className="run-code">▶ Run Code</div>
```js{7-22}
var img = '/assets/sample/layer.png';
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
art.layers.add({
name: 'potser',
html: `<img style="width: 100px" src="${img}">`,
tooltip: 'Potser Tip',
style: {
position: 'absolute',
top: '50px',
right: '50px',
},
click: function (...args) {
console.info('click', args);
},
mounted: function (...args) {
console.info('mounted', args);
},
});
// Get the Element of layer by name
console.info(art.layers['potser']);
```
## Removal
<div className="run-code">▶ Run Code</div>
```js{21}
var img = '/assets/sample/layer.png';
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
layers: [
{
name: 'potser',
html: `<img style="width: 100px" src="${img}">`,
style: {
position: 'absolute',
top: '50px',
right: '50px',
},
},
],
});
art.on('ready', () => {
setTimeout(() => {
// Delete the layer by name
art.layers.remove('potser');
}, 3000);
});
```
## Update
<div className="run-code">▶ Run Code</div>
```js{21-29}
var img = '/assets/sample/layer.png';
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
layers: [
{
name: 'potser',
html: `<img style="width: 100px" src="${img}">`,
style: {
position: 'absolute',
top: '50px',
right: '50px',
},
},
],
});
art.on('ready', () => {
setTimeout(() => {
// Update the layer by name
art.layers.update({
name: 'potser',
html: `<img style="width: 200px" src="${img}">`,
style: {
position: 'absolute',
top: '50px',
left: '50px',
},
});
}, 3000);
});
```
## Shared component behavior {#component-contract}
This section applies to layers, controls and contextmenu. The setting panel has its own API.
- add accepts an option object or a synchronous (art) => option factory. The factory receives the player; do not rely on its this. A truthy disable skips creation, rather than disabling an existing node. Options are retained and may be mutated: falsy html, including numeric 0, is normalized to an empty string.
- Names are unique within a manager. Duplicate add and remove of a missing name throw; update of a missing name calls add. Omitted names use the manager name and incrementing id. A name also becomes a node property on the manager: avoid existing members such as add, cache and show.
- index inserts in ascending order within one parent. A new equal-index entry precedes the old one. Zero and omitted index use the incrementing id, rather than forcing first position. Different control positions have separate ordering.
- html strings are parsed as HTML; use trusted content. An HTMLElement is moved rather than cloned. Nonzero numeric content is accepted. style assigns node styles directly; tooltip accepts strings or numbers, with falsy values omitted.
- click receives the component manager and native event, not the individual node. Its this is the player; preventDefault runs before the callback, but stopPropagation is not automatic. mounted and beforeUnmount also receive the player as this and the node as their argument. Ordinary component callback results are ignored; Promises are not awaited.
- mounted runs synchronously after insertion and cache/name registration. beforeUnmount runs before explicit remove or update removes the old node; if it throws, the old entry remains. Player destruction releases managed resources but does not call this hook for every entry. Own subscriptions and timers need a separate idempotent cleanup function connected to both the component hook and player destroy.
- update shallow-merges into the original option object by name, then removes and adds the entry. It replaces DOM, reruns mounted and invalidates old node references. Nested objects such as style are not deeply merged. beforeUnmount sees the merged options, including a newly supplied hook. Failed replacement does not transactionally restore the old node.
layers.add/update and contextmenu.add/update return the new HTMLDivElement, or possibly undefined when disabled or closing. controls.add/update retain their historical undefined result. add/remove/update are bound and can be extracted; toggle requires its manager receiver.
show controls the manager's CSS state; toggle inverts it. Every show assignment emits layer, control or contextmenu, even if the boolean state is unchanged; it does not remove entries. art references the player, name is layer/control/contextmenu, $parent is the current insertion parent, and id is an incrementing counter. cache is a Map keyed by name, with $ref, an events cleanup array and the original option. Observe these fields rather than mutating them to bypass lifecycle methods.
The layer parent is template.$layer; generated classes are art-layer and art-layer-NAME. Root types retain the historical Component/ComponentOption/Selector shapes, adding numeric content through ComponentInput overloads. The unpublished refactor's artplayer/runtime exports accurate Component, Controls, `ComponentInput<Host>` and callback/return types. Infer cache-entry shapes from the instance; the internal ComponentEntry declaration is not a named export of that entry.
## TypeScript component example
```ts
import Artplayer from 'artplayer/runtime';
const art = new Artplayer({ container: '#player', url: '/video.mp4' });
const element: HTMLDivElement | undefined = art.layers.add({
name: 'counter', html: 1,
click(manager, event) { console.log(this === art, manager.name, event.type); },
});
if (element) {
art.layers.update({ name: 'counter', html: 2 });
art.layers.remove('counter');
}
```
===== packages/artplayer-vitepress/docs/en/component/setting.md =====
# Settings Panel
## Built-in
Enable setting: true and the corresponding options to install these four built-in items: `flip`, `playbackRate`, `aspectRatio`, `subtitleOffset`.
<div className="run-code">▶ Run Code</div>
```js{4}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
setting: true,
flip: true,
playbackRate: true,
aspectRatio: true,
subtitleOffset: true,
});
```
## Create - Button
| Property | Type | Description |
| ---------- | ------------------- | -------------------- |
| `html` | `String`, `Element`, `Number` | The DOM element |
| `icon` | `String`, `Element`, `Number` | The icon element |
| `onClick` | `Function` | The click event |
| `width` | `Number` | The list width |
| `tooltip` | `String`, `Element`, `Number` | The tooltip text |
<div className="run-code">▶ Run Code</div>
```js
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
setting: true,
settings: [
{
html: 'Button',
icon: '<img width="22" height="22" src="/assets/img/state.svg">',
tooltip: 'tooltip',
onClick(item, $dom, event) {
console.info(item, $dom, event);
return 'new tooltip';
},
},
],
});
```
## Create - Selection List
| Property | Type | Description |
| ---------- | ------------------- | -------------------- |
| `html` | `String`, `Element`, `Number` | The DOM element |
| `icon` | `String`, `Element`, `Number` | The icon element |
| `selector` | `Array` | The list of elements |
| `onSelect` | `Function` | The click event |
| `width` | `Number` | The list width |
| `tooltip` | `String`, `Element`, `Number` | The tooltip text |
<div className="run-code">▶ Run Code</div>
```js
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
setting: true,
settings: [
{
html: 'Subtitle',
width: 250,
tooltip: 'Subtitle 01',
selector: [
{
default: true,
html: '<span style="color:red">Subtitle 01</span>',
url: '/assets/sample/subtitle.srt?id=1',
},
{
html: '<span style="color:yellow">Subtitle 02</span>',
url: '/assets/sample/subtitle.srt?id=2',
},
],
onSelect: function (item, $dom, event) {
console.info(item, $dom, event);
art.subtitle.url = item.url;
return item.html;
},
},
{
html: 'Quality',
width: 150,
tooltip: '1080P',
selector: [
{
default: true,
html: '1080P',
url: '/assets/sample/video.mp4?id=1080',
},
{
html: '720P',
url: '/assets/sample/video.mp4?id=720',
},
{
html: '360P',
url: '/assets/sample/video.mp4?id=360',
},
],
onSelect: function (item, $dom, event) {
console.info(item, $dom, event);
art.switchQuality(item.url, item.html);
return item.html;
},
},
],
});
```
## Create - Nested List
<div className="run-code">▶ Run Code</div>
```js
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
setting: true,
settings: [
{
html: 'Multi-level',
selector: [
{
html: 'Setting 01',
width: 150,
selector: [
{
html: 'Setting 01 - 01',
},
{
html: 'Setting 01 - 02',
},
],
onSelect: function (item, $dom, event) {
console.info(item, $dom, event);
return item.html;
},
},
{
html: 'Setting 02',
width: 150,
selector: [
{
html: 'Setting 02 - 01',
},
{
html: 'Setting 02 - 02',
},
],
onSelect: function (item, $dom, event) {
console.info(item, $dom, event);
return item.html;
},
},
],
},
],
});
```
## Create - Toggle Button
| Property | Type | Description |
| ---------- | ------------------- | -------------------------- |
| `html` | `String`, `Element`, `Number` | DOM element for the item |
| `icon` | `String`, `Element`, `Number` | Icon for the item |
| `switch` | `Boolean` | Default state of the button |
| `onSwitch` | `Function` | Button toggle event |
| `tooltip` | `String`, `Element`, `Number` | Tooltip text |
<div className="run-code">▶ Run Code</div>
```js
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
setting: true,
settings: [
{
html: 'PIP Mode',
tooltip: 'Close',
icon: '<img width="22" height="22" src="/assets/img/state.svg">',
switch: false,
onSwitch: function (item, $dom, event) {
console.info(item, $dom, event);
const nextState = !item.switch;
art.pip = nextState;
item.tooltip = nextState ? 'Open' : 'Close';
return nextState;
},
},
],
});
```
## Create - Range Slider
| Property | Type | Description |
| ---------- | ------------------- | -------------------------- |
| `html` | `String`, `Element`, `Number` | DOM element for the item |
| `icon` | `String`, `Element`, `Number` | Icon for the item |
| `range` | `Array` | Default state array |
| `onRange` | `Function` | Event triggered on completion |
| `onChange` | `Function` | Event triggered on change |
| `tooltip` | `String`, `Element`, `Number` | Tooltip text |
```js
const range = [5, 1, 10, 1];
const value = range[0];
const min = range[1];
const max = range[2];
const step = range[3];
```
<div className="run-code">▶ Run Code</div>
```js
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
setting: true,
settings: [
{
html: 'Slider',
tooltip: '5x',
icon: '<img width="22" height="22" src="/assets/img/state.svg">',
range: [5, 1, 10, 1],
onChange: function (item, $dom, event) {
console.info(item, $dom, event);
return item.range[0] + 'x';
},
},
],
});
```
## Add
<div className="run-code">▶ Run Code</div>
```js{9-14}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
setting: true,
});
art.setting.show = true;
art.setting.add({
html: 'Slider',
tooltip: '5x',
icon: '<img width="22" height="22" src="/assets/img/state.svg">',
range: [5, 1, 10, 1],
});
```
## Remove
<div className="run-code">▶ Run Code</div>
```js{22}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
setting: true,
flip: true,
settings: [
{
name: 'slider',
html: 'Slider',
tooltip: '5x',
icon: '<img width="22" height="22" src="/assets/img/state.svg">',
range: [5, 1, 10, 1],
},
],
});
art.setting.show = true;
art.on('ready', () => {
setTimeout(() => {
// Delete the setting by name
art.setting.remove('slider');
}, 3000);
});
```
## Update
<div className="run-code">▶ Run Code</div>
```js{21-27}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
setting: true,
settings: [
{
name: 'slider',
html: 'Slider',
tooltip: '5x',
icon: '<img width="22" height="22" src="/assets/img/state.svg">',
range: [5, 1, 10, 1],
},
],
});
art.setting.show = true;
art.on('ready', () => {
setTimeout(() => {
// Remove the old interaction field before changing from range to switch
delete art.setting.find('slider').range;
// Update the setting by name
art.setting.update({
name: 'slider',
html: 'PIP Mode',
tooltip: 'Close',
icon: '<img width="22" height="22" src="/assets/img/state.svg">',
switch: false,
});
}, 3000);
});
```
## Setting items and callback rules {#setting-contract}
Settings use a tree model, not the controls/layers node registry or beforeUnmount rules. Set setting: true to format/render the panel and install its regular events during construction. Each of the four built-in entries also requires its corresponding option. Their actual names are playback-rate, aspect-ratio, flip and subtitle-offset.
| Field | Actual purpose |
| --- | --- |
| name | Unique across the tree; omitted names become setting-N on the original object |
| html / icon / tooltip | Trusted HTML strings, HTMLElement or numbers; rendering installs content accessors tied to nodes |
| width | Requested submenu width; zero/omitted uses SETTING_WIDTH, and actual dimensions are constrained by the player |
| value | Custom string/number data, not automatic media switching; historical falsy handling writes numeric zero as an empty data-value |
| default | Selection flag; initial rendering does not replace the parent's tooltip from the selected entry |
| selector | Nested items; a nonempty array opens a submenu |
| switch | Initial state; onSwitch must return the next state, with no automatic inversion |
| range | [value, min, max, step]; provide all four values, with omitted values and clamping governed by the native range input |
| mounted | Deferred node-mount callback, not a synchronous construction callback |
| onClick / onSwitch / onRange / onChange / onSelect | Distinct interaction callbacks described below |
Falsy html/icon/tooltip during initial rendering use empty content or the default icon. Subsequent reads of content accessors return innerHTML strings. Assigning an HTMLElement moves it; strings are parsed as HTML and numbers become text. Do not insert untrusted content directly.
Each item has one interaction kind, selected by property presence: onClick takes precedence over range, then switch, then selector. This tests whether the property exists, not whether its value is truthy. When changing kinds, do not merely add a new field while retaining conflicting fields: replace with an independently named item or explicitly handle obsolete fields on the original object. Ordinary component disable/index/click/beforeUnmount are not setting availability, ordering or lifecycle APIs.
| Callback | Arguments and result |
| --- | --- |
| mounted | (itemElement, item), with the player as this; scheduled with zero delay after insertion. Removal/replacement/destruction cancels pending work. The result is not UI content; Promise rejections produce a warning |
| onClick | (item, itemElement, event); result becomes that item's tooltip |
| onSwitch | Same arguments; result becomes switch. Without a callback, clicking does not toggle it |
| onChange | Native input event; when installed, writes the input value into item.range[0] before the callback, then writes its result into tooltip |
| onRange | Native change event; same update rules, for committed changes |
| onSelect | Defined on the parent; receives the clicked leaf, its node and event. Selection/navigation to the parent list happens first, then the result becomes the parent tooltip |
All interaction callbacks use the player as this and support Promise results. Only the latest operation for that target writes back while its items remain active. Results after removal, replacement or destruction are ignored. An omitted return does not preserve the old tooltip automatically: return the desired content or switch state. Errors warn without undoing an earlier selection or range[0] update. Mutating one range-array element does not synchronize the native input; assigning a complete new item.range array updates its properties. Without the corresponding onChange/onRange callback, native input changes do not synchronize range[0] through that callback path.
## Manager methods and node ownership {#setting-manager}
- find(name) returns the original item or null. add(item, option?) returns the supplied item; it appends to the root by default, or to an existing selector array passed as the second argument. Items gain names and accessors: do not freeze them or share them between two active players. Duplicate names, repeated object identity and cycles in one tree are rejected.
- update(item) shallow-updates the existing named item and returns that original object; missing names call add. Rendered entries rebuild their nodes and release old listeners/subpanels, then return to the root list. Ordinary synchronous update failures attempt to restore the original item, nodes, listeners and navigation before rethrowing; this is not a transaction covering caller side effects.
- remove(name) removes the item and descendants, cleans their listeners/subpanels, renders the root list and returns undefined. A missing name throws. Old DOM references no longer represent current nodes after removal/update; application-created resources remain application-owned.
- show/toggle change visibility and emit setting. Setting show = true does not create a missing settings button or format an initially disabled panel; normally start with setting: true. resize() recomputes constrained dimensions when the panel is visible and has an active list and settings button.
- traverse(callback, option?) visits actual objects in parent-before-child order, defaulting to the root. check(item) updates a formatted child's parent tooltip and default flags throughout the containing list and its descendants, then returns to the parent's containing list. No argument or a root item is a no-op.
- render(option?) displays/caches a formatted list, defaulting to the root. format(option?, parent?, parents?, names?) validates/binds the tree and assigns the supplied list as the manager's option; it is not read-only validation. Usually let add/update manage this process.
- createHeader/createItem are lower-level renderers requiring formatted items and cached panels. inactivate releases an item's subtree resources and subpanels without removing the item from its array. Use remove for complete removal rather than composing these internal steps.
Manager art references the player, name is setting, $parent is template.$setting and id generates names. option is the root array: construction combines built-ins and settings into a new array while retaining item identities. active is the current list or null; cache maps array identity to panel nodes. Each builtin read creates fresh entries from current options, not references to registered items. Use find for active entries and avoid mutating cache/active to bypass rendering.
| Item metadata | Meaning |
| --- | --- |
| $parent | Direct parent item; undefined at root |
| $parents | List containing the direct parent, not the full ancestor chain; undefined at root |
| $option | Array containing this item |
| $events / $formatted | Managed DOM cleanup array / established tree binding, not a currently-mounted flag |
| $item / $icon / $html / $tooltip | Rendered item/content nodes; unopened submenus may not have these yet |
| $switch / $range | Kind-specific switch node or native range input; do not retain obsolete references across updates |
Tree metadata uses readonly nonenumerable accessors. DOM accessors are installed during rendering; retaining an item after removal does not mean its nodes are connected. Root declarations preserve historical inaccuracies: missing find results are typed undefined, add/update/remove are typed as returning the manager, and updateStyle(width?) does not actually exist. Use resize() and avoid chaining those methods based on old signatures. Root Setting describes an item, while the unpublished refactor's artplayer/runtime exports the Setting manager and generic SettingItem; these are different types.
## TypeScript accurate-type example
```ts
import Artplayer from 'artplayer/runtime';
import type { SettingItem } from 'artplayer/runtime';
const art = new Artplayer({ container: '#player', url: '/video.mp4', setting: true });
const option: SettingItem<typeof art> = {
name: 'speed-label', html: 'Label', tooltip: 'Before',
async onClick(item) { return 'After: ' + item.html; },
};
const added: SettingItem<typeof art> = art.setting.add(option);
const found: SettingItem<typeof art> | null = art.setting.find('speed-label');
if (found) art.setting.update({ name: found.name, html: 'New label' });
console.log(added === option);
art.setting.remove('speed-label');
```
===== packages/artplayer-vitepress/docs/en/index.md =====
# Installation and Usage
## Installation
::: code-group
```bash [npm]
npm install artplayer
```
```bash [yarn]
yarn add artplayer
```
```bash [pnpm]
pnpm add artplayer
```
```bash [bun]
bun add artplayer
```
```html [script]
<script src="path/to/artplayer.js"></script>
```
:::
## `CDN`
::: code-group
```bash [jsdelivr.net]
https://cdn.jsdelivr.net/npm/artplayer/dist/artplayer.js
```
```bash [unpkg.com]
https://unpkg.com/artplayer/dist/artplayer.js
```
:::
## Usage
::: code-group
```html [index.html]
<html>
<head>
<title>ArtPlayer Demo</title>
<meta charset="UTF-8" />
<style>
.artplayer-app {
width: 400px;
height: 300px;
}
</style>
</head>
<body>
<div class="artplayer-app"></div>
<script src="path/to/artplayer.js"></script>
<script>
const art = new Artplayer({
container: '.artplayer-app',
url: 'path/to/video.mp4',
});
</script>
</body>
</html>
```
:::
::: warning Note
The player's dimensions depend on the dimensions of its `container`. Therefore, your `container` must have defined dimensions.
:::
::: tip See more usage examples at the following link
[/example](https://github.com/zhw2590582/ArtPlayer/tree/master/example)
:::
## `Vue.js`
::: code-group
```vue [Artplayer.vue]
<template>
<div ref="$container" />
</template>
<script setup>
import Artplayer from 'artplayer'
import { onBeforeUnmount, onMounted, ref, shallowRef } from 'vue'
const props = defineProps({
option: {
type: Object,
required: true,
},
})
const emit = defineEmits(['getInstance'])
const art = shallowRef(null)
const $container = ref(null)
onMounted(() => {
art.value = new Artplayer({
...props.option,
container: $container.value,
})
emit('getInstance', art.value)
})
onBeforeUnmount(() => {
art.value.destroy(false)
})
</script>
```
```vue [app.vue]
<template>
<Artplayer :option="option" :style="style" @get-instance="getInstance" />
</template>
<script setup>
import { reactive } from 'vue'
import Artplayer from './Artplayer.vue'
const option = reactive({
url: 'path/to/video.mp4',
})
const style = reactive({
width: '600px',
height: '400px',
margin: '60px auto 0',
})
function getInstance(art) {
console.log(art)
}
</script>
```
:::
::: warning Artplayer is not reactive:
Directly modifying the `option` in `Vue.js` will not update the player.
:::
## `React.js`
::: code-group
```jsx [Artplayer.jsx]
import Artplayer from 'artplayer'
import { useEffect, useRef } from 'react'
export default function Player({ option, getInstance, ...rest }) {
const $container = useRef()
useEffect(() => {
const art = new Artplayer({
...option,
container: $container.current,
})
if (typeof getInstance === 'function') {
getInstance(art)
}
return () => art.destroy(false)
}, [])
return <div ref={$container} {...rest}></div>
}
```
```jsx [app.jsx]
import Artplayer from './Artplayer.jsx'
function App() {
return (
<div>
<Artplayer
option={{
url: 'path/to/video.mp4',
}}
style={{
width: '600px',
height: '400px',
margin: '60px auto 0',
}}
getInstance={art => console.log(art)}
/>
</div>
)
}
export default App
```
:::
::: warning Artplayer is not reactive:
Directly modifying the `option` in `React.js` will not update the player.
:::
## TypeScript
The `artplayer.d.ts` file is automatically imported when you import `Artplayer`.
### Vue.js
```vue{3}
<script setup>
import Artplayer from 'artplayer';
const art = shallowRef<Artplayer>(null);
art.value = new Artplayer();
</script>
```
### React.js
```jsx{2}
import Artplayer from 'artplayer';
const art = useRef<Artplayer>(null);
art.current = new Artplayer();
```
### Option
You can also use the type for the options.
```ts{3}
import Artplayer, { type Option } from 'artplayer';
const option: Option = {
container: '.artplayer-app',
url: './assets/sample/video.mp4',
};
option.volume = 0.5;
const art = new Artplayer(option);
```
::: tip Full TypeScript Definitions
[packages/artplayer/types](https://github.com/zhw2590582/ArtPlayer/tree/master/packages/artplayer/types)
:::
## JavaScript
Sometimes your `js` files may lose `TypeScript` type hints. In such cases, you can manually import the types.
Variable:
```js{1-3}
/**
* @type {import("artplayer")}
*/
let art = null;
```
Parameter:
```js{1-3}
/**
* @param {import("artplayer")} art
*/
function getInstance(art) {
//
}
```
Property:
```js{4-6}
export default {
data() {
return {
/**
* @type {import("artplayer")}
*/
art: null,
}
}
}
```
Option:
```js{1-3}
/**
* @type {import("artplayer/types/option").Option}
*/
const option = {
container: '.artplayer-app',
url: './assets/sample/video.mp4',
};
option.volume = 0.5;
const art8 = new Artplayer(option);
```
## Legacy Browsers
The production build `artplayer.js` only supports the latest major version of `Chrome`: `last 1 Chrome version`.
For legacy browsers, you can use the `artplayer.legacy.js` file, which is compatible down to: `IE 11`.
```js
import Artplayer from 'artplayer/legacy'
```
::: code-group
```bash [jsdelivr.net]
https://cdn.jsdelivr.net/npm/artplayer/dist/artplayer.legacy.js
```
```bash [unpkg.com]
https://unpkg.com/artplayer/dist/artplayer.legacy.js
```
:::
::: tip If you need to support even older browsers, modify the following configuration and build it yourself:
Build configuration: [scripts/build.js](https://github.com/zhw2590582/ArtPlayer/blob/master/scripts/build.js#L29)
Reference documentation: [browserslist](https://github.com/browserslist/browserslist#full-list)
:::
## ECMAScript Module
::: tip ESM Demo:
[https://artplayer.org/esm.html](https://artplayer.org/esm.html)
:::
Starting from version `5.2.6`, `artplayer` and all plugins also provide an `ESM` version in `mjs` format, such as:
- `artplayer/dist/artplayer.mjs`
- `artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.mjs`
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ArtPlayer ESM with Import Map</title>
<style>
#player {
width: 640px;
height: 360px;
margin: 50px auto;
border: 1px solid #ccc;
}
</style>
<script type="importmap">
{
"imports": {
"artplayer": "https://unpkg.com/artplayer/dist/artplayer.esm.js"
}
}
</script>
</head>
<body>
<div id="player"></div>
<script type="module">
import Artplayer from 'artplayer';
const art = new Artplayer({
container: '#player',
url: '/assets/sample/video.mp4',
});
</script>
</body>
</html>
```
## Custom userAgent
Currently, the detection of whether a device is mobile is not always accurate. Sometimes you may want to adjust the player's UI by changing the `userAgent`. Therefore, starting from version `5.2.4`, a global variable `globalThis.CUSTOM_USER_AGENT` has been added.
```html
<html>
<head>
<title>ArtPlayer Demo</title>
<meta charset="UTF-8" />
<style>
.artplayer-app {
width: 400px;
height: 300px;
}
</style>
</head>
<body>
<div class="artplayer-app"></div>
<script>globalThis.CUSTOM_USER_AGENT = 'iphone'</script>
<script src="path/to/artplayer.js"></script>
<script>
const art = new Artplayer({
container: '.artplayer-app',
url: 'path/to/video.mp4',
});
</script>
</body>
</html>
```
::: warning Note
You need to modify it before importing the `Artplayer` dependency for it to take effect.
:::
===== packages/artplayer-vitepress/docs/en/plugin/ads.md =====
# Video and HTML Ads
[简体中文](../../plugin/ads.md)
Show one preroll when content first plays, using a separate video or HTML. The plugin supplies countdown, close, details, mute and fullscreen controls without IMA. Ad-tag requests belong to the separate [VAST plugin](./vast.md). This page describes the unreleased refactor branch; online examples and unpinned packages are not the current candidate.
## Installation and example
```sh
yarn add artplayer artplayer-plugin-ads
```
```js
import Artplayer from 'artplayer';
import artplayerPluginAds from 'artplayer-plugin-ads';
```
For script usage, load ArtPlayer before `dist/artplayer-plugin-ads.js`; the global is `artplayerPluginAds`. The [original online example](https://artplayer.org/?libs=./uncompiled/artplayer-plugin-ads/index.js&example=ads) below supplies both video and HTML, so the video takes precedence.
<div className="run-code" data-libs="./uncompiled/artplayer-plugin-ads/index.js">▶ Run Code</div>
```js
// npm i artplayer-plugin-ads
// import artplayerPluginAds from 'artplayer-plugin-ads';
const art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
autoSize: true,
fullscreen: true,
fullscreenWeb: true,
plugins: [
artplayerPluginAds({
// html广告,假如是视频广告则忽略该值
html: '<img src="/assets/sample/poster.jpg">',
// 视频广告的地址
video: '/assets/sample/test1.mp4',
// 广告跳转网址,为空则不跳转
url: 'http://artplayer.org',
// 必须观看的时长,期间不能被跳过,单位为秒
// 当该值大于或等于totalDuration时,不能提前关闭广告
// 当该值等于或小于0时,则随时都可以关闭广告
playDuration: 5,
// 广告总时长,单位为秒
totalDuration: 10,
// 多语言支持
i18n: {
close: '关闭广告',
countdown: '%s秒',
detail: '查看详情',
canBeClosed: '%s秒后可关闭广告',
},
}),
],
})
// 广告被点击
art.on('artplayerPluginAds:click', (ads) => {
console.info('广告被点击', ads)
})
// 广告被跳过
art.on('artplayerPluginAds:skip', (ads) => {
console.info('广告被跳过', ads)
})
```
## Options
Options may be omitted or supplied as `{}` to use defaults.
| Field | Type | Default | Meaning |
| --- | --- | --- | --- |
| `html` | `string` | `''` | Ad HTML, including images; not sanitized automatically, so supply trusted content |
| `video` | `string` | `''` | Ad video URL; a nonempty value overrides html |
| `url` | `string` | `''` | Destination opened from ad content or details; empty disables navigation and hides details |
| `playDuration` | `number` | `5` | Countdown seconds before the close button is enabled; does not restrict programmatic skip |
| `totalDuration` | `number` | `10` | Total countdown seconds, independent of the video duration |
| `muted` | `boolean` | `false` | Initial ad-video mute state |
| `i18n` | `Translations` | Below | Replaces the entire translations object; all four fields are required |
| Translation | Default |
| --- | --- |
| `close` | `'关闭广告'` |
| `countdown` | `'%s秒'` |
| `detail` | `'查看详情'` |
| `canBeClosed` | `'%s秒后可关闭广告'` |
`%s` is replaced with the time value. Options are shallowly merged; partial i18n objects are not supported. Use numeric seconds: string durations are rejected without coercion. Historical workspace source/type fields are not runtime aliases for video/html; supply images through an img in html.
Positive integer durations are recommended. With `playDuration <= 0`, the button allows immediate closure; with `playDuration >= totalDuration`, it is hidden. Counting advances once per timer execution and pauses while the document is hidden. It is not an exact measure of video currentTime or real elapsed time. The ad video loops; countdown completion or skip ends the ad.
## Methods and events
Registration is synchronous. The result is `art.plugins.artplayerPluginAds`, with the fixed name `artplayerPluginAds`.
| Method | Runtime behavior |
| --- | --- |
| `pause()` | Pause only the countdown, leaving ad video playback unchanged |
| `play()` | Resume only the countdown without adding duplicate timer chains |
| `skip()` | Finish once, regardless of the button's playDuration restriction |
All return `undefined` synchronously. Before initialization, play/pause do not start an ad. Early skip cancels the pending preroll and emits once without creating DOM or starting content. Repeated skip after completion is inert.
| Player event | Payload and timing |
| --- | --- |
| `artplayerPluginAds:click` | The normalized options when ad content or available details are clicked; a nonempty url is opened first |
| `artplayerPluginAds:skip` | The same normalized options on completion, including countdown expiry, programmatic skip or media failure; not solely a user-click signal |
Event options remain live, not readonly snapshots. Listener mutations affect later reads, such as totalDuration. Opening the details destination remains subject to browser window policies.
## Lifecycle and media
Install through construction options. After ready, the first play or video:playing signal creates the overlay and pauses content. Late installation does not replay an earlier ready event. Video ads wait for their own metadata before counting and requesting playback; HTML ads start counting immediately.
Ad media loading or playback failure completes the ad, and rejected internal play requests warn. Normal completion requests content playback, pauses the ad, hides the overlay and synchronously emits skip. A playback request does not guarantee the browser has started playing; direct application `art.play()` rejection behavior remains unchanged.
The hidden overlay stays until player destruction. Destroy releases the ad source, listeners, timer and overlay, removing its own `art.template.$ads` even if player HTML is retained. There is no separate public destroy, reset or replay-ad API. Existing `artplayer-plugin-ads*` classes remain; the fullscreen control uses core fullscreen.
The separate ad video must be loadable and decodable by the browser. Main-player SDKs and proxies do not automatically handle it. Native fullscreen, mobile playback policies and media behavior require target-environment validation; page navigation is not that evidence.
## TypeScript compatibility
The root and `/legacy` retain acceptance of the old erroneous `totalDuration: string` declaration, but runtime still rejects strings. The approved inference correction makes `Parameters<typeof ads>[0].totalDuration` read as `number | string | undefined`; historical source/type also become optional. Narrow those values or migrate to the accurate Option type.
New code can select `/runtime`, which uses the same implementation:
```ts
import ads from 'artplayer-plugin-ads/runtime';
import type { Option, Result } from 'artplayer-plugin-ads';
const options: Option = { video: '/advertisement.mp4', totalDuration: 10 };
const installAds = ads(options);
function pauseCountdown(plugin: Result): void {
plugin.pause();
}
```
Public types include Translations, Option, LegacyOption, WorkspaceOption, CompatOption, Result, Callable, Factory, RuntimeCallable and RuntimeFactory. Historical option types do not add runtime aliases. CommonJS supports the function and `.default(...)`; ESM uses the default export. The precise entry is not a second plugin implementation.
===== packages/artplayer-vitepress/docs/en/plugin/ambilight.md =====
# Video ambilight
[简体中文](../../plugin/ambilight.md)
Sample colors from the video to display a blurred glow around the player. The plugin reads pixels with Canvas, needs no additional SDK, and does not change video playback or audio output.
This page describes the refactor branch. Its lifecycle fixes and accurate type entry are not published yet. The online example and unpinned npm/CDN packages are not the current candidate.
## Installation and example
```sh
yarn add artplayer artplayer-plugin-ambilight
```
```js
import Artplayer from 'artplayer';
import artplayerPluginAmbilight from 'artplayer-plugin-ambilight';
```
For script usage, load ArtPlayer before `dist/artplayer-plugin-ambilight.js`. The global is `artplayerPluginAmbilight`. The following code is unchanged from the [online ambilight example](https://artplayer.org/?libs=./uncompiled/artplayer-plugin-ambilight/index.js&example=ambilight). Your application must provide its own container and accessible video.
<div className="run-code" data-libs="./uncompiled/artplayer-plugin-ambilight/index.js">▶ Run Code</div>
```js
// npm i artplayer-plugin-ambilight
// import artplayerPluginAmbilight from 'artplayer-plugin-ambilight';
const art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
autoSize: true,
plugins: [
artplayerPluginAmbilight({
blur: '50px',
opacity: 1,
frequency: 10,
duration: 0.3,
}),
],
})
```
## Options
| Field | Type | Default | Meaning |
| --- | --- | --- | --- |
| `blur` | `string` | `'50px'` | CSS blur radius |
| `opacity` | `number` | `0.5` | Glow grid opacity; the example explicitly uses `1` |
| `frequency` | `number` | `10` | Maximum samples per second; use a positive value; playback and frame scheduling limit the actual rate |
| `duration` | `number` | `0.3` | Color transition duration in seconds |
| `zIndex` | `number` | Actual value fixed at `9` | Historical accepted input that does not change the grid's stacking order |
The plugin samples a 3×3 grid. Colors update only while the player is playing and the sampling interval has elapsed. `frequency` does not set the video's frame rate. Masks, clipping and an ancestor's `overflow` styles can also affect how much of the glow is visible.
## start and stop
The result has the fixed `name` of `artplayerPluginAmbilight`. When installed through the constructor options, it starts scheduling after the player's `ready` event:
```js
const light = art.plugins.artplayerPluginAmbilight;
light.stop(); // Stop sampling and retain the last colors.
light.start(); // Resume sampling while the player is playing.
```
Both methods return `undefined` synchronously and do not play or pause the video. `stop()` retains the last colors. `start()` is not a Promise that captures a frame immediately. Repeated calls do not create multiple sampling loops. If you install the plugin with `art.plugins.add(...)` after `ready` has already fired, call `start()` on the returned result yourself.
There is no plugin `update` or separate `destroy` method. Destroying the player stops frame scheduling, removes the grid and plugin subscriptions, and releases the sampling canvas. Retained `start/stop` methods do no work after destruction.
## Media access and proxies
The browser must be able to decode the video and read its pixels into Canvas. Cross-origin video needs the appropriate media CORS configuration and server response headers; successful playback alone does not grant pixel access. A failed pixel read skips that update and retains existing colors. A later readable source can recover. The plugin does not bypass browser origin restrictions.
Canvas proxy sampling uses the actual dimensions of its output canvas. Other proxies must be checked for a drawable output; a passing desktop combination does not establish support for every browser or proxy. The effect does not control a physical monitor's backlight.
## TypeScript compatibility entries
The root and `/legacy` entries retain the published 1.1.0 factory type: the options object is required, while its fields are optional. Pass `{}` for defaults with those types. JavaScript calls can still omit the argument.
For accurate optional calls, the CommonJS `.default` self-alias, or callable NodeNext ESM default types, use `/runtime`, which shares the same implementation:
```ts
import ambilight from 'artplayer-plugin-ambilight/runtime';
const installLight = ambilight();
const installDefaultLight = ambilight.default({ opacity: 0.5 });
```
The root declarations export the named types `Option`, `Result`, `Callable`, `Factory` and `RuntimeFactory`. Version 1.0.0 used `export =` and required option fields, unlike 1.1.0. Earlier `import = require` callers can move to `/runtime`; reads of optional fields need a fallback. Node10 TypeScript default imports of `/runtime` require `esModuleInterop`; `import = require` works without it.
===== packages/artplayer-vitepress/docs/en/plugin/asr.md =====
# Audio Capture and Recognition Subtitles
[简体中文](../../plugin/asr.md)
Capture audio from the player's video, pass PCM/WAV chunks to your recognition callback, and display the returned subtitles. The plugin includes no recognition model or network service and does not access the microphone. This page describes the unreleased refactor branch; the new type entry and capture option are not claims about the live release.
## Installation and local example
```sh
yarn add artplayer artplayer-plugin-asr
```
```js
import Artplayer from 'artplayer';
import artplayerPluginAsr from 'artplayer-plugin-asr';
```
For script usage, load ArtPlayer before `dist/artplayer-plugin-asr.js`. The global is `artplayerPluginAsr`. This preserves the original `asr.local` example: it captures the site's sample video and displays statistics. Subtitles are simulated, audio is not uploaded, and the example does not measure recognition accuracy. Integrate your own recognition service inside the callback.
<div className="run-code" data-libs="./uncompiled/artplayer-plugin-asr/index.js">▶ Run Code</div>
```js
/* global Artplayer, artplayerPluginAsr */
// Local audio capture demo. The subtitles below are simulated, not recognized speech.
// No audio is uploaded; only the sample media is loaded from this local site.
const statistics = document.createElement('div')
statistics.textContent = 'Local ASR demo: press play. No recognition service is used.'
let chunks = 0
let pcmBytes = 0
let wavBytes = 0
const art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/steve-jobs.mp4',
autoSize: true,
fullscreen: true,
fullscreenWeb: true,
layers: [{
name: 'asr-local-statistics',
html: statistics,
style: {
position: 'absolute',
top: '12px',
left: '12px',
right: '12px',
padding: '8px 12px',
background: 'rgba(0, 0, 0, 0.65)',
color: '#fff',
fontSize: '12px',
whiteSpace: 'pre-line',
pointerEvents: 'none',
},
}],
controls: [{
name: 'asr-local-stop',
position: 'right',
html: 'Stop ASR',
tooltip: 'Stop capture; pause and play to restart',
async click() {
await art.plugins.artplayerPluginAsr.stop()
if (!art.isDestroy)
statistics.textContent = 'Local capture stopped. Pause and play to restart. Nothing was uploaded.'
},
}],
plugins: [artplayerPluginAsr({
length: 2,
interval: 250,
sampleRate: 16000,
autoHideTimeout: 5000,
onAudioChunk({ pcm, wav }) {
if (art.isDestroy)
return
chunks++
pcmBytes += pcm.byteLength
wavBytes += wav.byteLength
const sampleRate = new DataView(wav).getUint32(24, true)
const samples = new DataView(pcm)
let peak = 0
for (let offset = 0; offset < pcm.byteLength; offset += 2)
peak = Math.max(peak, Math.abs(samples.getInt16(offset, true)))
const duration = (pcmBytes / 2 / sampleRate).toFixed(2)
statistics.textContent = [
'Local capture only - simulated subtitles, no speech recognition',
`Chunks: ${chunks} | ${sampleRate} Hz mono PCM16 | ${duration} seconds captured`,
`PCM: ${pcmBytes} bytes | WAV: ${wavBytes} bytes | Current peak: ${peak}`,
].join('\n')
return `Simulated local subtitle: audio chunk ${chunks} received.`
},
})],
})
```
## Options and audio chunks
| Field | Default | Meaning |
| --- | --- | --- |
| `length` | `3` | Number of final nonempty punctuation-separated segments displayed from the current subtitle, not seconds of audio |
| `interval` | `100` | Consumption timer interval and target audio duration per chunk, in milliseconds |
| `sampleRate` | `16000` | Requested sample rate in Hz; requires environment support |
| `autoHideTimeout` | `10000` | Delay before hiding an accepted subtitle, in milliseconds |
| `onAudioChunk` | Function returning `null` | Receives `{ pcm, wav }`; a returned string displays subtitles; Promises are supported |
| `audioInput` | Unset | Direct Web Audio by default; `{ type: 'capture' }` explicitly selects a captured stream |
Both `pcm` and `wav` are `ArrayBuffer` values. Audio comes from the first channel. PCM is signed 16-bit little-endian; WAV adds a 44-byte mono header. A chunk contains `Math.floor(sampleRate * interval / 1000)` samples, requiring at least one sample and a positive, finite interval. Audio availability, browser scheduling and recognition latency affect callback frequency; exact timing is not guaranteed.
At most one recognition callback is pending within the active capture generation. Partial chunks stay queued. Rejected callbacks log an error and allow later processing. A backlog exceeding the larger of about one minute of audio or two chunks pauses capture and logs an error to bound memory. Pause, source changes, stop and destroy invalidate old results. They do not cancel network requests already sent by your callback; manage those resources in your application.
## Subtitles and lifecycle
Access the result through `art.plugins.artplayerPluginAsr`; its `name` is always `artplayerPluginAsr`:
| Method | Behavior |
| --- | --- |
| `append(text)` | Returns `undefined` synchronously; displays the final `length` segments of this text and resets the hide timer, replacing earlier subtitles rather than accumulating history |
| `hide()` | Returns `undefined` synchronously; hides subtitles without stopping capture or clearing their text |
| `stop()` | Actually returns `Promise<void>`; stops the current capture and discards old results; a later play event can restart it |
Both `append` and returned strings preserve historical HTML rendering and are not automatically escaped. Supply trusted subtitles; escape or sanitize external recognition text in your application. `null`, `undefined` and other non-string callback results do not update subtitles. `stop()` does not immediately hide existing subtitles; their original auto-hide deadline remains active.
Play starts capture; pause stops recording and discards obsolete work. Source changes rebuild the applicable capture state, and media errors use a nonterminal stop. The default direct connection retains the video's audio output route until `art.destroy()`, allowing playback after stop. Destroy releases subscriptions, timers, recording resources and audio contexts. There is no separate public plugin `start()` or `destroy()`.
## Audio routing and cross-origin media
The default route reads `art.video`, not the independent Audio Track plugin's audio. The video's own volume and mute affect captured data. Cross-origin media requires `moreVideoAttr: { crossOrigin: 'anonymous' }` before loading and appropriate server CORS headers. Playable media is not necessarily readable by Web Audio: without access, the clock can advance while output is silent, samples are zero or no chunks arrive. Cross-origin redirects are also restricted.
If your application already owns a Web Audio graph, explicitly choose `audioInput: { type: 'capture' }`. It uses `captureStream/mozCaptureStream`, does not take over the existing output, and never falls back to a direct connection on failure. It releases only its own contexts and captured tracks. Pause retains this graph; stop or source changes release it, and later playback obtains a fresh stream. Unsupported capability or initialization failure is logged and resources are cleaned up.
Captured data can remain nonzero despite video volume or mute and does not include an external effects graph or independent audio mix. CORS still applies: capture may be rejected, or a track may exist without readable audio. Desktop results do not establish Safari, mobile, every proxy or physical speaker behavior.
## TypeScript entries
The root preserves historical `AsrPluginOption`, `AsrPluginInstance` and `AudioChunk` types: callbacks return `void | Promise<void>`, and stop returns void. To type returned recognition strings, await stop or select captured input, use `/runtime`, which points to the same implementation:
```ts
import asr from 'artplayer-plugin-asr/runtime';
import type { RuntimeResult } from 'artplayer-plugin-asr/runtime';
const installAsr = asr({
audioInput: { type: 'capture' },
onAudioChunk({ pcm, wav }) {
console.log(pcm.byteLength, wav.byteLength);
return null; // Replace with your recognizer; null displays no subtitle.
},
});
async function stopAsr(plugin: RuntimeResult): Promise<void> {
await plugin.stop();
}
```
Precise types are `RuntimeOption`, `RuntimeResult` and `RuntimeFactory`, with `AudioChunk` also exported. Some NodeNext ESM consumers retain the old root namespace shape; choose `/runtime` for a callable default import. CommonJS runtime supports the function itself and its `.default` self-alias; `/runtime` also supports TypeScript `import = require`.
===== packages/artplayer-vitepress/docs/en/plugin/audio-track.md =====
# Audio Track
[中文](../../plugin/audio-track.md)
Play a separate audio file in sync with a video. The plugin creates an `HTMLAudioElement` and follows the main video's playback, pause, position, volume and playback rate. It requires no additional SDK and does not add an audio-selection menu.
This page describes the current refactor branch. Its lifecycle fixes and precise `/runtime` types are not yet published; an unpinned npm or CDN installation does not select this branch.
## Installation
```sh
yarn add artplayer artplayer-plugin-audio-track
```
```js
import Artplayer from 'artplayer';
import artplayerPluginAudioTrack from 'artplayer-plugin-audio-track';
```
For script tags, load ArtPlayer before the plugin's `dist/artplayer-plugin-audio-track.js`. The global is `artplayerPluginAudioTrack`. Pin dependency versions and supply an audio URL the browser can access and decode.
## Complete example
This is the same code as the [online audio example](https://artplayer.org/?libs=./uncompiled/artplayer-plugin-audio-track/index.js&example=audio.track). The demo site supplies the media files and `.artplayer-app` container; replace both when integrating it into your application.
<div className="run-code" data-libs="./uncompiled/artplayer-plugin-audio-track/index.js">▶ Run Code</div>
```js
// npm i artplayer-plugin-audio-track
// import artplayerPluginAudioTrack from 'artplayer-plugin-audio-track';
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/sprite-fight.mp4',
plugins: [
artplayerPluginAudioTrack({
url: '/assets/sample/sprite-fight.aac',
offset: 0,
sync: 0.3,
}),
],
});
```
## Options and synchronization
`artplayerPluginAudioTrack(option)` returns a plugin factory. The options object is required.
| Field | Type and default | Behavior |
| --- | --- | --- |
| `url` | Required `string` | Separate audio URL. An empty string at construction leaves the source unloaded. |
| `offset` | `number`, default `0` | Target audio time is video time plus this value, in seconds. A positive offset reads further ahead in the audio file. |
| `sync` | `number`, default `0.3` | Adjust audio time only when its absolute difference from the target is strictly greater than this threshold, in seconds. |
For example, video time 10 with offset 0.25 targets audio time 10.25. This corrects media-time drift; it is not a sample-accurate audio clock. Use finite, reasonable offsets and a nonnegative threshold. Negative targets or targets beyond the audio duration depend on browser media behavior; the plugin adds no delayed start, looping or silence padding.
Playback, completed seeking and playback recovery synchronize the track. Ordinary playing timeupdate events also correct drift. Buffering, source emptying, native pause, starting a seek and reaching the video's end pause the separate audio. It resumes when the main video is playing and ready to recover; canplay alone does not start audio for a paused video.
## Result and updates
After installation, access the result at `art.plugins.artplayerPluginAudioTrack`:
| Member | Behavior |
| --- | --- |
| `name` | Always `artplayerPluginAudioTrack`. |
| `audio` | The actual `HTMLAudioElement`; source updates preserve its identity. |
| `update(option)` | Synchronously updates selected fields and returns `undefined`, not a loading Promise. |
```js
const track = art.plugins.artplayerPluginAudioTrack;
track.update({ offset: 0.25, sync: 0.1 });
track.update({ url: '/audio/another-language.m4a' });
```
Changing offset or sync does not immediately force a seek; the next synchronization event uses the new values. Only a different, nonempty URL replaces the source. Reusing the same URL does not reload it, and an empty URL is not a stop or clear command. Replacing a source while the main video is playing attempts playback; observe the exposed audio element for actual loading, decoding and errors.
`art.switchUrl()` changes only the main video. Your application must keep video and audio sources paired and select the new audio with `track.update()`. Use native media events when you need to wait for readiness; `await track.update(...)` does not wait for loading.
## Volume, playback failure and destruction
The plugin does not remove the video's original sound. Use a video source without its own audio track if the separate track should provide the only sound. Player volume, mute and playback rate also apply to the separate audio. Setting `art.muted = true` mutes both; it cannot selectively mute only the main video.
Browser playback policies still apply. While the instance is active, a rejected `audio.play()` is reported through `console.warn`; it does not become a rejection from update. Successful main-video playback does not prove that the separate audio is audible. Applications can observe native playing/error events on the audio element.
Destroying ArtPlayer removes plugin subscriptions, pauses audio, removes its src attribute and releases media loading. A retained result still points to the same element, but later update calls no longer reload or play it. Your application remains responsible for listeners it adds to audio. There is no separate plugin destroy method to call.
## TypeScript
The root and `/legacy` entries preserve the old `Result.update(Option)` declaration, including its required URL, to retain parameter extraction and function-assignment behavior. Runtime updates already support partial options. Use `/runtime` for accurate partial-update types over the same implementation:
```ts
import Artplayer from 'artplayer';
import audioTrack from 'artplayer-plugin-audio-track/runtime';
const installTrack = audioTrack({ url: '/audio/dialogue.m4a' });
const art = new Artplayer({
container: '.artplayer-app',
url: '/video/silent.mp4',
plugins: [(player) => {
const track = installTrack(player);
track.update({ offset: 0.25 });
return track;
}],
});
```
Named types include `Option`, `UpdateOption`, the old `Result`, `RuntimeResult` and `RuntimeFactory`. The online editor's default global retains legacy inference too. To select precise update typing, explicitly use `artplayerPluginAudioTrack as artplayerPluginAudioTrack.RuntimeFactory`. This does not create a second plugin implementation.
Actual desktop tests cover audio/video playback, pause, seeking, updates and destruction. They do not establish support for every mobile device, proxy player, audio format or long-running synchronization combination. Check browser decoding capabilities and the project's validation records for the applicable scope.
Windows WebKit MP4/AAC starvation remains an open native-media validation issue: a stalled video does not reliably emit waiting in that reproduction. Passing source-switch ordering checks does not close that separate case.
===== packages/artplayer-vitepress/docs/en/plugin/auto-thumbnail.md =====
# Automatic Thumbnails
[简体中文](../../plugin/auto-thumbnail.md)
Read frames with a separate video element, progressively create a JPEG sprite sheet in the browser, and update the player's progress-bar thumbnails. This page describes the unreleased refactor branch; the online example and unpinned npm/CDN packages are not the current candidate.
## Installation and example
```sh
yarn add artplayer artplayer-plugin-auto-thumbnail
```
```js
import Artplayer from 'artplayer';
import artplayerPluginAutoThumbnail from 'artplayer-plugin-auto-thumbnail';
```
For script usage, load ArtPlayer before `dist/artplayer-plugin-auto-thumbnail.js`; the global is `artplayerPluginAutoThumbnail`. This preserves the [original online example](https://artplayer.org/?libs=./uncompiled/artplayer-plugin-auto-thumbnail/index.js&example=auto.thumbnail). The factory requires an options object; use `{}` for defaults.
<div className="run-code" data-libs="./uncompiled/artplayer-plugin-auto-thumbnail/index.js">▶ Run Code</div>
```js
// npm i artplayer-plugin-auto-thumbnail
// import artplayerPluginAutoThumbnail from 'artplayer-plugin-auto-thumbnail';
const art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
plugins: [
artplayerPluginAutoThumbnail({
//
}),
],
})
```
## Options and generation
| Field | Type | Default | Meaning |
| --- | --- | --- | --- |
| `url` | `string` | Current `art.option.url` | Media read by the separate decoder; an explicit nonempty URL takes precedence |
| `width` | `number` | `160` | Width of each thumbnail cell in pixels |
| `number` | `number` | `100` | Target sample count; normally use a positive integer |
| `scale` | `number` | `1` | Preview display scale passed to the player; does not reduce the generated canvas size |
| `height` | `number` | Derived from video aspect ratio | Historical ignored option accepted by the precise runtime type |
Defaults preserve `value || default` behavior: `width: 0`, for example, uses 160. After fallback, width and number must convert to finite positive values. Historical JavaScript numeric strings and fractional counts are retained, but typed examples use numbers and positive integer counts. Height is `Math.floor(width * videoHeight / videoWidth)`, with ten columns per row. Media needs finite positive duration and dimensions; infinite-duration live streams do not qualify.
Sampling uses `duration * index / number`, starting at index 0 and excluding the media endpoint. Duration and sheet dimensions are captured at the separate video's metadata event. Each drawn cell is followed by encoding the whole JPEG and publishing a new `art.thumbnails` configuration. Later cells can therefore remain empty while generation is in progress.
Larger width and count increase canvas allocation, decoding and repeated encoding cost. Dimension checks are not a browser memory budget. Choose values appropriate for the media and device, or use prebuilt [VTT thumbnails](./vtt-thumbnail.md).
## Registration, source changes and cleanup
The factory returns an asynchronous registrar whose result contains only `name: 'artplayerPluginAutoThumbnail'`. Its Promise resolves after installing player subscriptions, before extraction. It does not confirm that media was read successfully. There are no public progress, extraction-completion, update, stop or destroy methods.
Each `video:loadedmetadata` event starts extraction and rereads the original options object, so later mutations affect the next run. Installation does not replay metadata events that already occurred. `restart` cancels the old job; a subsequent metadata event starts another. Old frame or encoding callbacks cannot overwrite the newer job.
Metadata acquisition, each frame wait and each JPEG encoding operation have separate 30-second limits, not one total extraction deadline. Media, canvas and encoding failures clean up the current job, report through `console.warn` and retain the last usable preview. They cannot reject an already-resolved registration Promise.
Completion or cancellation releases the separate video and canvas. The final image URL remains until a usable replacement is installed or the player is destroyed. Only generated URLs are revoked; application-owned thumbnail URLs are not. Destroy also removes subscriptions. Directly invoking a retained registrar after player destruction returns the same name without allocating a decoder; it does not override core `plugins.add()` destruction checks.
## Media access and browser limits
The separate video uses `crossOrigin = 'anonymous'`, is muted, and is never explicitly played. Media must support native browser loading and Canvas pixel access, including appropriate server CORS headers. Player custom loaders, SDKs, request headers and proxies are not installed into this decoder; supply a directly readable media `url` when necessary.
The hidden video is attached to the document with a rendered box; it is not an additional visible player. Actual decoding and first-frame correctness remain browser-dependent. The current Windows WebKit first-frame issue is still unresolved. Types, navigation and other browser results do not close it or establish physical Safari/mobile support.
## Compatible TypeScript entries
The root and `/legacy` preserve npm 1.1.0 synchronous result declarations and required options; the root Option does not contain height. For the actual Promise, historical height input or `.default` self-alias types, use `/runtime`, which loads the same implementation:
```ts
import type Artplayer from 'artplayer';
import autoThumbnail from 'artplayer-plugin-auto-thumbnail/runtime';
import type { Option, Result } from 'artplayer-plugin-auto-thumbnail/runtime';
const options: Option = { width: 160, number: 100, scale: 1 };
async function registerThumbnails(art: Artplayer): Promise<Result> {
return await autoThumbnail(options)(art); // Registration only, not extraction completion.
}
```
Runtime exports `Option`, `Result`, `Factory` and `RuntimeFactory`. Factory is a plain asynchronous factory; RuntimeFactory adds the writable `.default` self-alias. CommonJS runtime supports direct and `.default(...)` calls. Earlier 1.0.x `export =` and height declarations differ from 1.1.0; use the runtime `import = require` form when migrating those imports. Root NodeNext namespace behavior remains unchanged; use runtime for an accurate callable default import. Classic Node10 default imports need `esModuleInterop`.
===== packages/artplayer-vitepress/docs/en/plugin/chapter.md =====
# Video chapters
[简体中文](../../plugin/chapter.md)
Divide the progress bar into chapters and display a title on hover while retaining the player's seeking and thumbnail controls. The plugin does not extract chapter metadata from the media file; your application supplies the times.
This page describes the refactor branch. Its candidates and fixes are not published yet. The online example and unpinned npm/CDN packages may use different code.
## Installation and example
```sh
yarn add artplayer artplayer-plugin-chapter
```
```js
import Artplayer from 'artplayer';
import artplayerPluginChapter from 'artplayer-plugin-chapter';
```
For script usage, load ArtPlayer before `dist/artplayer-plugin-chapter.js`. The plugin global is `artplayerPluginChapter`. The following code is unchanged from the [online chapter example](https://artplayer.org/?libs=./uncompiled/artplayer-plugin-chapter/index.js&example=chapter). The site provides its container, video and thumbnail image; replace those resources when integrating it into your application.
<div className="run-code" data-libs="./uncompiled/artplayer-plugin-chapter/index.js">▶ Run Code</div>
```js
// npm i artplayer-plugin-chapter
// import artplayerPluginChapter from 'artplayer-plugin-chapter';
const art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
autoSize: true,
fullscreen: true,
fullscreenWeb: true,
miniProgressBar: true,
autoOrientation: true,
thumbnails: {
url: '/assets/sample/thumbnails.png',
number: 60,
column: 10,
},
plugins: [
artplayerPluginChapter({
chapters: [
{ start: 0, end: 18, title: 'One more chance' },
{ start: 18, end: 36, title: '谁でもいいはずなのに' },
{ start: 36, end: 54, title: '夏の想い出がまわる' },
{ start: 54, end: 72, title: 'こんなとこにあるはずもないのに' },
{ start: 72, end: Infinity, title: '终わり' },
],
}),
],
})
```
## chapters
The factory accepts an optional options object containing `chapters`. Each entry has these fields:
| Field | Type | Meaning |
| --- | --- | --- |
| `start` | `number` | Start time in seconds; a finite, nonnegative number |
| `end` | `number` | End time in seconds; `Infinity` means the current video's end |
| `title` | `string` | Hover title; an empty string retains an untitled interval |
Every interval must satisfy `start < end <= video duration`, without overlapping the next chapter. The plugin sorts chapters by start time and fills uncovered intervals at the beginning, end and between chapters. It **mutates the supplied array** by sorting it, inserting untitled entries and replacing `Infinity` with the current duration. Pass a fresh array and fresh entry objects whenever you need to preserve the original configuration.
Chapters are created only when the media duration is finite and positive. Missing, empty or non-array input clears the view; TypeScript still accepts only the declared array type. Invalid field types throw `TypeError`; invalid times and overlapping intervals throw `Error`. Titles are plain text, not HTML. Displayed titles are trimmed without changing the original object's `title`.
## update
The result has the fixed `name` of `artplayerPluginChapter`. `update(option)` replaces the chapters synchronously and returns `undefined`. Its options object is required; `update({})` clears the chapters:
```js
art.plugins.artplayerPluginChapter.update({
chapters: [{ start: 0, end: Infinity, title: 'Introduction' }],
});
// Clear all chapter segments and the hover title.
art.plugins.artplayerPluginChapter.update({});
```
An update clears the previous view before validating the replacement. If validation throws, the previous chapters are not retained. A successful update synchronously emits the existing `setBar('loaded', ...)` event; progress interactions continue to use the core controls.
The initial configuration is applied only on the first `video:loadedmetadata`. Switching media does not calculate new chapters automatically. After the new media loads, call `update` with fresh data for its duration. Do not reuse an object whose `Infinity` end was already replaced by the previous duration.
## Lifecycle and styling
Destroying the player removes this plugin's listeners, chapter nodes, title and `artplayer-plugin-chapter` class, including when `art.destroy(false)` retains the player HTML. Calling `update` on a retained result after destruction does not recreate the view. There is no separate plugin `destroy()` method.
The existing `.art-chapter`, `.art-chapter-title` and chapter `data-start/end/duration/title` hooks remain. Long titles are clipped to the progress bar width, while their full text remains in the text content and data attributes. The stylesheet is shared by the page and is retained when one player is destroyed.
## TypeScript
The root and `/legacy` entries share the public API and export the `Chapters`, `Option` and `Result` types. This package does not require a separate `/runtime` entry:
```ts
import artplayerPluginChapter from 'artplayer-plugin-chapter';
import type { Chapters } from 'artplayer-plugin-chapter';
const chapters: Chapters = [{ start: 0, end: Infinity, title: 'Introduction' }];
const installChapters = artplayerPluginChapter({ chapters });
```
Desktop tests cover chapters with quality selection, thumbnails and fullscreen. They do not establish support on every mobile device. The current Windows WebKit quality-switch tests still encounter stalls while reading browser state, so this page does not promise timing reliability on every platform.
===== packages/artplayer-vitepress/docs/en/plugin/chromecast.md =====
# Chromecast
[简体中文](../../plugin/chromecast.md)
Add a right-side Cast control that selects a Cast session and loads media. Use a supported Chrome sender, HTTPS, a real receiver and receiver-accessible media. This page describes the unreleased branch. A local page, mocked SDK or successful session does not prove playback on a television.
## Installation and example
```sh
yarn add artplayer artplayer-plugin-chromecast
```
```js
import Artplayer from 'artplayer';
import artplayerPluginChromecast from 'artplayer-plugin-chromecast';
```
For scripts, load ArtPlayer before `dist/artplayer-plugin-chromecast.js`; the global is `artplayerPluginChromecast`. This preserves the [original example](https://artplayer.org/?libs=./uncompiled/artplayer-plugin-chromecast/index.js&example=chromecast). Supply an absolute media URL reachable by your receiver; sender localhost, relative URLs and Blob URLs are not rewritten automatically.
<div className="run-code" data-libs="./uncompiled/artplayer-plugin-chromecast/index.js">▶ Run Code</div>
```js
// npm i artplayer-plugin-chromecast
// import artplayerPluginChromecast from 'artplayer-plugin-chromecast';
const art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
fullscreen: true,
fullscreenWeb: true,
plugins: [
artplayerPluginChromecast({
// sdk: '', // The URL of the Cast SDK
// mimeType: '', // The MIME type of the media
}),
],
})
```
## Options and callbacks
An options object is required; `{}` uses defaults. The icon is captured at registration; other options and callbacks are read from the original object at the relevant operation.
| Field | Type | Behavior |
| --- | --- | --- |
| `url` | `string` | Nonempty override, otherwise current art.option.url |
| `sdk` | `string` | Override the Cast SDK script URL |
| `icon` | `string` | Trusted control HTML inside the retained art-icon/art-icon-cast wrapper |
| `mimeType` | `string` | Nonempty override, otherwise inferred from the URL extension |
| `onStateChange` | `(state) => void` | Normalized disconnected/connecting/connected/disconnecting state |
| `onCastAvailable` | `(available: boolean) => void` | SDK availability event, not playback success |
| `onCastStart` | `() => void` | Called after the current loadMedia completes |
| `onError` | `(error: unknown) => void` | SDK, connection or loading failure; not necessarily an Error instance |
Callback this is the original options object. The default SDK URL is `https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1`. Concurrent loads in the same module/window share the first pending URL; a later retry can read another URL.
MIME inference strips query/hash and lowercases the extension. mp4/webm/ogg/ogv/mp3/wav/flv/mov/avi/wmv/mpd/m3u8 map respectively to video/mp4, video/webm, video/ogg, video/ogg, audio/mp3, audio/wav, video/x-flv, video/quicktime, video/x-msvideo, video/x-ms-wmv, application/dash+xml and application/x-mpegURL. Unknown extensions use application/octet-stream. This mapping is not receiver codec support; provide mimeType explicitly when needed.
## Registration and session state
Registration returns a Promise but adds the `chromecast` control immediately. SDK loading starts on first click, so registration is not SDK readiness. The loader has a 30-second readiness limit; script load alone is insufficient without the Framework. It configures the default media receiver and ORIGIN_SCOPED auto-join policy, with no custom receiver-application option.
A click initializes the SDK and checks the current session. If absent, it requests a session, reads it again, then awaits loadMedia. Pending clicks for one controller share the operation. A successful session request without a current session is a connection error. The media URL is read when sending; later player source changes do not automatically cast again. Use the control to send the new media.
The result is at `art.plugins.artplayerPluginChromecast`:
| Member | Meaning |
| --- | --- |
| `name` | Always artplayerPluginChromecast |
| `getCastState()` | Last raw SDK SessionState, initially null; not the normalized callback state |
| `isCasting()` | Whether a session reference is retained, not proof of receiver playback |
The icon uses white for disconnected, orange for transitional and red for connected states. Session termination, failure or replacement invalidates pending work; late results cannot start stale media or show obsolete notices. SDK failures display a stage-specific notice and call onError. User callback exceptions can still propagate.
## Cleanup and shared sessions
Player destruction releases this controller's loader subscription, SDK listeners and pending operation. It does not interrupt another player waiting for the SDK or end the page-shared receiver session. Successful scripts remain; failed or last-owner-cancelled pending scripts are removed. The core owns control DOM removal.
There is no public start, stop, disconnect or destroy method, and no continuous synchronization of local pause, time or volume. Callbacks and icons describe observed controller state. Receiver playback, network reachability, source changes and disconnection require real hardware validation; local tests cannot substitute for it.
## TypeScript
Root and `/legacy` preserve npm1.1.0's required options and synchronous name-only declaration. For precise callbacks, Promise registration and state methods, use `/runtime`, with the same implementation:
```ts
import cast from 'artplayer-plugin-chromecast/runtime';
import type { RuntimeOption, RuntimeResult } from 'artplayer-plugin-chromecast/runtime';
const options: RuntimeOption = {
onStateChange(state) { console.log(state, this.url); },
onError(error) { console.error(error); },
};
const registerCast = cast(options);
function readSession(plugin: RuntimeResult): boolean {
return plugin.isCasting(); // Session presence, not receiver playback.
}
```
Root/runtime named types are Option, Chromecast, Result, Factory, ConnectionState, RuntimeOption, RuntimeResult and RuntimeFactory. Historical root NodeNext namespace behavior remains. Use runtime for accurate ESM default calls or older `import = require` syntax. JavaScript supports direct and `.default(...)` factory calls; type compatibility does not establish device capabilities.
===== packages/artplayer-vitepress/docs/en/plugin/danmuku-mask.md =====
# Danmuku Mask
[简体中文](../../plugin/danmuku-mask.md)
Generate a CSS mask from person segmentation so danmuku avoids people in the video. The plugin masks the core `.art-danmuku` layer without changing its queue, individual items or video pixels. This describes the unreleased branch; the online example and navigation checks are not model-quality acceptance.
## Installation and example
```sh
yarn add artplayer artplayer-plugin-danmuku artplayer-plugin-danmuku-mask
```
```js
import Artplayer from 'artplayer';
import artplayerPluginDanmuku from 'artplayer-plugin-danmuku';
import artplayerPluginDanmukuMask from 'artplayer-plugin-danmuku-mask';
```
For scripts, load the core and both plugin dist files. Their globals are `artplayerPluginDanmuku` and `artplayerPluginDanmukuMask`. The [original example](https://artplayer.org/?libs=./uncompiled/artplayer-plugin-danmuku/index.js%0A./uncompiled/artplayer-plugin-danmuku-mask/index.js&example=danmuku.mask) registers them in that order and loads MediaPipe assets from the site's own directory:
<div className="run-code" data-libs="./uncompiled/artplayer-plugin-danmuku/index.js&#10;./uncompiled/artplayer-plugin-danmuku-mask/index.js">▶ Run Code</div>
```js
// npm i artplayer-plugin-danmuku-mask
// import artplayerPluginDanmukuMask from 'artplayer-plugin-danmuku-mask';
// npm i @mediapipe/selfie_segmentation
// 把 node_modules/@mediapipe/selfie_segmentation 目录复制到你的项目下
const art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/steve-jobs.mp4',
autoSize: true,
fullscreen: true,
fullscreenWeb: true,
autoOrientation: true,
plugins: [
artplayerPluginDanmuku({
danmuku: '/assets/sample/danmuku.xml',
}),
artplayerPluginDanmukuMask({
solutionPath: '/assets/@mediapipe/selfie_segmentation',
}),
],
})
```
Host matching MediaPipe assets under solutionPath. Copying a directory alone does not establish script, model, WASM, network-policy or browser support.
## Options
Options may be omitted; values are captured at registration:
| Field | Type / default | Runtime use |
| --- | --- | --- |
| `solutionPath` | `string` | SDK asset root; default CDN URL is unversioned |
| `modelSelection` | `number` / `1` | Historical forwarded field, not proof that the current model changes |
| `smoothSegmentation` | `boolean` / `true` | Historical forwarded field; explicit false is retained |
| `minDetectionConfidence` | `number` / `0.5` | Historical forwarded field |
| `minTrackingConfidence` | `number` / `0.5` | Historical forwarded field |
| `selfieMode` | `boolean` / `false` | Historical forwarded field |
| `drawContour` | `boolean` / `false` | Passed to binary-mask conversion |
| `foregroundThreshold` | `number` / `0.5` | Passed to foreground threshold conversion |
| `opacity` | `number` / `1` | Passed to SDK drawMask, not directly applied as danmuku-layer opacity |
| `maskBlurAmount` | `number` / `3` | Blur argument passed to SDK drawMask |
The default solutionPath is the unversioned [MediaPipe asset root on jsDelivr](https://cdn.jsdelivr.net/npm/@mediapipe/selfie_segmentation).
Except for smoothSegmentation's undefined check, defaults retain `value || default`. Zero modelSelection, opacity, threshold or blur uses its default rather than disabling the effect. Use stop to disable masking.
The current selection is fixed to `runtime: 'mediapipe'` and `modelType: 'general'`. The installed adapter maps general to modelSelection 0 and ignores some extra forwarded fields. Their presence does not promise an inference change. TensorFlow first attempts webgl and tries cpu only on rejection; that does not establish MediaPipe's actual inference backend.
## Start, stop and failures
Registration synchronously returns `{ name: 'artplayerPluginDanmukuMask', start, stop }`, available at `art.plugins.artplayerPluginDanmukuMask`. The ready event starts it automatically; call start yourself if installed after ready.
| Method | Behavior |
| --- | --- |
| `start()` | Returns `Promise<void>` after initialization/scheduling, not after a complete first mask |
| `stop()` | Returns undefined synchronously; cancels scheduling and immediately sets layer maskImage to none |
Repeated start does not overlap inference loops. Stop during initialization settles public start promptly while uncancellable SDK work remains observed. Late results cannot update the mask. A subsequent start waits for old work and disposal before creating another model.
Model creation failure logs an error and can resolve start without a usable model; explicit start can retry. Backend or required DOM/Canvas initialization can still reject start. Automatic ready startup logs rejection. Inference/pixel errors log and continue scheduling while retaining the previous usable mask.
Inference runs only while video is playing, not ended, and has valid dimensions. Binary-mask conversion, drawMask and Canvas pixel processing produce a PNG data URL assigned to the whole layer. No-person results retain the preceding mask. Accuracy, performance and cross-origin pixel access need actual media checks.
## Lifecycle and types
Player destruction stops work and removes ready/destroy subscriptions. The plugin owns its private canvas; after SDK work settles it resets dimensions and disposes the model. Stop is not a GPU-release completion Promise. Private SDK disposal completion still needs separate evidence. There is no public destroy, update or new event, and no takeover of application-wide TensorFlow lifetime.
Root and `/legacy` retain optional options, synchronous registration, async start and sync stop. There is no `/runtime` subpath. Option/Result are private declaration types, extractable from the factory. This example is type-only extraction for NodeNext ESM; it does not call `.default` at runtime:
```ts
import type MaskModule from 'artplayer-plugin-danmuku-mask';
type MaskFactory = typeof MaskModule.default;
type MaskOptions = Parameters<MaskFactory>[0];
type MaskResult = ReturnType<ReturnType<MaskFactory>>;
const options: MaskOptions = { solutionPath: '/assets/@mediapipe/selfie_segmentation' };
async function restartMask(mask: MaskResult): Promise<void> {
mask.stop();
await mask.start();
}
```
The preserved NodeNext root declaration has namespace behavior; it does not give the current runtime factory a `.default` property. Current CommonJS calls the function directly, and ESM runtime uses its default export. Do not infer runtime aliases from the type namespace. Historical export forms, actual segmentation and device performance remain separate checks.
===== packages/artplayer-vitepress/docs/en/plugin/danmuku.md =====
# Danmuku
[中文说明](../../plugin/danmuku.md)
Display timed comments over the video, with an input panel, display settings and an optional heatmap.
This guide describes the current refactor branch. The `/runtime` entrypoint and refactor fixes have not yet been published to npm; unversioned CDN links still load the published release.
## Demo
[Open the full example](https://artplayer.org/?libs=./uncompiled/artplayer-plugin-danmuku/index.js&example=danmuku).
Run Code examples below use the site's local plugin build and sample media. In your application, use your own container and media URLs.
## Installation
::: code-group
```sh [npm]
npm install artplayer artplayer-plugin-danmuku
```
```sh [yarn]
yarn add artplayer artplayer-plugin-danmuku
```
```sh [pnpm]
pnpm add artplayer artplayer-plugin-danmuku
```
```html [script]
<script src="path/to/artplayer.js"></script>
<script src="path/to/artplayer-plugin-danmuku.js"></script>
```
:::
JavaScript projects can import the default factory from `artplayer-plugin-danmuku`.
Script builds expose `artplayerPluginDanmuku`; pass its result to the player's `plugins` array.
## CDN
::: code-group
```text [jsDelivr]
https://cdn.jsdelivr.net/npm/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.js
```
```text [unpkg]
https://unpkg.com/artplayer-plugin-danmuku/dist/artplayer-plugin-danmuku.js
```
:::
## Comment structure
Only `text` is required. Empty or whitespace-only comments are ignored. Direct `emit()` and loaded rows retain the original text, including surrounding spaces; only the input panel trims its submitted text.
```js
({
text: 'Hello!',
time: 10, // Seconds; omitted time defaults to currentTime + 0.5
mode: 0, // 0: scrolling, 1: top, 2: bottom; defaults to option.mode
color: '#FFFFFF', // Defaults to option.color
border: false,
style: {}, // CSS properties for this comment
});
```
Explicit `time: 0` is preserved. Negative times are clamped to zero. Other than 0, 1 and 2, comment modes are ignored.
Normalization fills time, mode, color and style on the supplied object before calling `filter`. The queue then receives a shallow copy; its style object is still shared. Copy reusable inputs when these mutations matter. The optional string `id` becomes the displayed element's `data-id`; it does not deduplicate comments. Text is rendered through `textContent`, not as HTML.
## All options
Pass an option object to the factory. At runtime, `{}` is valid and all fields have defaults.
The historical root declarations still require `danmuku`; existing TypeScript projects can keep providing it. See [TypeScript](#typescript) for accurate declarations.
```js
({
danmuku: [], // Array, XML URL, Promise of an array, or function returning an array/Promise
speed: 5, // Display duration in seconds, clamped to 1–10
margin: [10, '25%'], // Top/bottom spacing: pixels or percentages
opacity: 1, // Clamped to 0–1
color: '#FFFFFF', // Default comment color
mode: 0, // Default comment mode
modes: [0, 1, 2], // Visible modes
fontSize: 25, // Pixels or a percentage of player height
antiOverlap: true,
synchronousPlayback: false, // Follow video playbackRate when enabled
mount: undefined, // Defaults to the center of the player controls
heatmap: false, // Enable at construction: true or a heatmap options object
width: 512, // Below this width, the default input panel moves below the player
points: [], // Stored option; use the points event to draw custom data
filter: () => true, // Synchronous; do not return a Promise
beforeEmit: () => true, // Input-panel submissions only; may return a Promise
beforeVisible: () => true, // Called before display; may return a Promise
visible: true,
emitter: true, // Show the input panel's sending UI
maxLength: 200, // Input length, clamped to 1–1000
lockTime: 5, // Seconds between input-panel submissions, clamped to 1–60
theme: 'dark', // 'dark' or 'light' for an external mount
OPACITY: {},
FONT_SIZE: {},
MARGIN: {},
SPEED: {},
COLOR: [],
});
```
`OPACITY`, `FONT_SIZE`, `MARGIN` and `SPEED` override slider definitions with `min`, `max` and `steps`.
Each step can contain `name`, `value`, `hide` and `show`. `hide` suppresses its label; the retained `show` field is not read by the renderer. Margin values are pairs such as `[10, '50%']`.
`COLOR` replaces the palette with an array of CSS color strings; an empty array uses the built-in palette.
## Array, XML and asynchronous input
XML input uses Bilibili's comment format. Fetching a cross-origin XML URL requires that server to allow browser access.
An input function runs without the option object as its receiver. It may return an array directly or asynchronously.
<div className="run-code" data-libs="./uncompiled/artplayer-plugin-danmuku/index.js">
▶ Run Code
</div>
```js
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
plugins: [
artplayerPluginDanmuku({
danmuku: [{ text: 'Hello from an array', time: 1 }],
// Alternatives:
// danmuku: '/assets/sample/danmuku.xml',
// danmuku: Promise.resolve([{ text: 'From a Promise', time: 1 }]),
// danmuku: async () => [{ text: 'From a function', time: 1 }],
}),
],
});
```
## Lifecycle callbacks
Input-panel submissions run `beforeEmit → filter → beforeVisible → artplayerPluginDanmuku:visible`.
Loaded comments and direct `emit` calls run `filter → beforeVisible → artplayerPluginDanmuku:visible`.
This describes accepted comments; callbacks can reject them, and scheduling still requires playback and an available track.
| Callback | Input | Acceptance |
| --- | --- | --- |
| `beforeEmit` | Input-panel comment | Only strict `true`, or a Promise resolving to `true`, sends it |
| `filter` | Comment with time, mode, color and style filled in | A synchronous truthy result adds it to the queue |
| `beforeVisible` | Queue item | A truthy result, including an awaited result, permits display |
Normal functions receive the current option object as `this` for all three callbacks. Arrow functions retain their lexical `this`.
`emit()` does not call `beforeEmit`: perform application validation before calling it when needed.
`beforeEmit` failures are logged in the console and leave the input available for another attempt.
An asynchronous `beforeVisible` rejection emits `artplayerPluginDanmuku:error` once for that item in the current run; other items continue.
Pause/resume, reset or replacing the callback permits a retry if the item is still eligible by time.
Pausing, seeking, hiding, resetting or destroying cancels unfinished visibility preparation.
## Methods and state
The registered plugin is available synchronously as `art.plugins.artplayerPluginDanmuku`.
| Member | Behavior and return value |
| --- | --- |
| `emit(comment)` | Processes one comment for the queue; returns a Promise |
| `load()` | Reads `option.danmuku` and replaces the queue; returns a Promise |
| `load(input)` | Appends comments from the input; returns a Promise |
| `config(partialOption)` | Synchronously merges configuration |
| `hide()` / `show()` | Synchronously hides or shows the comment layer |
| `reset()` | Clears displayed comments and returns queue items to waiting; does not delete the queue |
| `mount(target)` | Moves the panel to an existing element or selector; returns `undefined` |
| `option` | Live current configuration; use `config()` for validated updates |
| `isHide` | Read-only live visibility state: `true` when hidden |
| `isStop` | Read-only live stopped state; distinct from visibility and not a media-readiness signal |
`emit/load` Promises resolve to the internal Danmuku owner. `config/hide/show/reset` return that same owner synchronously.
The owner is **different from the registered plugin facade**. Keep using the registered facade for subsequent commands.
Awaiting `emit()` means queue processing has finished, not that the comment has appeared.
### Loading and configuration
Changing `config({ danmuku: input })` does not load the new input. Follow it with `load()` to replace the queue.
Input-read failures leave the existing queue intact; failures while filtering individual rows do not guarantee an atomic rollback of the entire batch.
Independent append operations do not cancel one another. A newer replacement cancels an unfinished older replacement.
Destroy cancels pending loads. Cancelled Promises resolve to the owner without a late `loaded` or `error` event.
Fetch and response-text failures emit `artplayerPluginDanmuku:error` and reject the corresponding public `load()` Promise.
Handle rejection with `await`/`try...catch` or `.catch(...)`. Initial automatic loading observes rejection and logs a warning.
Invalid configuration leaves the current option intact. Use `mount(target)` to move the panel; changing `option.mount` through `config()` does not perform a mount.
Enable heatmap when constructing the plugin; `config({ heatmap: true })` does not create it later.
<div className="run-code" data-libs="./uncompiled/artplayer-plugin-danmuku/index.js">
▶ Run Code
</div>
```js
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
plugins: [artplayerPluginDanmuku({ danmuku: [], emitter: false })],
});
async function updateComments() {
var plugin = art.plugins.artplayerPluginDanmuku;
plugin.config({ danmuku: [{ text: 'Replacement', time: 1 }] });
await plugin.load();
await plugin.load([{ text: 'Appended', time: 2 }]);
await plugin.emit({ text: 'Scheduled from the current time' });
plugin.hide();
console.info('Hidden:', plugin.isHide);
plugin.show();
plugin.reset();
}
updateComments().catch(console.error);
```
## External mount
Create a separate mount element before constructing the plugin. The panel moves into the controls during player fullscreen or web fullscreen and returns to its configured mount on exit.
Use `theme: 'light'` on a light background. The live `mount(target)` method requires a valid target; omitting its argument does not select the default.
Destroy releases the plugin panel; the application owns any container it created.
<div className="run-code" data-libs="./uncompiled/artplayer-plugin-danmuku/index.js">
▶ Run Code
</div>
```js
var $danmu = document.createElement('div');
document.querySelector('.artplayer-app').after($danmu);
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
fullscreenWeb: true,
plugins: [artplayerPluginDanmuku({
danmuku: [{ text: 'External input panel', time: 1 }],
mount: $danmu,
theme: 'dark',
})],
});
art.on('destroy', () => $danmu.remove());
// Move it later with art.plugins.artplayerPluginDanmuku.mount(otherElement).
```
## Heatmap
Set `heatmap: true` at construction to sample the queue automatically. A live stream does not draw a heatmap.
Dense automatic curves now fit in the bottom quarter of the chart instead of covering the video (issue #958).
Explicit finite `yMin` or `yMax` and custom points retain their coordinate mapping.
An object can set `xMin`, `xMax`, `yMin`, `yMax`, `scale`, `opacity`, `minHeight`, `sampling`, `smoothing` and `flattening`.
Defaults are `xMin: 0`, `xMax: chartWidth`, `yMin: 0`, `yMax: 128`, `scale: 0.25`, `opacity: 0.2`,
`minHeight: floor(chartHeight * 0.05)`, `sampling: max(1, floor(chartWidth / 100))`, `smoothing: 0.2`, `flattening: 0.2`.
Send `art.emit('artplayerPluginDanmuku:points', points)` to draw custom `[x, value]` pairs.
The default x-axis uses chart pixels, not seconds. Set `xMin/xMax` explicitly if supplying another coordinate range.
Rendering mutates the inner point arrays for historical compatibility: copy each pair if reusing the original data.
The stored `points` option does not draw custom data. A resize or successful load redraws the automatic curve, so resend custom data after those events when needed.
<div className="run-code" data-libs="./uncompiled/artplayer-plugin-danmuku/index.js">
▶ Run Code
</div>
```js
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
plugins: [artplayerPluginDanmuku({
danmuku: [{ text: 'Heatmap example', time: 1 }],
heatmap: true,
})],
});
var points = [[0, 5], [0.25, 12], [0.5, 30], [0.75, 10], [1, 5]];
function drawPoints() {
var width = art.controls.heatmap.offsetWidth;
art.emit('artplayerPluginDanmuku:points', points.map(([ratio, value]) => [ratio * width, value]));
}
art.on('ready', drawPoints);
art.on('resize', drawPoints);
art.on('artplayerPluginDanmuku:loaded', drawPoints);
```
## Events
Subscribe with `art.on(name, callback)` and remove subscriptions with `art.off(name, callback)`.
| Event | Payload / meaning |
| --- | --- |
| `artplayerPluginDanmuku:visible` | Queue item; its `$ref` is the displayed element |
| `artplayerPluginDanmuku:loaded` | Current full queue after a successful load, including appends |
| `artplayerPluginDanmuku:error` | Original error from loading or scheduling; handle public Promise rejections separately |
| `artplayerPluginDanmuku:config` | Current configuration |
| `artplayerPluginDanmuku:start` | No payload; scheduling starts |
| `artplayerPluginDanmuku:stop` | No payload; scheduling stops |
| `artplayerPluginDanmuku:hide` | No payload; layer hidden |
| `artplayerPluginDanmuku:show` | No payload; layer shown |
| `artplayerPluginDanmuku:reset` | No payload; displayed items reset |
| `artplayerPluginDanmuku:destroy` | No payload; plugin destroyed |
| `artplayerPluginDanmuku:points` | Application-sent custom points for the heatmap |
Events are not replayed to later listeners. In particular, an initially empty array can finish loading during construction.
Subscribe before invoking a later `load()` if you need to observe its completion event.
Use `$ref.textContent` when adding text in a `visible` handler.
## TypeScript
The `/runtime` `Owner` type describes the internal return object for existing advanced integrations. Its `art`, `option`, `queue` and `states` are live objects; `readys` is the current candidate list. `speed`, `fontSize`, `marginTop/marginBottom` and `isRotate` are computed views. Do not mutate queue state to simulate public commands.
Owner's additional `start/stop` methods control comment scheduling and emit their events; `continue/suspend` resume or pause existing displayed items; `update` schedules work. These methods synchronously return Owner and do not control video playback. `resize` adjusts displayed items, while `seek` cancels old display preparation and schedules again; both return `undefined`. Owner's `destroy` cleans scheduling, nodes and input tasks and emits its destroy event, but does not fully uninstall the settings panel and heatmap. Use `art.destroy()` for complete cleanup. These additional commands are absent from the registered result.
The root and `/legacy` entrypoints keep the npm 5.3.0 declaration shapes for compatibility, including historical inaccuracies about return values.
The current branch adds `/runtime` for accurate types while loading the same runtime factory:
```ts
import Artplayer from 'artplayer';
import danmuku from 'artplayer-plugin-danmuku/runtime';
import type { RuntimeOption, Point, EventMap } from 'artplayer-plugin-danmuku/runtime';
const option: RuntimeOption = { danmuku: [], heatmap: true };
const points: Point[] = [[0, 5], [100, 10]];
const onError = (...[error]: EventMap['artplayerPluginDanmuku:error']) => console.error(error);
const art = new Artplayer({ container: '#player', url: '/video.mp4', plugins: [danmuku(option)] });
art.on('artplayerPluginDanmuku:error', onError);
```
The explicit `EventMap` describes payloads; it does not augment the core's historical event declarations automatically.
The factory also exposes the existing `icons` object for customization.
Root named types are `Mode`, `Danmuku`, `Slider`, `Danmu`, `Option` and `Result`. `/runtime` exposes `Mode`, `State`, `Margin`, `Point`, `Danmu`, `NormalizedDanmu`, `Item`, `Input`, `SliderStep`, `Slider`, `Heatmap`, `NormalizedOption`, `RuntimeOption`, `Owner`, `RuntimeResult`, `Icons`, `RuntimeFactory` and `EventMap`. `Item` adds `$state/$index/$ref/$restTime/$lastStartTime`; nodes are reused and `$ref` becomes null when recycled. A node received by a visible handler does not permanently belong to that comment.
`icons` contains `$on/$off/$config/$style`, the three pairs `$mode_0_off/$mode_0_on` through `$mode_2_off/$mode_2_on`, and `$check_on/$check_off`. Applications can reuse these SVG strings in their own interfaces. The built-in panel reads bundled icons directly; changing the factory icons object does not replace its icons.
Package `README.md` and `ARCHITECTURE.md` describe module ownership, maintenance commands and remaining device/combination validation.
===== packages/artplayer-vitepress/docs/en/plugin/dash-control.md =====
# DASH Control
[中文说明](../../plugin/dash-control.md)
Add video quality and audio-track menus to a dash.js player. You create and attach the SDK instance at `art.dash`; the plugin controls that instance and releases its own menus and listeners.
This guide describes the current refactor branch. Its SDK adaptation, automatic refresh and lifecycle fixes have not yet been published. An unversioned npm/CDN install still uses the published release.
## Installation
```sh
yarn add artplayer dashjs artplayer-plugin-dash-control
```
```js
import Artplayer from 'artplayer';
import dashjs from 'dashjs';
import artplayerPluginDashControl from 'artplayer-plugin-dash-control';
```
For script tags, load ArtPlayer, dash.js and `dist/artplayer-plugin-dash-control.js` before setup. The plugin global is `artplayerPluginDashControl`. Pin your dependency versions and use a browser-accessible MPD and segments.
## Complete example
The example matches the [online DASH example](https://artplayer.org/?libs=https://cdnjs.cloudflare.com/ajax/libs/dashjs/5.2.1/modern/umd/dash.all.min.js%0A./uncompiled/artplayer-plugin-dash-control/index.js&example=dash.control). Replace the site's container and media URL in your application.
<div className="run-code" data-libs="https://cdnjs.cloudflare.com/ajax/libs/dashjs/5.2.1/modern/umd/dash.all.min.js
./uncompiled/artplayer-plugin-dash-control/index.js">▶ Run Code</div>
```js
// npm i dashjs
// npm i artplayer-plugin-dash-control
// import dashjs from 'dashjs';
// import artplayerPluginDashControl from 'artplayer-plugin-dash-control';
const useDash = dashjs.supportsMediaSource()
let dash
function destroyDash() {
const previous = dash
dash = undefined
if (previous)
previous.destroy()
}
const art = new Artplayer({
container: '.artplayer-app',
url: 'https://media.axprod.net/TestVectors/v7-Clear/Manifest_1080p.mpd',
setting: true,
plugins: useDash
? [
artplayerPluginDashControl({
quality: {
// Show quality choices in the controls
control: true,
// Show quality choices in settings
setting: true,
// Get the quality name from level
getName: level => `${level.height}P`,
// I18n
title: 'Quality',
auto: 'Auto',
},
audio: {
// Show audios in control
control: true,
// Show audios in setting
setting: true,
// Get the audio name from track
getName: track => track.lang?.toUpperCase() || String(track.id ?? 'Audio'),
// I18n
title: 'Audio',
auto: 'Auto',
},
}),
]
: [],
customType: {
mpd: function playMpd(video, url, art) {
destroyDash()
if (useDash) {
dash = dashjs.MediaPlayer().create()
art.dash = dash
dash.initialize(video, url, art.option.autoplay)
}
else {
art.notice.show = 'Unsupported playback format: mpd'
}
},
},
})
art.on('destroy', destroyDash)
```
The example chooses its SDK capability path once per player. If dash.js cannot use MediaSource, it displays the existing unsupported-format notice without creating an SDK or installing its controls. This example does not provide a native DASH fallback.
## Configuration
`artplayerPluginDashControl(option?)` synchronously returns a plugin factory. Both `quality` and `audio` accept:
| Field | Meaning and default |
| --- | --- |
| `control` | Display a bottom control; omitted means hidden. |
| `setting` | Display a settings entry; omitted means hidden. Also enable the player's `setting: true`. |
| `title` | Menu title, default `Quality` or `Audio`. |
| `auto` | Fallback text, default `Auto`; quality also uses it for its automatic selection row. It does not create a synthetic audio track. |
| `getName(item)` | Return a string from the original SDK level or track. It receives one argument, with no player receiver or index. |
The default quality label is `level.height + 'p'`. Default audio labels use `track.lang` or `track.id`; metadata may be missing or null. A custom formatter should return a string fallback, as the complete example does. Empty title/Auto text uses the default.
Equal text labels collapse into one displayed choice. Include bitrate or other metadata when several variants have the same height and should remain separate choices. A selected duplicate keeps the actual selected SDK key or track object. Empty track lists remove their menus.
## Quality and audio selection
Manual quality selection disables video Auto switching, then selects the SDK quality. Auto enables video Auto switching without overwriting unrelated ABR settings. A synchronous menu selection does not mean buffering or decoding has completed.
The plugin detects the SDK's available method family:
| SDK interface | Quality list and selection |
| --- | --- |
| dash.js 4 style | `getBitrateInfoListFor('video')`, `getQualityFor('video')`, `setQualityFor('video', qualityIndex)` |
| dash.js 5 style | `getRepresentationsByType('video')`, `getCurrentRepresentationForType('video')`, `setRepresentationForTypeById('video', id)` |
For the representation interface, selection uses the representation ID, including numeric zero. Do not substitute the index of a filtered array. You do not need to configure a version switch in the plugin.
Audio selection calls `setCurrentTrack()` with the original SDK track object. It matches the current track by object identity or an unambiguous combination of available id/index/lang fields. There is no extra Auto audio row.
## Refresh after external changes
Player `ready`/`restart` and SDK quality, track and stream events refresh menus. SDK event refreshes are coalesced after the current synchronous selection. Unchanged playback-time events do not redraw menus; they can detect an external Auto-setting change.
When changing SDK configuration while paused without a subsequent SDK event, refresh explicitly:
```js
art.plugins.artplayerPluginDashControl.update();
```
`update()` is synchronous and returns `undefined`. It requires `art.dash` to be attached to the player's video and can throw if that contract is not met. Assigning a different `art.dash` alone does not subscribe to it immediately; call `update()` or use the normal ready/restart flow after attachment.
Automatic refresh preserves an open, plugin-owned quality or audio settings panel. Explicit `update()` keeps its existing rebuild behavior. Reserve `dash-quality` and `dash-audio` for the plugin's menu names.
If a synchronous SDK getter or formatter throws during an asynchronous refresh scheduled by an SDK event, the plugin warns, stops that observation and clears its menus. Fix the formatter/SDK state and call `update()` to recover. Getters and formatters must return synchronously; the plugin does not await their Promises. Explicit update and synchronous selection errors keep their normal throwing behavior.
## SDK ownership and source switching
Use the existing player `switchUrl()` or `switchQuality()` and handle its Promise. The complete example destroys the replaced SDK, assigns the new instance before initialization, and keeps one final player cleanup listener. It does not accumulate a new destroy listener on every load or destroy replaced engines again at the end.
The plugin does not own your SDK: it never calls `dash.destroy()`, changes the manifest URL or removes listeners belonging to other consumers. Retained callbacks from old menus become inactive after replacement or player destruction. Your application remains responsible for DRM, SDK errors, autoplay decisions and any asynchronous SDK shutdown policy its integration requires.
## TypeScript
Default callback types include quality height/width/ID/bitrate and nullable audio id/index/lang. Use the generic factory for more specific metadata. This example declares only the fields it uses and does not require importing SDK declarations:
```ts
import dashControl from 'artplayer-plugin-dash-control';
interface Level { height: number; bitrateInKbit?: number }
interface Track { id?: string | number | null; lang?: string | null }
const plugin = dashControl<Level, Track>({
quality: {
control: true,
getName: level => level.height + 'p',
},
audio: {
setting: true,
getName: track => track.lang?.toUpperCase() || String(track.id ?? 'Audio'),
},
});
```
`Option`, `Config`, `QualityLevel`, `AudioTrack` and `Result` are exported from the root. Root and legacy paths remain available. If using actual SDK declarations, dash.js 4.5.2 exposes `BitrateInfo` for quality; 5.2.1 uses `Representation`. Its declarations have different compiler/module-resolution requirements, so test your actual SDK and TypeScript combination. Describe the externally attached `art.dash` in your application's integration types.
The registered result contains only the fixed `name: 'artplayerPluginDashControl'` and `update()`. Ordinary factory calls may omit the options, while the last declaration overload keeps its parameter required: `Parameters<typeof dashControl>[0]` remains `Option`, without `undefined`. This package has no `/runtime` subpath or factory `.default` self-alias.
## Validation scope
The refactor tests fixed dash.js 4.5.2 and 5.2.1 with local adaptive media and old/new core combinations. Those are tested points, not a new blanket support range. The plugin includes a targeted 4.5.2 paused-seek recovery for stale empty-buffer metrics while preserving the caller's SDK settings and media time. Windows Playwright WebKit lacks the MSE path used by these tests; it is not Safari/device playback acceptance. Test your own MPDs, DRM and target devices before adopting the unpublished refactor.
===== packages/artplayer-vitepress/docs/en/plugin/document-pip.md =====
# Document Picture-in-Picture
[简体中文](../../plugin/document-pip.md)
Move the whole player, including its controls, into a browser Document Picture-in-Picture window. Closing the window restores the same player node to its original position. This page describes the unreleased refactor branch; the online example and unpinned npm/CDN packages are not the current candidate.
## Installation and example
```sh
yarn add artplayer artplayer-plugin-document-pip
```
```js
import Artplayer from 'artplayer';
import artplayerPluginDocumentPip from 'artplayer-plugin-document-pip';
```
For script usage, load ArtPlayer before `dist/artplayer-plugin-document-pip.js`. The global is `artplayerPluginDocumentPip`. The following code preserves the original [online example](https://artplayer.org/?libs=./uncompiled/artplayer-plugin-document-pip/index.js&example=document.pip).
<div className="run-code" data-libs="./uncompiled/artplayer-plugin-document-pip/index.js">▶ Run Code</div>
```js
// npm i artplayer-plugin-document-pip
// import artplayerPluginDocumentPip from 'artplayer-plugin-document-pip';
const art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
plugins: [
artplayerPluginDocumentPip({
width: 480,
height: 270,
fallbackToVideoPiP: true,
placeholder: `Playing in Document Picture-in-Picture`,
}),
],
})
art.on('document-pip', (state) => {
console.log('Document Picture-in-Picture', state)
})
```
## Options
| Field | Type | Default | Meaning |
| --- | --- | --- | --- |
| `width` | `number` | `480` | Requested window width; the browser determines the actual size |
| `height` | `number` | `270` | Requested window height |
| `placeholder` | `string` | `'Playing in Document Picture-in-Picture'` | Placeholder text at the original player position |
| `fallbackToVideoPiP` | `boolean` | `true` | Try setting `art.pip = true` when the Document PiP API is absent |
The plugin registers as `artplayerPluginDocumentPip` and adds a PiP control button. It detects `documentPictureInPicture.requestWindow`; a positive result does not guarantee that permissions, the calling context or user activation will allow opening. Call `open()` or `toggle()` directly from a user click handler, before awaiting unrelated work.
Video PiP fallback also depends on the browser and media. It does not move the whole player, set the plugin's `isActive` flag or emit a Document PiP activation event. `close()` manages the Document PiP window; use `art.pip = false` to exit video PiP. Disabling fallback does not add Document PiP support to the browser.
## State, methods and events
Access the result through `art.plugins.artplayerPluginDocumentPip`:
| Member | Runtime behavior |
| --- | --- |
| `name` | Always `artplayerPluginDocumentPip` |
| `isSupported` | Readonly getter; Document PiP API capability snapshot taken at plugin creation |
| `isActive` | Readonly getter; whether a Document PiP session is held, not video PiP state |
| `open()` | Returns `Promise<void>`; requests a window and moves the player, coalesces pending requests and does nothing when already open |
| `close()` | Returns `Promise<void>`; cancels a pending request or restores the node and closes the window |
| `toggle()` | Returns `undefined` synchronously; opens or closes the active/pending window |
Successful activation and normal closure emit the player's `document-pip` event with `true` and `false`, respectively. The plugin updates the existing `artplayer-document-pip` class, rebinds document events and schedules resize. This event does not signal successful media loading or playback.
Normal window request/restoration failures display a notice and console warning; awaiting `open()` alone is not proof that a window opened. An error thrown by the video PiP fallback setter can still reject its Promise. A window arriving after cancellation is closed without adopting the player. Destroying the player releases the window, control, subscriptions and timers, suppressing further plugin state events. There is no separate public plugin `destroy()`.
Styles are copied from the player's document on a best-effort basis. Inaccessible cross-origin styles and externally managed DOM need validation in your environment. Canvas, other proxies, keyboard focus and continuous playback also need their own checks; the capability flag cannot establish those results.
## Compatible TypeScript views
The root and `/legacy` declarations preserve the old required options object, writable `Result` flags and void actions. JavaScript permits omitted options, the runtime flags are readonly getters, and `open/close` return Promises. The old shape preserves consumer and replacement-function compatibility; it does not make the runtime getters writable.
This package has no `/runtime` subpath. For accurate types, explicitly view the real factory as `RuntimeFactory`:
```ts
import documentPip from 'artplayer-plugin-document-pip';
import type { AsyncResult, RuntimeFactory } from 'artplayer-plugin-document-pip';
const runtimeFactory = documentPip as RuntimeFactory;
const installPip = runtimeFactory();
const installDefaultPip = runtimeFactory.default({ width: 480 });
async function closePip(pip: AsyncResult): Promise<void> {
await pip.close();
}
```
Named types are `Option`, `Result`, `AsyncResult`, `Factory` and `RuntimeFactory`. Apply the precise view only to the unmodified implementation, not a void-returning mock or replaced method. CommonJS runtime supports both `require(package)(options)` and `.default(options)`. Historical `import = require` types use `.default`; use the precise factory view when direct calling is needed.
===== packages/artplayer-vitepress/docs/en/plugin/hls-control.md =====
# HLS Control
[中文说明](../../plugin/hls-control.md)
Add quality and audio-track menus to an Hls.js player. This plugin controls the Hls.js instance you provide at `art.hls`; it does not download, create or destroy the SDK.
This guide describes the current refactor branch. The automatic refresh and lifecycle fixes described here have not yet been published. An unversioned npm/CDN install still uses the published release.
## Installation
```sh
yarn add artplayer hls.js artplayer-plugin-hls-control
```
```js
import Artplayer from 'artplayer';
import Hls from 'hls.js';
import artplayerPluginHlsControl from 'artplayer-plugin-hls-control';
```
For script tags, load ArtPlayer, Hls.js and the plugin's `dist/artplayer-plugin-hls-control.js` before running your setup. The plugin global is `artplayerPluginHlsControl`. Pin versions in your application and use media URLs that permit browser access.
## Complete example
The example uses the site's player container and the same source as the [online HLS example](https://artplayer.org/?libs=https://cdnjs.cloudflare.com/ajax/libs/hls.js/1.5.17/hls.min.js%0A./uncompiled/artplayer-plugin-hls-control/index.js&example=hls.control). Replace the container and stream URL in your application.
<div className="run-code" data-libs="https://cdnjs.cloudflare.com/ajax/libs/hls.js/1.5.17/hls.min.js
./uncompiled/artplayer-plugin-hls-control/index.js">▶ Run Code</div>
```js
// npm i hls.js
// npm i artplayer-plugin-hls-control
// import Hls from 'hls.js';
// import artplayerPluginHlsControl from 'artplayer-plugin-hls-control';
const useHls = Hls.isSupported()
let hls
function destroyHls() {
const previous = hls
hls = undefined
if (previous)
previous.destroy()
}
const art = new Artplayer({
container: '.artplayer-app',
url: 'https://playertest.longtailvideo.com/adaptive/elephants_dream_v4/index.m3u8',
setting: true,
plugins: useHls
? [
artplayerPluginHlsControl({
quality: {
// Show quality choices in the controls
control: true,
// Show quality choices in settings
setting: true,
// Get the quality name from level
getName: level => `${level.height}P`,
// I18n
title: 'Quality',
auto: 'Auto',
},
audio: {
// Show audios in control
control: true,
// Show audios in setting
setting: true,
// Get the audio name from track
getName: track => track.name || track.lang || 'Audio',
// I18n
title: 'Audio',
auto: 'Auto',
},
}),
]
: [],
customType: {
m3u8: function playM3u8(video, url, art) {
destroyHls()
if (useHls) {
hls = new Hls()
art.hls = hls
hls.loadSource(url)
hls.attachMedia(video)
}
else if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = url
}
else {
art.notice.show = 'Unsupported playback format: m3u8'
}
},
},
})
art.on('destroy', destroyHls)
```
Only install the control plugin when Hls.js is supported. If the browser instead plays HLS natively, the example assigns `video.src` without installing a plugin that requires `art.hls`. Native quality/audio selection is not supplied by this plugin. Keep `useHls` fixed for that player instance; recreate the player if the playback integration changes.
## Configuration
`artplayerPluginHlsControl(option?)` returns a synchronous plugin factory. Both `quality` and `audio` accept these fields:
| Field | Meaning and default |
| --- | --- |
| `control` | Show the bottom control; omitted means hidden. |
| `setting` | Show the settings entry; omitted means hidden. Enable the player's `setting: true` as well. |
| `title` | Menu title; quality defaults to `Quality`, audio to `Audio`. |
| `auto` | Fallback text, default `Auto`. Quality also uses it for its Auto option. Audio does not gain a synthetic Auto track. |
| `getName(item, index?)` | Return a string from the original SDK level or track. The current-label call omits `index`; list calls include it. |
Without a quality formatter, labels use `level.name` or `level.height + 'P'`. Audio labels use `track.name`, `track.lang`, then `track.language`. Empty titles/Auto text fall back to their defaults. Give formatters a string fallback when your source lacks metadata.
The formatter is a plain callback; it does not receive the player as `this`. Avoid using its optional index as a required field. Equal labels collapse into one displayed choice, so include bitrate or other distinguishing metadata when separate variants have the same height. Empty track lists remove their menus.
Choosing a quality writes `hls.currentLevel`; Auto writes `-1`. The selected label reflects automatic mode when `autoLevelEnabled` is true. Choosing audio writes `hls.audioTrack` with the SDK track ID. A menu selection is synchronous and does not mean the new stream has finished buffering or decoding.
## Refresh after external changes
The plugin updates on player `ready` and `restart`. When the SDK provides its event API, manifest, level, audio and destruction events also refresh or clear the menus. Current SDK state determines the selected choice.
After attaching a replacement instance to the same video and assigning `art.hls`, use the existing synchronous method when an immediate refresh is needed:
```js
art.plugins.artplayerPluginHlsControl.update();
```
`update()` returns `undefined`; it is not a Promise. Calling it without an attached Hls.js instance can throw. SDK-like integrations without supported event hooks must call it when their state changes. Reserve menu names `hls-quality` and `hls-audio` for this plugin.
## SDK ownership and source switching
Use the player's existing `switchUrl()` or `switchQuality()` for source changes and handle the returned Promise. The example's custom loader destroys the previous SDK before creating the next one, updates `art.hls`, and keeps one final cleanup listener per player. Each instance is destroyed once. Do not add a new player `destroy` listener on every load while also destroying replaced instances yourself.
The control plugin releases its own SDK subscriptions and makes retained menu callbacks inert after replacement or player destruction. It does not destroy the SDK, remove other consumers' SDK listeners, or implement Hls.js error recovery. Your application remains responsible for SDK fatal errors and playback policy.
## TypeScript
The default callback types include level height/name and audio id/name/language fields. Applications can specify their actual SDK metadata types through the existing generic factory. The example below uses only fields it needs and works without importing Hls.js declarations:
```ts
import hlsControl from 'artplayer-plugin-hls-control';
interface Level { height: number; bitrate: number }
interface Track { id: number; name: string; lang?: string }
const plugin = hlsControl<Level, Track>({
quality: {
control: true,
getName: level => level.height + 'p / ' + level.bitrate,
},
audio: {
setting: true,
getName: track => track.name || track.lang || 'Audio',
},
});
```
Public `Option`, `Config`, `QualityLevel`, `AudioTrack` and `Result` types are exported from the root entry. The root and legacy import paths remain available. The types do not pretend every ArtPlayer instance already owns an Hls.js engine; describe `art.hls` in your application's integration types.
The registered result contains only the fixed `name: 'artplayerPluginHlsControl'` and `update()`. Ordinary factory calls may omit the options, while the last declaration overload keeps its parameter required: `Parameters<typeof hlsControl>[0]` remains `Option`, without `undefined`. This package has no `/runtime` subpath or factory `.default` self-alias.
## Validation scope
The refactor tests Hls.js 1.5.17 and 1.7.2 with local media and real workers; these are tested points, not a newly declared supported range. Firefox grouped-stream crashes and a separate switching stall remain under investigation. Windows Playwright WebKit lacks the MSE path used in those tests; it does not establish Safari/iOS native-HLS acceptance. Verify your actual streams and target devices before adopting the unpublished refactor.
===== packages/artplayer-vitepress/docs/en/plugin/jassub.md =====
# ASS Subtitles with JASSUB
[简体中文](../../plugin/jassub.md)
Render ASS subtitles on Canvas using the JASSUB Worker and WASM. The plugin returns the actual JASSUB instance with its methods/events rather than converting ASS to plain VTT. This describes the unreleased branch; Worker, WASM, fonts and actual playback require deployment-specific validation.
## Installation and example
```sh
yarn add artplayer artplayer-plugin-jassub
```
```js
import Artplayer from 'artplayer';
import artplayerPluginJassub from 'artplayer-plugin-jassub';
```
For scripts, load ArtPlayer before `dist/artplayer-plugin-jassub.js`; the global is `artplayerPluginJassub`. This preserves the [original example](https://artplayer.org/?libs=./uncompiled/artplayer-plugin-jassub/index.js&example=jassub). Its Worker/WASM/font URLs are site assets, not paths automatically created in your application by installing the plugin.
<div className="run-code" data-libs="./uncompiled/artplayer-plugin-jassub/index.js">▶ Run Code</div>
```js
// https://github.com/ThaUnknown/jassub
// npm i artplayer-plugin-jassub
// import artplayerPluginJassub from 'artplayer-plugin-jassub';
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/jassub/FGOBD.mp4',
autoSize: true,
fullscreen: true,
fullscreenWeb: true,
plugins: [
artplayerPluginJassub({
subUrl: '/assets/jassub/FGOBD.ass',
workerUrl: '/assets/jassub/jassub-worker.js',
wasmUrl: '/assets/jassub/jassub-worker.wasm',
modernWasmUrl: '/assets/jassub/jassub-worker-modern.wasm',
availableFonts: {
'liberation sans': '/assets/jassub/default.woff2'
},
fonts: [
'/assets/jassub/fonts/Averia Sans Libre Light.ttf',
'/assets/jassub/fonts/Averia Serif Simple Light.ttf',
'/assets/jassub/fonts/Gramond.ttf'
],
timeOffset: -0.041
}),
],
});
```
Host matching Worker, WASM and fonts with correct paths, access policies and font usage rights. Missing fonts affect layout. Default relative URLs do not establish that files are deployed.
## Resource and rendering options
Runtime options are optional and default to art.video. They are read at registration; an explicit video overrides the host video. These are current local implementation defaults, including differences from old upstream comments.
| Field | Actual default or meaning |
| --- | --- |
| `video` / `canvas` | Video defaults to art.video; caller canvas allows manual DOM handling; a usable rendering target is required |
| `workerUrl` | `'jassub-worker.js'` |
| `wasmUrl` | `'jassub-worker.wasm'` |
| `legacyWasmUrl` | `'jassub-worker.wasm.js'` |
| `modernWasmUrl` | Used when supplied and SIMD is detected; otherwise wasmUrl |
| `subUrl` / `subContent` | Subtitle URL or ASS text sent to the Worker |
| `fonts` | Empty array by default; entries can be URLs or Uint8Array |
| `availableFonts` | Defaults to `{ 'liberation sans': './default.woff2' }`; font-name-to-URL/bytes map |
| `fallbackFont` | `'liberation sans'` |
| `useLocalFonts` | Enabled only when queryLocalFonts exists; current code defaults to true when available, with explicit false supported; permissions still apply |
| `blendMode` | `'js'`, with `'wasm'` also accepted |
| `asyncRender` | Defaults true, subject to createImageBitmap availability |
| `offscreenRender` | Defaults true, requiring transferControlToOffscreen and no caller canvas |
| `onDemandRender` | Defaults true, subject to requestVideoFrameCallback availability |
| `targetFps` | Defaults 24; not a fixed update guarantee in on-demand mode |
| `timeOffset` | Defaults 0 seconds |
| `debug` | Defaults false |
| `prescaleFactor` / `prescaleHeightLimit` / `maxRenderHeight` | Defaults 1 / 1080 / 0; rendering size controls, with no maximum imposed by a zero maxRenderHeight |
| `dropAllAnimations` / `dropAllBlur` | Simplification options forwarded to the Worker; not set to true by the wrapper when omitted |
| `libassMemoryLimit` / `libassGlyphLimit` | Defaults 0; libass cache limits in MiB, not a total browser-memory cap |
Capabilities can change the chosen rendering path. Async/offscreen/backend options are not universal performance guarantees. Do not mix incompatible Worker and wrapper versions.
## Instance and methods
Registration synchronously returns `{ name: 'artplayerPluginJassub', instance }`, not a Worker-ready Promise. Access it at `art.plugins.artplayerPluginJassub.instance`. It is an EventTarget: query after ready, and observe its error event rather than assuming a same-named player event.
| Method | Behavior and units |
| --- | --- |
| `resize(width?, height?, top?, left?, force?)` | Synchronous void; force is last, with omitted dimensions derived from video |
| `setVideo(video)` | Rebind video, observation and the owned container position synchronously |
| `setTrackByUrl(url)` / `setTrack(content)` / `freeTrack()` | Set subtitle URL/text or release the track |
| `setIsPaused(boolean)` / `setRate(number)` | Manually update Worker playback state/rate |
| `setCurrentTime(isPaused?, currentTime?, rate?)` | currentTime uses seconds |
| `createEvent(event)` / `setEvent(event, index)` / `removeEvent(index)` | Mutate ASS events, accepting partial fields |
| `getEvents(callback)` | Callback-based event query, not a Promise |
| `createStyle(style)` / `setStyle(style, index)` / `removeStyle(index)` | Mutate styles, accepting partial fields |
| `getStyles(callback)` | Callback-based style query, not a Promise |
| `styleOverride(style)` / `disableStyleOverride()` | Enable/disable style override |
| `setDefaultFont(font)` / `addFont(font)` | Set the default font or add a URL/byte font |
| `runBenchmark()` | Request the Worker benchmark; not an end-to-end performance conclusion |
| `sendMessage(target, data?, transferable?)` | Promise resolves after posting, or without posting after destroy; not a Worker acknowledgement |
| `destroy()` | Synchronous, idempotent cleanup; no resource-reclamation Promise |
Except sendMessage, ordinary control methods above return void; queries supply callback data. The historical destroy(error) overload returns an original Error, converts a nonempty string to Error, or retains an empty string. Normal cleanup uses no-argument destroy.
AssEvent Start/Duration use milliseconds and Style is a numeric style index. Other fields are Name, MarginL/MarginR/MarginV, Effect, Text, ReadOrder and Layer. AssStyle includes Name/FontName/FontSize, PrimaryColour/SecondaryColour/OutlineColour/BackColour, Bold/Italic/Underline/StrikeOut, ScaleX/ScaleY/Spacing/Angle, BorderStyle/Outline/Shadow/Alignment, MarginL/MarginR/MarginV, Encoding, treat_fontname_as_pattern, Blur and Justify. Query results have no `_index`; the ASS text-format style name is not the runtime numeric Style.
Successful queries call `(null, array)`; failures supply an Error or native Event without data. Destroy fails pending queries after releasing their listeners/timers. The protocol matches by response target, without a new request ID; do not assume simultaneous same-target queries have independently correlated responses.
## Lifecycle and types
The plugin-owned `.JASSUB` container uses z-index20 and is removed on destruction; caller canvas/video nodes remain. Player destruction calls the instance's current destroy method; direct destruction is also idempotent. setVideo/destroy invalidate old frame callbacks and clean Worker/observer ownership. Late bitmaps must not restart rendering. Actual offscreen stalls, browser differences and physical devices retain separate acceptance requirements.
Root and `/legacy` retain historical JassubOption/JassubInstance: three required URLs, force-first resize, inaccurate Promise method returns and open extension indexes. Use `/runtime` for accurate types with the same implementation:
```ts
import type Artplayer from 'artplayer';
import jassub from 'artplayer-plugin-jassub/runtime';
function attachSubtitles(art: Artplayer) {
const { instance } = jassub({
workerUrl: '/assets/jassub/jassub-worker.js',
wasmUrl: '/assets/jassub/jassub-worker.wasm',
subUrl: '/subtitles.ass',
})(art);
instance.addEventListener('ready', () => {
instance.getEvents((error, events) => {
if (error) console.error(error);
else console.log(events);
});
});
return instance;
}
```
Runtime exports FontSource, RuntimeOption, AssEvent, AssStyle, AssEventInput, AssStyleInput, WorkerRequestError, EventsCallback, StylesCallback, RuntimeEventMap, RuntimeInstance, RuntimeResult and RuntimeFactory. Precise instance fields include timeOffset, debug, the three sizing settings, optional busy and historical public `_canvas/_ctx`; `_ctx` may be false/null. There is no runtime factory `.default` self-alias. The old root's NodeNext namespace does not create one. Runtime supports an accurate ESM default and CommonJS `import = require`.
===== packages/artplayer-vitepress/docs/en/plugin/multiple-subtitles.md =====
# Multiple Subtitles
[简体中文](../../plugin/multiple-subtitles.md)
Download several subtitle files, retain their individual cue times, and merge selected tracks into the player's subtitles. Display multiple languages and select or reorder them by name. This page describes the unreleased refactor branch; online examples and unpinned packages are not the current candidate.
## Installation and example
```sh
yarn add artplayer artplayer-plugin-multiple-subtitles
```
```js
import Artplayer from 'artplayer';
import artplayerPluginMultipleSubtitles from 'artplayer-plugin-multiple-subtitles';
```
For script usage, load ArtPlayer before `dist/artplayer-plugin-multiple-subtitles.js`; the global is `artplayerPluginMultipleSubtitles`. The following preserves the [online example](https://artplayer.org/?libs=./uncompiled/artplayer-plugin-multiple-subtitles/index.js&example=multiple.subtitles), including its selection menu and styles. The application configures that menu; the plugin does not create one automatically.
<div className="run-code" data-libs="./uncompiled/artplayer-plugin-multiple-subtitles/index.js">▶ Run Code</div>
```js
// npm i artplayer-plugin-multiple-subtitles
// import artplayerPluginMultipleSubtitles from 'artplayer-plugin-multiple-subtitles';
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
setting: true,
plugins: [
artplayerPluginMultipleSubtitles({
subtitles: [
{
name: 'chinese',
url: '/assets/sample/subtitle.cn.srt',
},
{
name: 'japanese',
url: '/assets/sample/subtitle.jp.srt',
},
],
}),
],
settings: [
{
width: 200,
html: 'Subtitle',
tooltip: 'Double',
icon: '<img width="22" height="22" src="/assets/img/subtitle.svg">',
selector: [
{
html: 'Display',
tooltip: 'Show',
switch: true,
onSwitch(item) {
item.tooltip = item.switch ? 'Hide' : 'Show'
// 显示/隐藏字幕
// Show/hide subtitles
art.subtitle.show = !item.switch
return !item.switch
},
},
{
html: 'Reverse',
tooltip: 'Off',
switch: false,
onSwitch(item) {
item.tooltip = item.switch ? 'Off' : 'On'
// 修改字幕顺序
// Change the order of subtitles
if (item.switch) {
art.plugins.multipleSubtitles.tracks(['chinese', 'japanese'])
}
else {
art.plugins.multipleSubtitles.tracks(['japanese', 'chinese'])
}
return !item.switch
},
},
{
default: true,
html: 'Double',
name: 'double',
},
{
html: 'Chinese',
name: 'chinese',
},
{
html: 'Japanese',
name: 'japanese',
},
],
onSelect(item) {
if (item.name === 'double') {
// 重置字幕
// Reset subtitles
art.plugins.multipleSubtitles.reset()
}
else {
// 显示单个字幕
// Show single subtitle
art.plugins.multipleSubtitles.tracks([item.name])
}
return item.html
},
},
],
})
// 自定义你自己的样式,请勿复制以下代码
// Customize your own style, please do not copy the following code
const style = `
.art-subtitle-chinese {
color: red;
font-size: 18px;
}
.art-subtitle-japanese {
color: yellow;
font-size: 12px;
}
`
const $style = document.getElementById('artplayer-subtitle-style')
if ($style) {
$style.textContent = style
}
else {
const $style = document.createElement('style')
$style.id = 'artplayer-subtitle-style'
$style.textContent = style
document.head.appendChild($style)
}
```
## Track options
The factory requires an options object containing a `subtitles` array. Runtime `{}` uses an empty array; historical root types still require the field. Each `TrackOption` has these fields:
| Field | Type | Default or behavior |
| --- | --- | --- |
| `url` | `string` | Subtitle URL loaded with browser fetch; provide a valid URL despite the historical optional type |
| `name` | `string` | Selection and CSS name; use a unique simple identifier such as `chinese` |
| `type` | `'vtt' \| 'srt' \| 'ass'` | Explicit value takes precedence over the URL extension |
| `encoding` | `string` | Defaults to `'utf-8'`, passed to TextDecoder |
| `onParser` | `(...args: object[]) => object` | Retained historical declaration; the implementation does not call it |
Files download concurrently, then decode and merge. Cross-origin servers must allow fetch. SRT and ASS use core conversion utilities to produce VTT; full ASS layout and animation are not preserved. Use JASSUB when full ASS rendering is needed. Unknown types produce empty content. Parsing is best-effort, and diagnostics do not necessarily discard all usable cues.
Unsuccessful HTTP responses and thrown decoding/conversion errors reject registration and release sibling requests. Track metadata is read from the original configuration after downloads, not a deep copy; avoid mutating the array or track objects during installation.
## Registration and selection
Registration is asynchronous and returns `{ name: 'multipleSubtitles', tracks, reset }`. The registered result is `art.plugins.multipleSubtitles`, not the global factory name. Await registration before directly using its result; readiness of that result does not guarantee the player has finished loading subtitles.
| Call | Result |
| --- | --- |
| `tracks(['chinese', 'japanese'])` | Select tracks in the caller's name order |
| `tracks(['japanese'])` | Select only that track |
| `tracks()` or `tracks([])` | Clear the selection |
| `reset()` | Restore all originally downloaded tracks in their original order |
Both methods return `undefined` synchronously. Unknown names retain the historical synchronous TypeError. Repeated names select the first matching track on each lookup without deduplication; use unique configured names. Merging follows selection order but does not rewrite cue timestamps, so reordering cannot synchronize mistimed translations.
Each selection creates a new VTT Blob URL, initializes player subtitles and releases the previous owned URL. Async host installation failures warn and clean up the failed resource; void methods cannot be awaited for subtitle readiness. Video source changes do not refetch tracks, and reset does not reload the server files. There is no public update, reload or separate destroy method.
## Styling and resource ownership
Content is wrapped with `.art-subtitle-<name>`, such as the example's `.art-subtitle-chinese` and `.art-subtitle-japanese`. Names enter HTML class markup; use application-defined simple identifiers. Selection sets `art.option.subtitle.escape = false` and supplies the subtitle URL, type and onVttLoad. Establish ownership when combining it with other subtitle managers, since later writes replace that configuration.
Literal cue text, entities and supported markup are handled separately. Inline timestamps remain in cue data, while captions still display whole cues; this does not introduce karaoke highlighting. Older cores receive a multiple-active-cue display adapter. Install custom subtitle DOM listeners after plugin registration so the plugin's later view update does not overwrite them.
Player destruction cancels requests, removes subscriptions and releases generated URLs. Pending registration settles with an inert result; retained tracks/reset calls become no-ops. Destroy does not restore the shared escape option, because another consumer may have changed it. Fonts, historical core combinations and physical Safari/mobile display still need their own validation.
## Compatible TypeScript entries
The root and `/legacy` preserve the latest published 1.2.0 factory shape: required `{ subtitles: TrackOption[] }` and a synchronous name-only `LegacyResult`. This retains historical extraction and replacement functions. For the actual Promise and selection methods, use `/runtime`, pointing to the same implementation:
```ts
import type Artplayer from 'artplayer';
import multipleSubtitles from 'artplayer-plugin-multiple-subtitles/runtime';
import type { Result, RuntimeOption } from 'artplayer-plugin-multiple-subtitles/runtime';
const options: RuntimeOption = {
subtitles: [{ url: '/subtitles/en.vtt', name: 'en' }],
};
async function selectSubtitles(art: Artplayer): Promise<Result> {
const result = await multipleSubtitles(options)(art);
result.tracks(['en']);
result.reset();
return result;
}
```
Root named types are `TrackOption`, `Option`, `RuntimeOption`, `LegacyResult`, `Result`, `Factory` and `RuntimeFactory`. Runtime exports TrackOption, RuntimeOption, Result and RuntimeFactory. RuntimeFactory also describes the writable `.default` self-alias; the historical Factory does not require it.
The 1.0/1.1 export-assignment declarations conflict with 1.2's default-module shape; the approved policy retains 1.2 at the root. NodeNext ESM root types expose the factory at `root.default`; use runtime for accurate default calls. Classic CommonJS without interop can use runtime `import = require`; classic default imports need `esModuleInterop`. Historical JavaScript `.default(...)` calls remain supported; an old incorrect declaration does not establish a runtime call that never worked.
===== packages/artplayer-vitepress/docs/en/plugin/vast.md =====
# VAST Ads
[简体中文](../../plugin/vast.md)
Request and display ads through Glomex VAST IMA Player and Google IMA. Unlike the separate [Ads plugin](./ads.md), this accepts ad-tag URLs or VAST responses rather than managing a local countdown ad. SDK and ad resources must be reachable; network, VPN or blocking rules can prevent loading. This page describes the unreleased refactor branch, not proof that the online example validates the current candidate.
## Installation and example
```sh
yarn add artplayer artplayer-plugin-vast
```
```js
import Artplayer from 'artplayer';
import artplayerPluginVast from 'artplayer-plugin-vast';
```
For script usage, load ArtPlayer before `dist/artplayer-plugin-vast.js`; the global is `artplayerPluginVast`. Its Glomex dependency loads IMA, so no separate custom loader is needed. This preserves the [original online example](https://artplayer.org/?libs=./uncompiled/artplayer-plugin-vast/index.js&example=vast); its external ad URL is not a local test fixture.
<div className="run-code" data-libs="./uncompiled/artplayer-plugin-vast/index.js">▶ Run Code</div>
```js
// Depends on:
// https://glomex.github.io/vast-ima-player/
// https://developers.google.com/interactive-media-ads/docs/sdks/html5/client-side
// Google's IMA SDK are blocked by your Ad blocker.
// Please Turn Off Your Ad Blocker.
// npm i artplayer-plugin-vast
// import artplayerPluginVast from 'artplayer-plugin-vast';
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
fullscreen: true,
fullscreenWeb: true,
plugins: [
artplayerPluginVast(({ playUrl, imaPlayer, ima }) => {
// Play the ad when the video is played
art.once('play', () => {
playUrl('https://artplayer.org/assets/vast/linear-ad.xml')
})
}),
],
})
```
## Initialization compatibility
`artplayerPluginVast(callback?, options?)` returns an asynchronous registrar. It loads the SDK, creates the appropriate context and awaits the callback before resolving. A callback can return a Promise. Registration does not imply an ad was requested or played.
| Behavior | Default npm mode | `{ compatibility: 'workspace-1.2' }` |
| --- | --- | --- |
| Callback entry | SDK player and container already allocated | Allocation waits for init/playUrl/playRes |
| IMA settings | SDK defaults preserved | Preloading and restoration of custom playback state default to true |
| Resource fields | Writable imaPlayer/id/$container data fields | Live readonly getters, null before allocation and after release |
| Requests | Wrapper forwards every explicit request | New requests are suppressed during an active ad |
| Container | SDK manages visibility with default styles | Black overlay, four ad events manage visibility and active state |
This implements the approved decision: published npm1.0.0 calls retain their default behavior. Code relying on unpublished workspace1.2 lazy initialization must select it explicitly. The mode is captured once at factory creation; unknown values immediately throw TypeError before SDK loading.
## Callback context
| Field | Runtime purpose |
| --- | --- |
| `art` | Current ArtPlayer instance |
| `ima` | Loaded IMA SDK |
| `adsRenderingSettings` | IMA AdsRenderingSettings passed to a newly allocated SDK player |
| `playerOptions` | Glomex PlayerOptions passed to a newly allocated SDK player |
| `imaPlayer` | SDK player; already created in default mode, nullable in workspace mode |
| `id` / `$container` | Container ID/element, with the mode-specific snapshot/null rules above |
| `container` | Live readonly container getter in both modes, null before allocation or after release |
| `init()` | Return or create the current SDK player; null after terminal disposal |
| `playUrl(url, config?)` | Create an AdsRequest, set adTagUrl and request ads; returns void synchronously |
| `playRes(response, config?)` | Create an AdsRequest, set adsResponse and request ads; returns void synchronously |
The extra config fields retain historical for-in copying, including enumerable inherited fields. Copying follows the primary assignment, so config can override adTagUrl/adsResponse. Use application-controlled configuration. Request construction or SDK synchronous errors can throw; void does not signal success or provide an ad-completion Promise.
To configure before SDK player allocation, select workspace mode and mutate the original settings/options objects before init or request methods. The default mode's first player already exists on callback entry; do not assume later option changes retroactively alter construction.
Default imaPlayer/id/$container fields retain the last allocation after release, even though that SDK is destroyed and its container removed. They are not active resources. Explicit recreation updates them, and assigning these fields does not transfer internal ownership. In workspace mode, keep the context and read getters when needed rather than destructuring their initial null values.
## Events and destruction
Subscribe to SDK events on imaPlayer; they are not same-named ArtPlayer events. Workspace mode listens to `AdContentPauseRequested`, `AdContentResumeRequested`, `AdStarted` and `AdError` to update its overlay and active state, logging AdError details. Default mode does not add those workspace listeners. The SDK handles content pause/resume; the wrapper does not duplicate it.
The result is named `artplayerPluginVast` and registered at `art.plugins.artplayerPluginVast`. Its synchronous `destroy()` releases the current session. While the core remains alive, retained context init/playUrl/playRes methods can create a new session. A recreated SDK player is a different object; reinstall your own SDK subscriptions on it.
Core destruction makes this attachment terminal: late SDK completion cannot invoke the callback or allocate a container, init returns null and request methods become inert. It does not cancel a shared SDK script load for other instances. SDK loading or callback failure cleans up the attachment and preserves the original rejection value. Cleanup errors can be observable while remaining resources are still released. Plugin destroy and core destroy therefore have different restart semantics.
Real ad playback depends on an available response, IMA, browser policies and devices. When SDK loading is blocked, mocks, guide navigation or continued main-content playback cannot count as successful ads. Validate those separately on the target network and device.
## TypeScript entries
The root and `/legacy` preserve npm1.0.0's required callback, any SDK fields and inaccurate synchronous name-only result. Plain historical replacement factories do not need a `.default` property. Registration has always been asynchronous. Use `/runtime` for accurate types, optional callback, the second parameter and destroy, with the same implementation:
```ts
import vast from 'artplayer-plugin-vast/runtime';
import type { RuntimeResult } from 'artplayer-plugin-vast/runtime';
const installPublished = vast(({ imaPlayer }) => {
imaPlayer.addEventListener('AdStarted', () => console.log('Ad started'));
});
const installWorkspace = vast((context) => {
context.playerOptions.autoResize = false;
context.init()?.addEventListener('AdStarted', () => console.log('Ad started'));
}, { compatibility: 'workspace-1.2' });
function releaseAd(result: RuntimeResult): void {
result.destroy();
}
```
Runtime exports RequestConfig, CompatibilityOptions, Context, PublishedContext, WorkspaceContext, RuntimeContext, RuntimeCallback, RuntimeResult, Registration and RuntimeFactory, plus the historical workspace aliases ArtplayerPluginVastOption and ArtplayerPluginVastInstance. Those aliases describe the workspace callback and awaited result, respectively. CommonJS without interop can use runtime `import = require`, supporting both direct and `.default` calls. These types do not modify the old root declaration.
===== packages/artplayer-vitepress/docs/en/plugin/vtt-thumbnail.md =====
# VTT Thumbnail
[中文](../../plugin/vtt-thumbnail.md)
Load a WebVTT thumbnail index and show the selected sprite region when hovering over the progress bar. Generate the index and images beforehand; this plugin does not scan the video or require another SDK.
This page describes the current refactor branch. Parser and lifecycle fixes and the precise `/runtime` types are not published yet. An unpinned npm or CDN installation is not evidence of this branch's behavior.
## Installation
```sh
yarn add artplayer artplayer-plugin-vtt-thumbnail
```
```js
import Artplayer from 'artplayer';
import artplayerPluginVttThumbnail from 'artplayer-plugin-vtt-thumbnail';
```
For script loading, load ArtPlayer first, followed by `dist/artplayer-plugin-vtt-thumbnail.js`. The global is `artplayerPluginVttThumbnail`. Pin dependency versions and make the VTT and images accessible. Cross-origin VTT requests require appropriate server CORS headers.
## Complete example
This is the exact code from the [online thumbnail example](https://artplayer.org/?libs=./uncompiled/artplayer-plugin-vtt-thumbnail/index.js&example=vtt.thumbnail). The demo site supplies the media and `.artplayer-app` container; replace them in your application.
<div className="run-code" data-libs="./uncompiled/artplayer-plugin-vtt-thumbnail/index.js">▶ Run Code</div>
```js
// npm i artplayer-plugin-vtt-thumbnail
// import artplayerPluginVttThumbnail from 'artplayer-plugin-vtt-thumbnail';
const art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/bbb-video.mp4',
plugins: [
artplayerPluginVttThumbnail({
vtt: '/assets/sample/bbb-thumbnails.vtt',
}),
],
})
```
## Options
The options object passed to `artplayerPluginVttThumbnail(option)` is required.
| Field | Type | Behavior |
| --- | --- | --- |
| `vtt` | `string`, optional in the declaration | VTT file URL. Supply a valid URL in practice: omission fetches an empty URL, meaning the current page, rather than disabling the plugin. |
| `style` | Optional `Partial<CSSStyleDeclaration>` | Initial inline styles on the thumbnail control, such as `borderRadius: '4px'`. |
Rendering updates display, width, height, left, backgroundImage and backgroundPosition. Initial styles cannot permanently override these properties. Each cue supplies the crop dimensions; the plugin does not automatically scale the sprite.
## Index format and image paths
```text
WEBVTT
00:00.000 --> 00:05.000
bbb-sprite.jpg#xywh=0,0,128,72
00:05.000 --> 00:10.000
bbb-sprite.jpg#xywh=128,0,128,72
```
The four values `x,y,w,h` are the left position, top position, width and height within the image, in pixels. x/y must be nonnegative and w/h positive; all four must be finite numbers. Each cue has one image URL line with a crop fragment. Ordinary subtitle text is not a thumbnail index.
Relative images are joined to the directory of the **supplied VTT URL**. For example, `bbb-sprite.jpg` inside `/assets/sample/bbb-thumbnails.vtt` becomes `/assets/sample/bbb-sprite.jpg`. Root-relative URLs and full URLs with supported protocols are kept as supplied. The directory is not recalculated from a redirected HTTP response URL. Prefer explicit image URLs when redirects or complex relative paths are involved.
The parser supports a BOM, common line endings, optional cue identifiers and timing settings, and skips NOTE, STYLE and REGION blocks. It is a thumbnail index parser, not a complete WebVTT subtitle layout engine.
## Timing and display boundaries
For historical compatibility, start and end times are rounded down to whole seconds. Both interval endpoints are included, and the first matching cue in file order wins. At exactly 5 seconds in the example above, the first image still applies; the second appears after 5 seconds. Do not assume millisecond precision or exclusive end times.
Desktop hover selects a cue using the progress percentage multiplied by the video duration. Gaps hide the preview, and previews near the edges are aligned inward. The mobile path responds to progress dragging with an input event and hides about 500ms after the last drag update. Desktop coverage does not establish real touch-device support.
The selected position must be strictly inside the progress bar. Desktop progress at exactly 0 or 1 hides the preview. Those mobile endpoints do not draw a new preview, but an existing preview still hides on its timer. The plugin uses control name `vtt-thumbnail` and CSS class `art-control-thumbnails`; preserve these existing hooks.
## Asynchronous registration, errors and cleanup
Registration fetches and parses the VTT and actually returns a Promise. Its success result contains only `name: 'artplayerPluginVttThumbnail'`. Installation through the constructor's plugins array is asynchronous; do not assume that the result is registered immediately after construction.
To wait explicitly and handle request or parse errors, call `art.plugins.add()` once after constructing the player:
```ts
import Artplayer from 'artplayer';
import thumbnails from 'artplayer-plugin-vtt-thumbnail/runtime';
const art = new Artplayer({
container: '.artplayer-app',
url: '/video/movie.mp4',
});
async function installThumbnails() {
try {
const result = await art.plugins.add(thumbnails({ vtt: '/video/movie.vtt' }));
console.log(result.name);
}
catch (error) {
console.error('Unable to load thumbnails', error);
}
}
void installThumbnails();
```
Request and format errors reject registration; format errors include a line number. Registration completion establishes that the VTT was parsed and the control created, not that every image has decoded. Images load when the browser displays them; a later image failure does not reject an already settled registration Promise.
There is no update, reload or independent destroy method. Switching the main video does not refetch the VTT. For a different video and thumbnail set, you can destroy and recreate the player. Removing a control is not a complete uninstall; repeated installation is not an update API.
Destroying the player cancels outstanding requests where AbortController is available, settles canceled registration, removes owned listeners and timers, and removes the control only if this installation still owns it. Cancellation still returns the name object, so the name alone does not prove an image is available. Late requests cannot remount the interface.
## TypeScript compatibility
The root and `/legacy` entrances preserve the latest published 1.1.0 synchronous return declaration and replacement-function shape, although registration is asynchronous at runtime. The `/runtime` entrance above uses the same JavaScript implementation and accurately declares the Promise and runtime `.default` self-alias. It also exposes `Option`, `Result`, `Factory` and `RuntimeFactory` types.
The older 1.0.x `export =` shape cannot preserve the same type extraction as the 1.1.0 default export. TypeScript consumers relying on those earlier CommonJS declarations should migrate to `/runtime`. NodeNext ESM consumers should also prefer this entrance to avoid the historical namespace shape retained by the root. Legal older JavaScript calls and distribution file entrances remain available.
Browser checks cover cropping and cleanup with published and candidate cores. Complete mobile, plugin combination and release-artifact acceptance remains tracked separately.
===== packages/artplayer-vitepress/docs/en/proxy/canvas.md =====
# Canvas video proxy
[简体中文](../../proxy/canvas.md)
Decode audio/video with a real video element, draw its picture to Canvas and optionally post-process each draw. Install through ArtPlayer's proxy option, not its plugins array. This page describes the unreleased branch; decoding, pixel access and device limitations still apply.
## Install and example
```sh
yarn add artplayer artplayer-proxy-canvas
```
ESM uses `import canvas from 'artplayer-proxy-canvas'`. Scripts load `dist/artplayer-proxy-canvas.js`, exposing `artplayerProxyCanvas`. The [original example](https://artplayer.org/?libs=./uncompiled/artplayer-proxy-canvas/index.js&example=canvas) is retained below:
<div className="run-code" data-libs="./uncompiled/artplayer-proxy-canvas/index.js">▶ Run Code</div>
```js
// npm i artplayer-proxy-canvas
// import artplayerProxyCanvas from 'artplayer-proxy-canvas';
const art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
poster: '/assets/sample/poster.jpg',
volume: 0.5,
autoplay: false,
autoSize: false,
screenshot: true,
setting: true,
loop: true,
flip: true,
pip: true,
playbackRate: true,
aspectRatio: true,
fullscreen: true,
fullscreenWeb: true,
miniProgressBar: true,
autoPlayback: true,
autoOrientation: true,
subtitle: {
url: '/assets/sample/subtitle.srt',
},
thumbnails: {
url: '/assets/sample/thumbnails.png',
number: 60,
column: 10,
scale: 0.85,
},
proxy: artplayerProxyCanvas(),
})
```
The optional argument is a drawing callback, not an options object:
```js
proxy: artplayerProxyCanvas((context, video) => {
context.fillStyle = 'rgba(0, 0, 0, 0.4)';
context.fillRect(0, 0, context.canvas.width, 32);
context.fillStyle = '#fff';
context.fillText(video.currentTime.toFixed(1), 10, 22);
})
```
It receives the actual CanvasRenderingContext2D and backing HTMLVideoElement. The base picture is drawn and any acquired ImageBitmap closed before the synchronous callback. Return values are unused; an async callback's Promise is not awaited. Set required Canvas state yourself; changing dimensions resets drawing state.
## Returned element and media surface
Initialization synchronously returns an actual HTMLCanvasElement for ArtPlayer's media position. The backing video is connected to the player before playback, transparent and unfocusable, without another visible player.
Native Canvas members win: width/height, DOM events, getContext and toDataURL remain Canvas operations. Only enumerated video members absent from Canvas are forwarded; this is not a complete runtime copy of HTMLVideoElement. Access the real video through the draw callback when needed.
Backing media events are forwarded as ArtPlayer `video:<type>` events with the original Event. Canvas addEventListener('play', ...) is not a backing-video listener; use art.on('video:play', ...). Assigning media src/srcObject or invoking load invalidates old draws.
Normal ArtPlayer subtitle options remain supported. The active proxy's appendChild specially routes HTML tracks to the video; ordinary nodes still belong to Canvas. Core owns subtitle track replacement and URLs. The initial empty metadata track is removed before the first real subtitle is attached. Its final parent is VIDEO.
## Drawing, dimensions and events
| Trigger | Behavior |
| --- | --- |
| Play | One RAF drawing chain without overlapping asynchronous acquisitions |
| Pause/source emptying | Cancel pending draws, keeping the displayed picture |
| Paused seek | Request one fresh picture |
| Resize | Coalesce requests and invalidate old asynchronous results |
| loadedmetadata | Set Canvas dimensions when intrinsic video size is valid |
| autoSize=false | Fit the video aspect ratio into the container with centering padding on resize |
| autoSize=true | Skip that proxy fit; let the player handle automatic sizing |
Drawing needs readyState>=2, no seek in progress, and valid video/Canvas dimensions. It uses createImageBitmap when available, otherwise drawImage(video). This does not guarantee a callback for every decoded frame. Cross-origin CORS settings/responses still determine screenshot and pixel-read access.
| Custom event | Arguments and order |
| --- | --- |
| `artplayerProxyCanvas:draw` | Context and backing video, after a successful callback |
| `artplayerProxyCanvas:error` | Original failure value from drawing, callback or initialization |
A missing2D context reports an error. The specific first-frame createImageBitmap InvalidStateError before decoding waits for another draw request; callback/drawImage errors are not broadly treated as first-frame delays. Destroying the player in the callback prevents a subsequent draw event or restarted loop.
## Cleanup and capabilities
Player destruction cancels RAF/deferred setup, removes internal subscriptions, pauses/unloads the video, clears srcObject and releases Canvas buffers. Late bitmaps are still closed. Clearing the stream reference does not stop caller-owned media tracks. Cleanup attempts all resources before throwing its first error. Escaped media methods/forwarded setters cannot restart playback or source loading; native Canvas methods still act on the same element.
There is no separate public start/stop/destroy control object; use ArtPlayer lifecycle. The original example's PiP/fullscreen/screenshot options remain subject to actual browser capabilities. Their presence is not proof of universal support, and Windows results do not establish Safari/iPhone/Android acceptance.
## TypeScript and historical types
Root and `/legacy` retain published1.1.0's optional callback, plain factory and exact HTMLCanvasElement result. Ordinary replacement functions remain assignable. The conflicting1.0 export= required-callback shape follows the approved latest-root policy. Use `/runtime` for accurate ESM/older CommonJS invocation and self-alias types with the same implementation:
```ts
import Artplayer from 'artplayer';
import canvas from 'artplayer-proxy-canvas/runtime';
import type { MediaCanvas, Option } from 'artplayer-proxy-canvas/runtime';
const overlay: Option = (context, video) => {
context.fillText(video.currentTime.toFixed(1), 10, 22);
};
const art = new Artplayer({ container: '#player', url: '/movie.mp4', proxy: canvas(overlay) });
const media = art.template.$video as MediaCanvas;
function play(): Promise<void> { return media.play(); }
```
Public types are Option, Result, Factory, Callable, MediaCanvas and RuntimeFactory. MediaCanvas is an explicit view preserving native Canvas collisions; it adds no runtime capabilities and does not narrow factory return inference. The root's historical NodeNext ESM namespace remains; use runtime for accurate default calls. JavaScript root and `.default` reference the same factory; runtime types describe the alias as readonly. CommonJS can require the function directly, and older TS import=require callers can choose runtime.
===== packages/artplayer-vitepress/docs/en/proxy/mediabunny.md =====
# Mediabunny video proxy
[简体中文](../../proxy/mediabunny.md)
Read media through Mediabunny and expose a video-like surface backed by Canvas and an audio pipeline, including HLS quality/audio selection. Install through ArtPlayer's proxy option. This describes the unreleased branch; it does not supply missing decoders or every HTMLVideoElement capability.
## Install and example
```sh
yarn add artplayer artplayer-proxy-mediabunny
```
ESM uses `import mediabunny from 'artplayer-proxy-mediabunny'`. Scripts load `dist/artplayer-proxy-mediabunny.js`, exposing `artplayerProxyMediabunny`. This is the [original HLS example](https://artplayer.org/?libs=./uncompiled/artplayer-proxy-mediabunny/index.js&example=mediabunny); its remote stream is not an offline fixture:
<div className="run-code" data-libs="./uncompiled/artplayer-proxy-mediabunny/index.js">▶ Run Code</div>
```js
// npm i artplayer-proxy-mediabunny
// import artplayerProxyMediabunny from 'artplayer-proxy-mediabunny';
const art = new Artplayer({
container: '.artplayer-app',
url: 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8',
autoSize: true,
setting: true,
loop: true,
flip: true,
playbackRate: true,
fullscreen: true,
fullscreenWeb: true,
miniProgressBar: true,
autoPlayback: true,
autoOrientation: true,
proxy: artplayerProxyMediabunny({
m3u8: {
quality: {
control: true,
setting: true,
getName: level => level.height ? `${level.height}P` : level.name,
title: 'Quality',
auto: 'Auto',
},
audio: {
control: true,
setting: true,
getName: track => track.name || track.language,
title: 'Audio',
auto: 'Auto',
},
},
}),
})
```
The optional-options factory synchronously initializes an actual HTMLCanvasElement and installs art.mediabunny. Native Canvas members win collisions: DOM event/attribute methods do not become shim methods. Use ArtPlayer video:* events or the explicit shim for media operations.
## Inputs and options
| Option | Default or behavior |
| --- | --- |
| `source` | Initial source; a truthy value precedes art.option.url, but later host assignments can replace it |
| `loadTimeout` | 0, no deadline; a finite positive value limits loading in milliseconds |
| `timeupdateInterval` | 250ms renderer update interval, not an exact clock guarantee |
| `avSyncTolerance` | 0.12 seconds, used in the late-frame threshold when dropping is enabled |
| `dropLateFrames` | false; enable to skip sufficiently late pictures |
| `poster` | Empty by default; captured by the video engine at initialization |
| `preflightRange` | false; optional HEAD check for ordinary URLs |
| `volume` / `muted` | Initial shim values0.7 / false; later player settings may override them |
| `autoplay` / `loop` | Compatibility readback values defaulting false, not standalone autoplay/loop execution |
| `crossOrigin` | Compatibility readback defaulting empty; does not configure SDK fetch credentials/CORS |
| `m3u8` | Optional quality/audio menus, below |
Use ArtPlayer's own autoplay/loop configuration for player behavior, subject to browser playback policy. Shim autoplay/loop/crossOrigin setters remain inert. Changing shim.poster updates its option readback, not necessarily the poster already captured by the video engine.
The source declaration accepts URL strings, Blobs and byte ReadableStreams. Runtime also passes SDK Source/SourceRef values to SDK validation. Case-insensitive `.m3u8` suffixes, optionally followed by query/hash, select HLS_FORMATS; other sources use ALL_FORMATS. A URL without that suffix does not enable this proxy's HLS menu state merely from response content. Random access/reloading a stream depends on its source capabilities.
When enabled, preflight sends HEAD only for non-HLS strings. Missing accept-ranges or a value of none emits error with a RangeNotSupported Event detail and stops that preflight path, without setting shim.error. Network failures warn and continue; HLS/non-string sources skip HEAD. This is not a complete Range GET or codec-support check. Media responses must still be readable with suitable CORS.
## HLS menus and selection
Both m3u8.quality and m3u8.audio accept control, setting, title, auto and getName. No menu appears unless control or setting is enabled; setting also needs the player's settings panel.
| Setting | Default |
| --- | --- |
| `quality.title` / `audio.title` | Quality / Audio |
| Either `auto` | Auto |
| `quality.getName(level)` | level.name, otherwise height plus P |
| `audio.getName(track)` | First available name, lang or language |
Levels provide id, index, name, height and bitrate; audio entries provide id, index, name, lang, language and bitrate. Full state also includes SDK track objects. Name can be null; return a displayable string from getName.
Quality needs video levels; audio needs at least two pairable tracks. Quality sorts by descending height. Equal labels are merged, favoring the selected item. Highlight/button text follows the actual current track, so auto mode may still display its chosen track rather than Auto.
```js
const shim = art.mediabunny;
const state = await shim.getM3u8State();
if (state?.levels.length) {
await shim.switchM3u8Quality(state.levels[0].id);
}
await shim.switchM3u8Audio('auto');
```
Pass a numeric state id or literal 'auto', not an index, label or numeric string. Auto selects the SDK primary track; the proxy does not thereby measure bandwidth and continuously adapt bitrate. Quality changes retain pairable audio when possible, otherwise choose primary pairable audio; audio selection also maintains video pairing. Unknown IDs retain the current track. Methods are no-ops without an active HLS source.
Menu names are mediabunny-quality and mediabunny-audio. Metadata/restart refreshes them; loadstart/error/destruction clears them. A source without a capability removes its old UI. Stale menu callbacks and superseded choices cannot replace current state. Active asynchronous selection failures can reject.
## Shim members
| Member | Behavior |
| --- | --- |
| `canvas` | Native output element |
| `src` / `currentSrc` | Source value; truthy src assignments load, empty assignments do not unload |
| `play()` / `pause()` / `load()` | Promise playback, synchronous pause, reload current src; load has no completion Promise |
| `currentTime` | Seconds; assignment starts an asynchronous seek without an awaitable setter |
| `duration` | Seconds, possibly NaN when unknown or Infinity for live input |
| `volume` / `muted` | Volume is numerically coerced/clamped0–1 and unmutes; muted is boolean-coerced |
| `playbackRate` | Defaults1; accepts coerced positive non-NaN values and emits ratechange |
| `paused` / `playing` / `ended` / `seeking` | Coordinator states; playing does not prove a real frame is displayed |
| `readyState` / `networkState` / `error` | Compatibility state and `{code, message}` or null |
| `videoWidth` / `videoHeight` | Video-engine dimensions |
| `buffered` / `played` / `seekable` | Synthetic0-to-duration/current-time/duration ranges, not measured network buffering |
| `createTimeRanges(start, end)` | Same synthetic helper, length0 or1; does not implement native index-range exceptions |
| `canPlayType(type)` | Always maybe, without probing support |
| `getM3u8State()` | Promise of state, or null for non-HLS/obsolete state |
| `switchM3u8Quality(value)` / `switchM3u8Audio(value)` | Promise-based track selection |
| `addEventListener` / `removeEventListener` | Shim media events, not Canvas DOM listeners |
| `getBoundingClientRect()` | Delegate to Canvas |
| `setAttribute(name, value)` | Shim handles src/muted, uses inert autoplay/loop setters, otherwise delegates to Canvas |
| `destroy()` | Synchronous terminal shim/engine cleanup, idempotent |
Poster/autoplay/loop/crossOrigin follow the rules above. Controls=false, playsInline=true, preload='auto', defaultMuted=false and defaultPlaybackRate=1 have inert setters. Direct canvas.setAttribute is native Canvas behavior, unlike shim.setAttribute. Normally use art.destroy to also release host menus, alias and subscriptions.
HlsState contains levels, audios, currentLevel, currentAudio, videoMode and audioMode. Current entries may be null; modes are auto/manual. Audios are pairable with the selected video, so audio-only input does not guarantee menu entries. Narrow unknown track objects with the SDK types used by your application.
## Events, frame callbacks and cleanup
Shim events forward to ArtPlayer video:* as Event plus detail. Ordinary listeners retain duplicates, first-match removal, live-array iteration and exception propagation. This is not the complete native EventTarget API and does not accept native listener options.
Successful loading publishes loadedmetadata/durationchange/progress after participants prepare, then loadeddata/canplay/canplaythrough/progress. Waiting/loadstart remain deferred notifications. If neither selected track can decode, report code4 without success readiness; one usable track can support partial playback. Seek emits seeking/waiting, then seeked; track replacement also refreshes metadata/readiness. Each step checks for newer operations/destruction.
requestVideoFrameCallback(callback) returns a cancellable RAF ID for one synthetic notification, not a newly decoded-frame guarantee; it may run while paused. Callback now/captureTime/receiveTime use RAF time, expectedDisplayTime estimates now+16.6, and presentationTime/mediaTime are media seconds. Width/height come from the engine; presentedFrames, processingDuration and rtpTimestamp are always0. Do not use these values as decoder-throughput or network-latency measurements.
Source changes, cancellation and destruction invalidate old asynchronous work. Pause cancels pending playback and prevents old seeks from resuming it. Superseded operations may resolve normally; resolution does not prove their original intent ran. Active play/selection errors can reject. Src/load errors report through media events, normally code4; Range preflight is the separate path described above.
Destruction cancels input/preflight, frame callbacks, timers, audio nodes/context and decoder resources. Host cleanup also removes owned UI/subscriptions and deletes art.mediabunny only if it still belongs to this proxy. Late results cannot reactivate it. Cleanup is not a Promise of physical GPU/browser reclamation; long-duration and real-device acceptance remain separate.
## TypeScript
Root and `/legacy` preserve optional options and exact HTMLCanvasElement return inference. There is no `/runtime` subpath. Use explicit views for media capabilities:
```ts
import Artplayer from 'artplayer';
import mediabunny from 'artplayer-proxy-mediabunny';
import type { MediaBunnyPlayer } from 'artplayer-proxy-mediabunny';
const art: MediaBunnyPlayer = new Artplayer({
container: '#player', url: '/movie.m3u8',
proxy: mediabunny({ m3u8: { quality: { control: true } } }),
});
async function selectFirstLevel(): Promise<void> {
const shim = art.mediabunny;
if (!shim) return;
const state = await shim.getM3u8State();
if (state?.levels.length) await shim.switchM3u8Quality(state.levels[0].id);
}
```
Public types are Option, Result, HlsLevel, HlsAudio, HlsState, MediaBunnyCanvas, MediaBunnyPlayer, MediaBunnyShim, MediaListener, SyntheticFrameCallback and SyntheticFrameMetadata. The Canvas view preserves native collisions; the optional player alias is removed on host destruction. CommonJS runtime supports direct and historical .default calls while the factory type remains plain callable. The legacy build does not polyfill WebCodecs, Web Audio or other browser capabilities.
===== packages/artplayer-vitepress/docs/en/start/i18n.md =====
# Language Settings
::: danger
Due to the increasing number of bundled multilingual resources, starting from version `5.1.0`, the core `artplayer.js` code will no longer bundle any languages other than `Simplified Chinese` and `English`. You will need to manually import any other languages you require.
:::
:::warning
When a language cannot be matched, English will be displayed by default. For i18n syntax reference, see: [artplayer/types/i18n.d.ts](https://github.com/zhw2590582/ArtPlayer/blob/master/packages/artplayer/types/i18n.d.ts)
:::
## Default Languages
The default languages are: `en`, `zh-cn`. No manual import is required.
```js{4}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
lang: 'zh-cn', // or 'en'
});
```
## Importing Languages
Language files before bundling are located at: `artplayer/src/i18n/*.js`. Contributions for new languages are welcome.
Bundled language files are located at: `artplayer/dist/i18n/*.js`
::: code-group
```js [import]
import id from 'artplayer/i18n/id';
import zhTw from 'artplayer/i18n/zh-tw';
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
i18n: {
id: id,
'zh-tw': zhTw,
},
lang: 'zh-tw',
});
```
```js [script]
<script src="artplayer/dist/i18n/id.js"></script>
<script src="artplayer/dist/i18n/zh-tw.js"></script>
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
i18n: {
id: window['artplayer-i18n-id'],
'zh-tw': window['artplayer-i18n-zh-tw'],
},
lang: 'zh-tw',
});
```
:::
## Adding a New Language
```js{4-9}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
lang: 'your-lang',
i18n: {
'your-lang': {
Play: 'Your Play'
},
},
});
```
## Modifying a Language
```js
import zhTw from 'artplayer/i18n/zh-tw';
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
i18n: {
// Change the default language
'zh-cn': {
Play: 'Your Play'
},
// Change the imported language
'zh-tw': {
...zhTw,
Play: 'Your Play'
},
},
});
```
===== packages/artplayer-vitepress/docs/en/start/option.md =====
# Basic Options
## Construction and option ownership {#construction-contract}
`new Artplayer(option, readyCallback?)` is synchronous. It requires a browser and a DIV container, either directly or selected by a CSS selector. The default getter includes '#artplayer', but constructor input must supply container: the input value replaces that default before validation. One active instance owns each container. Without useSSR, mounting replaces its contents; with useSSR, insert the matching Artplayer.html first. This flag does not enable server-side construction or repair incomplete markup.
Each construction reads a fresh Artplayer.option, merges the input, validates it, then creates the subsystems. Validation failure happens before DOM mounting or proxy invocation. Initialization failures release owned resources and restore the captured container; a successful constructor return does not mean media, subtitles, asynchronous plugins, or external SDKs are ready.
The second argument is the ready callback, not an option named onReady. A normal function receives the player as both this and its argument. It is registered after constructor plugins and runs from the first successfully handled canplay; it does not await plugin Promises. An empty URL is valid and can be assigned later. The numeric art.id is separate from option.id, which is the optional playback-memory key.
### Merge and validation {#option-merge}
- Artplayer.option returns fresh nested defaults on every access; modifying one returned object does not set global defaults. lang reads the current navigator.language in lowercase; moreVideoAttr.preload uses the module's Safari detection. In non-browser inspection lang may be undefined, but construction still requires a browser.
- Merging creates a new top-level object and recursively merges objects present on both sides. Arrays use the existing concat/spread rule, so the array itself is new but its ordinary object items retain identity. Function and Element references are retained; this is not a complete deep clone. Components may later add metadata to shared item objects.
- Own enumerable extension fields survive without schema validation. Inherited fields are generally omitted, except container is read explicitly from the input again. Accessor properties can therefore run more than once. An explicit undefined can replace a required default and cause a validation error; omission and undefined are not interchangeable.
- Artplayer.scheme is a shared mutable schema. Artplayer.validator(value, scheme) validates an already supplied value, returns that same value on success, and throws at the first invalid known field. It neither merges defaults nor mounts a player. Artplayer.kindOf is the validator's type classifier, not a media-capability test. Runtime types also expose the validator's optional diagnostic path argument.
- art.option is the resolved live object, not a reactive configuration API. Some handlers read it later, while other settings are captured or build UI only during initialization. Change playback through the documented setters and UI through the component managers; assigning a new option field does not automatically rebuild everything.
### Initial media values and precedence {#option-precedence}
moreVideoAttr is copied through art.attr as media **properties**, not setAttribute calls. A value of undefined follows attr's getter behavior and does not write. The core then applies truthy muted, volume, poster, autoplay, playsInline and theme settings, CSS variables, and the URL. Do not depend on object-key order to override these later steps.
The historical volume initialization only assigns a truthy option.volume, clamped to [0,1]; volume:0 therefore skips that assignment. A numeric saved storage volume is applied afterward and overrides it. For explicit initial silence, use muted:true; set art.volume after construction when you need to override stored volume. False autoplay/muted/playsInline values do not undo properties already set through moreVideoAttr. Browser autoplay and inline-playback policies still apply.
A nonempty theme writes '--art-theme' into the resolved cssVar object before those styles are applied, taking precedence over a conflicting initial cssVar value. poster uses the player's background layer. loop is implemented by the ended handler seeking to zero and calling play; it is distinct from setting the native video.loop property through moreVideoAttr.
### UI, platform, and content options {#option-capabilities}
| Options | Actual scope |
| --- | --- |
| isLive | Selects live UI and disables the normal seek/time controls and several VOD helpers; it does not detect a stream protocol or install a decoder |
| flip, playbackRate, aspectRatio | Enable selector entries in desktop context menus and, with setting enabled, the settings panel. They are feature flags, not initial flip/rate/ratio values |
| setting, settings | Enable the settings UI and provide its entries; the registry still exists when the panel is disabled |
| screenshot, pip, fullscreen, fullscreenWeb, airplay | Request the corresponding controls. Screenshot UI is desktop-only; AirPlay also checks the native availability API. A button flag does not grant browser permissions or guarantee support |
| hotkey | Enables built-in desktop shortcuts; false does not remove the public manager or prevent manually registered shortcuts |
| gesture | Enables mobile video-surface seeking gestures for VOD. The progress-surface gesture binding is separate and remains when this flag is false |
| lock, fastForward, autoOrientation | Install mobile-only helpers; fastForward is also VOD-only. Changing the flags later does not install missing plugins |
| miniProgressBar, autoPlayback | Install VOD-only helpers. Playback memory uses id or the current URL and requires storage; it is not browser autoplay permission |
| autoMini, autoSize | React to viewport events / standard-mode resize paths. They do not guarantee initial visibility detection or continuous ResizeObserver behavior |
| mutex | After a successful art.play, pause other registered instances. Direct native video.play bypasses that custom-method step |
| backdrop, playsInline | Add the initial backdrop CSS class / write inline-playback properties. Actual CSS and media behavior remain browser-dependent |
| layers, controls, contextmenu | Initial component entries; keep their required html and, for controls, position. Constructor context menus are desktop-only. See the component guides for callback receivers and cleanup |
| quality | Initial selector items need string html and URL under the constructor schema. default marks the label/selection; it does not replace option.url or load that URL. Initial selector installation is deferred; later selection invokes switchQuality |
| highlight | time/text markers rendered on metadata readiness. Times are clamped to duration for positioning, text is stored as text, and this is not a chapter playback API |
| lang, i18n, icons | Initial language, dictionaries and icon overrides. Their dedicated managers and guides describe fallback, node ownership and later updates |
### Nested defaults and media adapters {#option-nested}
The actual thumbnails defaults are `{ url: '', number: 60, column: 10, width: 0, height: 0, scale: 1 }`. They describe a sprite, not a video URL or thumbnail generator. Width/height are multiplied by scale when supplied; otherwise width comes from image width/column and height from the video ratio. Cells are zero-based in row order. Loading is lazy on progress interaction. Later art.thumbnails assignment replaces the object without constructor default merging; provide the geometry you need. Empty/live assignments are ignored by that setter.
The actual subtitle defaults include empty url/type/name, an empty style object, escape:true, encoding:'utf-8', and an identity onVttLoad callback. Partial constructor input merges these defaults. See the subtitle manager guide for conversion, track readiness, per-call overrides and Blob URL ownership; a constructor return does not mean the track loaded.
type is an explicit customType lookup key; otherwise getExt derives the key from the URL. The core does not normalize a supplied type or install SDKs from extension names. A matching callback receives (video, url, art) with this===art after the owned initialization delay. Its Promise is observed for failure but does not define media readiness; the adapter must set up the media surface and native events and clean up its resources. Without a matching callback, the URL is assigned directly to video.src. Optional art.hls/art.flv fields in types do not instantiate those libraries.
proxy runs earlier, during template mounting, before art.template, video/query/proxy getters and most managers are usable. Return an actual HTMLVideoElement or HTMLCanvasElement; an arbitrary object or undefined is rejected at runtime despite the older proxy type permitting undefined. The node replaces the template video and its className becomes art-video. A canvas needs its media-like properties/methods/events supplied by the adapter; it is not made playable automatically.
### Constructor TypeScript views {#option-types}
The root Option retains its historical required URL/read shape; OptionInput permits an omitted URL and numeric component HTML. runtime exposes resolved options and accurate callback receivers: ProxyHost is deliberately limited, component callbacks can see later managers as optional, and PluginHost does not assume art.plugins was assigned during its own construction. These are type views of the same constructor, not separate runtime implementations. A permissive historical type does not bypass runtime schema checks; for example, constructor quality labels must still be strings.
```ts
import LegacyArtplayer from 'artplayer';
import type { OptionInput as LegacyInput } from 'artplayer';
import Artplayer from 'artplayer/runtime';
import type { OptionInput, ProxyHost } from 'artplayer/runtime';
const input: LegacyInput = { container: '#legacy', controls: [{ name: 'count', html: 42, position: 'left' }] };
new LegacyArtplayer(input);
const options: OptionInput = {
container: '#player',
proxy: function (art: ProxyHost) {
const same: boolean = this === art;
void same;
return document.createElement('video');
},
plugins: [function (art) {
const pending = art.plugins;
void pending;
return { name: 'example' };
}],
};
new Artplayer(options, function (art) {
const same: boolean = this === art;
void same;
});
```
## `container`
- Type: `String, Element`
- Default: `#artplayer`
The `DOM` container where the player is mounted.
<div className="run-code">▶ Run Code</div>
```js{2}
var art = new Artplayer({
container: '.artplayer-app',
// container: document.querySelector('.artplayer-app'),
url: '/assets/sample/video.mp4',
});
```
You may need to set the size of the container element, for example:
```css{2-3}
.artplayer-app {
width: 400px;
height: 300px;
}
```
Or use `aspect-ratio`:
```css{2}
.artplayer-app {
aspect-ratio: 16/9;
}
```
:::warning Note
Among all options, only `container` is required.
:::
## `url`
- Type: `String`
- Default: `''`
The video source URL.
<div className="run-code">▶ Run Code</div>
```js{3}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
```
Sometimes the `url` is not known immediately. In such cases, you can set the `url` asynchronously.
<div className="run-code">▶ Run Code</div>
```js{6}
var art = new Artplayer({
container: '.artplayer-app',
});
setTimeout(() => {
art.url = '/assets/sample/video.mp4';
}, 1000);
```
:::warning Note
By default, three video file formats are supported: `.mp4`, `.ogg`, `.webm`.
To play other formats like `.m3u8` or `.flv`, please refer to the `Third-party Libraries` section on the left.
:::
## `id`
- Type: `String`
- Default: `''`
The unique identifier for the player. Currently used only for playback memory `autoplayback`.
<div className="run-code">▶ Run Code</div>
```js{2}
var art = new Artplayer({
id: 'your-url-id',
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
});
```
## `onReady`
- Type: `Function`
- Default: `undefined`
The constructor accepts a function as the second parameter. This function is triggered when the player is successfully initialized and the video is ready to play, similar to the `ready` event.
<div className="run-code">▶ Run Code</div>
```js{7-9}
var art = new Artplayer(
{
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
muted: true,
},
function onReady(art) {
this.play()
},
);
```
Equivalent to:
```js{7-9}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
muted: true,
});
art.on('ready', () => {
art.play();
});
```
:::warning Note
Inside the callback function, `this` refers to the player instance. However, if an arrow function is used for the callback, `this` will not point to the player instance.
:::
## `poster`
- Type: `String`
- Default: `''`
The video poster image, which only appears when the player is initialized and not yet playing.
<div className="run-code">▶ Run Code</div>
```js{4}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
poster: '/assets/sample/poster.jpg',
});
```
## `theme`
- Type: `String`
- Default: `#f00`
The player's theme color, currently used for the `progress bar` and `highlighted elements`.
<div className="run-code">▶ Run Code</div>
```js{4}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
theme: '#ffad00',
});
```
## `volume`
- Type: `Number`
- Default: `0.7`
The player's default volume.
<div className="run-code">▶ Run Code</div>
```js{4}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
volume: 0.5,
});
```
:::warning Note
The player caches the last volume setting. Upon the next initialization (e.g., page refresh), the player will read this cached value.
:::
## `isLive`
- Type: `Boolean`
- Default: `false`
Enable live streaming mode. This will hide the progress bar and playback time.
<div className="run-code">▶ Run Code</div>
```js{4}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
isLive: true,
});
```
## `muted`
- Type: `Boolean`
- Default: `false`
Whether to start muted by default.
<div className="run-code">▶ Run Code</div>
```js{4}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
muted: true,
});
```
## `autoplay`
- Type: `Boolean`
- Default: `false`
Whether to autoplay.
<div className="run-code">▶ Run Code</div>
```js{4}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
autoplay: true,
muted: true,
});
```
:::warning Note
If you want the video to autoplay immediately upon page load, `muted` must be set to `true`. For more information, please read [Autoplay Policy Changes](https://developers.google.com/web/updates/2017/09/autoplay-policy-changes).
:::
## `autoSize`
- Type: `Boolean`
- Default: `false`
By default, the player's dimensions fill the entire `container`, often resulting in black bars. This option automatically adjusts the player size to hide black bars, similar to `css`'s `object-fit: cover;`.
<div className="run-code">▶ Run Code</div>
```js{4}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
autoSize: true,
});
```
## `autoMini`
- Type: `Boolean`
- Default: `false`
Automatically enters `Mini Player` mode when the player scrolls out of the browser viewport.
<div className="run-code">▶ Run Code</div>
```js{4}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
autoMini: true,
});
```
## `loop`
- Type: `Boolean`
- Default: `false`
Whether to loop playback.
<div className="run-code">▶ Run Code</div>
```js{4}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
loop: true,
});
```
## `flip`
- Type: `Boolean`
- Default: `false`
Whether to display the video flip function. Currently only appears in the `Settings Panel` and `Context Menu`.
<div className="run-code">▶ Run Code</div>
```js{4}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
flip: true,
setting: true,
});
```
## `playbackRate`
- Type: `Boolean`
- Default: `false`
Whether to display the video playback speed function. It will appear in the `Settings Panel` and `Context Menu`.
<div className="run-code">▶ Run Code</div>
```js{4}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
playbackRate: true,
setting: true,
});
```
## `aspectRatio`
- Type: `Boolean`
- Default: `false`
Whether to display the video aspect ratio function. It will appear in the `Settings Panel` and `Context Menu`.
<div className="run-code">▶ Run Code</div>
```js{4}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
aspectRatio: true,
setting: true,
});
```
## `screenshot`
- Type: `Boolean`
- Default: `false`
Whether to display the `Screenshot` function in the bottom control bar.
<div className="run-code">▶ Run Code</div>
```js{4}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
screenshot: true,
});
```
:::warning Note
Due to browser security mechanisms, screenshotting may fail if the video source URL is cross-origin with the website.
:::
## `setting`
- Type: `Boolean`
- Default: `false`
Whether to display the toggle button for the `Settings Panel` in the bottom control bar.
<div className="run-code">▶ Run Code</div>
```js{4}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
setting: true,
});
```
## `hotkey`
- Type: `Boolean`
- Default: `true`
Whether to use hotkeys.
<div className="run-code">▶ Run Code</div>
```js{4}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
hotkey: true,
});
```
| Hotkey | Description |
| ------- | -------------------- |
| `↑` | Increase volume |
| `↓` | Decrease volume |
| `←` | Seek forward |
| `→` | Seek backward |
| `space` | Toggle play/pause |
:::warning Note
These hotkeys only take effect after the player gains focus (e.g., after clicking on the player).
:::
## `pip`
- Type: `Boolean`
- Default: `false`
Whether to display the `Picture-in-Picture` toggle button in the bottom control bar.
<div className="run-code">▶ Run Code</div>
```js{4}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
pip: true,
});
```
## `mutex`
- Type: `Boolean`
- Default: `true`
If multiple players exist on the page simultaneously, whether only one player is allowed to play at a time.
<div className="run-code">▶ Run Code</div>
```js{4}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
mutex: true,
});
```
## `backdrop`
- Type: `Boolean`
- Default: `true`
Whether to enable the backdrop blur effect for the player UI. When enabled, overlays such as the settings panel, context menu, and volume bar will apply a `backdrop-filter` frosted glass effect for a more transparent look. However, this may cause performance or compatibility issues on some low-performance devices or older browsers.
<div className="run-code">▶ Run Code</div>
```js{4}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
backdrop: false, // Disable frosted glass effect
});
```
## `fullscreen`
- Type: `Boolean`
- Default: `false`
Whether to display the player `Window Fullscreen` button in the bottom control bar.
<div className="run-code">▶ Run Code</div>
```js{4}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
fullscreen: true,
});
```
## `fullscreenWeb`
- Type: `Boolean`
- Default: `false`
Whether to display the player `Web Fullscreen` button in the bottom control bar.
<div className="run-code">▶ Run Code</div>
```js{4}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
fullscreenWeb: true,
});
```
## `subtitleOffset`
- Type: `Boolean`
- Default: `false`
Subtitle time offset, ranging from `[-5s, 5s]`. Appears in the `Settings Panel`.
<div className="run-code">▶ Run Code</div>
```js{4}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
subtitleOffset: true,
subtitle: {
url: '/assets/sample/subtitle.srt',
},
setting: true,
});
```
## `miniProgressBar`
- Type: `Boolean`
- Default: `false`
A mini progress bar that only appears when the player loses focus and is playing.
<div className="run-code">▶ Run Code</div>
```js{4}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
miniProgressBar: true,
});
```
## `useSSR`
- Type: `Boolean`
- Default: `false`
Whether to use SSR (Server-Side Rendering) mount mode. Useful if you want to pre-render the player's required HTML before the player is mounted.
You can access the player's required HTML via `Artplayer.html`.
<div className="run-code">▶ Run Code</div>
```js{7}
var $container = document.querySelector('.artplayer-app');
$container.innerHTML = Artplayer.html;
var art = new Artplayer({
container: $container,
url: '/assets/sample/video.mp4',
useSSR: true,
});
```
## `playsInline`
- Type: `Boolean`
- Default: `true`
Whether to use `playsInline` mode on mobile devices.
<div className="run-code">▶ Run Code</div>
```js{4}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
playsInline: true,
});
```
## `layers`
- Type: `Array`
- Default: `[]`
Initialize custom layers.
<div className="run-code">▶ Run Code</div>
```js{5-23}
var img = '/assets/sample/layer.png';
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
layers: [
{
name: 'potser',
html: `<img style="width: 100px" src="${img}">`,
style: {
position: 'absolute',
top: '20px',
right: '20px',
opacity: '.9',
},
click: function (...args) {
console.info('click', args);
art.layers.show = false;
},
mounted: function (...args) {
console.info('mounted', args);
},
},
],
});
```
:::warning For `Component Configuration`, please refer to:
[/component/layers.html](/component/layers.html)
:::
## `settings`
- Type: `Array`
- Default: `[]`
Initialize custom settings panels.
<div className="run-code">▶ Run Code</div>
```js{5-34}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
setting: true,
settings: [
{
html: 'setting01',
selector: [
{
html: 'setting01-01',
},
{
html: 'setting01-02',
},
],
onSelect: function (...args) {
console.info(args);
},
},
{
html: 'setting02',
selector: [
{
html: 'setting02-01',
},
{
html: 'setting02-02',
},
],
onSelect: function (...args) {
console.info(args);
},
},
],
});
```
:::warning For `Settings Panel`, please refer to:
[/component/setting.html](/component/setting.html)
:::
## `contextmenu`
- Type: `Array`
- Default: `[]`
Initialize custom context menus.
<div className="run-code">▶ Run Code</div>
```js{4-12}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
contextmenu: [
{
html: 'your-menu',
click: function (...args) {
console.info('click', args);
art.contextmenu.show = false;
},
},
],
});
```
:::warning For `Component Configuration`, please refer to:
[/component/contextmenu.html](/component/contextmenu.html)
:::
## `controls`
- Type: `Array`
- Default: `[]`
Initialize custom bottom control bar.
<div className="run-code">▶ Run Code</div>
```js{4-16}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
controls: [
{
position: 'left',
html: 'your-control',
tooltip: 'Your Control',
style: {
color: 'green',
},
click: function (...args) {
console.info('click', args);
},
},
],
});
```
:::warning For `Component Configuration`, please refer to the following address:
[/component/controls.html](/component/controls.html)
:::
## `quality`
- Type: `Array`
- Default: `[]`
Whether to display the `Quality Selection` list in the bottom control bar.
| Property | Type | Description |
| --------- | --------- | ---------------- |
| `default` | `Boolean` | Default quality |
| `html` | `String` | Quality name |
| `url` | `String` | Quality URL |
<div className="run-code">▶ Run Code</div>
```js{4-14}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
quality: [
{
default: true,
html: 'SD 480P',
url: '/assets/sample/video.mp4',
},
{
html: 'HD 720P',
url: '/assets/sample/video.mp4',
},
],
});
```
## `highlight`
- Type: `Array`
- Default: `[]`
Display `Highlight Information` on the progress bar.
| Property | Type | Description |
| -------- | -------- | ------------------------------- |
| `time` | `Number` | Highlight time (in seconds) |
| `text` | `String` | Highlight text |
<div className="run-code">▶ Run Code</div>
```js{4-25}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
highlight: [
{
time: 60,
text: 'One more chance',
},
{
time: 120,
text: '谁でもいいはずなのに',
},
{
time: 180,
text: '夏の想い出がまわる',
},
{
time: 240,
text: 'こんなとこにあるはずもないのに',
},
{
time: 300,
text: '--终わり--',
},
],
});
```
## `plugins`
- Type: `Array`
- Default: `[]`
Initialize custom `plugins`.
<div className="run-code">▶ Run Code</div>
```js{15}
function myPlugin(art) {
console.info(art);
return {
name: 'myPlugin',
something: 'something',
doSomething: function () {
console.info('doSomething');
},
};
}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
plugins: [myPlugin],
});
```
## `thumbnails`
- Type: `Object`
- Default: `{ url: '', number: 60, column: 10, width: 0, height: 0, scale: 1 }`
Set `Preview Thumbnails` on the progress bar.
| Property | Type | Description |
| -------- | -------- | -------------------------- |
| `url` | `String` | Thumbnail image URL |
| `number` | `Number` | Number of thumbnails |
| `column` | `Number` | Number of thumbnail columns|
| `width` | `Number` | Thumbnail width |
| `height` | `Number` | Thumbnail height |
| `scale` | `Number` | Thumbnail scale |
<div className="run-code">▶ Run Code</div>
```js{4-8}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
thumbnails: {
url: '/assets/sample/thumbnails.png',
number: 60,
column: 10,
},
});
```
:::warning Generate Thumbnails Online
[artplayer-tool-thumbnail](https://artplayer.org/?libs=./uncompiled/artplayer-tool-thumbnail/index.js&example=thumbnail)
:::
## `subtitle`
- Type: `Object`
- Default: `{ url: '', type: '', name: '', style: {}, escape: true, encoding: 'utf-8', onVttLoad: vtt => vtt }`
Set video subtitles. Supported subtitle formats: `vtt`, `srt`, `ass`.
| Property | Type | Description |
| ----------- | ---------- | ------------------------------------------------ |
| `name` | `String` | Subtitle name |
| `url` | `String` | Subtitle URL |
| `type` | `String` | Subtitle type, options: `vtt`, `srt`, `ass` |
| `style` | `Object` | Subtitle style |
| `encoding` | `String` | Subtitle encoding, default `utf-8` |
| `escape` | `Boolean` | Whether to escape `html` tags, default `true` |
| `onVttLoad` | `Function` | Function for modifying `vtt` text |
<div className="run-code">▶ Run Code</div>
```js{4-12}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
subtitle: {
url: '/assets/sample/subtitle.srt',
type: 'srt',
encoding: 'utf-8',
escape: true,
style: {
color: '#03A9F4',
'font-size': '30px',
},
},
});
```
## `moreVideoAttr`
- Type: `Object`
- Default: `{'controls': false, 'preload': 'metadata'}` (In Safari, it will automatically adjust to `preload: 'auto'` for better loading experience.)
More video attributes. These attributes will be written directly into the video element.
<div className="run-code">▶ Run Code</div>
```js{4-7}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
moreVideoAttr: {
'webkit-playsinline': true,
playsInline: true,
},
});
```
## `icons`
- Type: `Object`
- Default: `{}`
Used to replace default icons. Supports `Html` strings and `HTMLElement`.
<div className="run-code">▶ Run Code</div>
```js{4-7}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
icons: {
loading: '<img src="/assets/img/ploading.gif">',
state: '<img src="/assets/img/state.png">',
},
});
```
:::warning All Icon Definitions
[artplayer/types/icons.d.ts](https://github.com/zhw2590582/ArtPlayer/blob/master/packages/artplayer/types/icons.d.ts)
:::
## `type`
- Type: `String`
- Default: `''`
Used to specify the video format. It needs to be used together with `customType`. By default, the video format is determined by the suffix of the video URL (e.g., `.m3u8`, `.mkv`, `.ts`). However, sometimes the video URL may not have the correct suffix, so it needs to be explicitly specified.
<div className="run-code">▶ Run Code</div>
```js{4}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.m3u8',
type: 'm3u8',
});
```
:::warning Suffix Recognition
The player can only parse suffixes like this: `/assets/sample/video.m3u8`
But cannot parse suffixes like this: `/assets/sample/video?type=m3u8`
Therefore, if you use `customType`, it's best to also specify the `type`.
:::
## `customType`
- Type: `Object`
- Default: `{}`
Matches based on the video's `type` and delegates video decoding to third-party programs for processing. The processing function can receive three parameters:
- `video`: The video `DOM` element
- `url`: The video URL
- `art`: The current instance
<div className="run-code">▶ Run Code</div>
```js{4-8}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.m3u8',
customType: {
m3u8: function (video, url, art) {
//
},
},
});
```
## `lang`
- Type: `String`
- Default: `navigator.language.toLowerCase()`
The default display language. Currently supported: `en`, `zh-cn`.
<div className="run-code">▶ Run Code</div>
```js{4}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
lang: 'en',
});
```
:::warning More Language Settings
[/start/i18n.html](/start/i18n.html)
:::
## `i18n`
- Type: `Object`
- Default: `{}`
Custom `i18n` configuration. This configuration will be deeply merged with the built-in `i18n`.
Add your language:
<div className="run-code">▶ Run Code</div>
```js{4-9}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
lang: 'your-lang',
i18n: {
'your-lang': {
Play: 'Your Play'
},
},
});
```
Modify an existing language:
<div className="run-code">▶ Run Code</div>
```js{4-11}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
i18n: {
'zh-cn': {
Play: 'Your Play'
},
'zh-tw': {
Play: 'Your Play'
},
},
});
```
:::warning More Language Settings
[/start/i18n.html](/start/i18n.html)
:::
## `lock`
- Type: `Boolean`
- Default: `false`
Whether to display a `lock button` on mobile devices to hide the bottom `control bar`.
<div className="run-code">▶ Run Code</div>
```js{4}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
lock: true,
});
```
## `gesture`
- Type: `Boolean`
- Default: `true`
Whether to enable gesture events on the video element on mobile devices.
<div className="run-code">▶ Run Code</div>
```js{4}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
gesture: false,
});
```
## `fastForward`
- Type: `Boolean`
- Default: `false`
Whether to add a long-press video fast-forward feature on mobile devices.
<div className="run-code">▶ Run Code</div>
```js{4}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
fastForward: true,
});
```
## `autoPlayback`
- Type: `Boolean`
- Default: `false`
Whether to use the automatic `playback feature`.
<div className="run-code">▶ Run Code</div>
```js{4-5}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
id: 'your-url-id',
autoPlayback: true,
});
```
:::warning Note
Because the player uses the `url` as the `key` to cache playback progress by default.
However, if the `url` for the same video is different, then you need to use `id` to identify the unique `key` for the video.
:::
## `autoOrientation`
- Type: `Boolean`
- Default: `false`
Whether to rotate the player in fullscreen mode on mobile web, based on the video dimensions and viewport dimensions.
<div className="run-code">▶ Run Code</div>
```js{4}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
autoOrientation: true,
});
```
## `airplay`
- Type: `Boolean`
- Default: `false`
Whether to display the `airplay` button. Currently, only some browsers support this feature.
<div className="run-code">▶ Run Code</div>
```js{4}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
airplay: true,
});
```
## `cssVar`
- Type: `Object`
- Default: `{}`
Used to modify the built-in CSS variables.
<div className="run-code">▶ Run Code</div>
```js{4}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
cssVar: {
//
},
});
```
:::warning Reference for `cssVar` Syntax
[artplayer/types/cssVar.d.ts](https://github.com/zhw2590582/ArtPlayer/blob/master/packages/artplayer/types/cssVar.d.ts)
:::
## `proxy`
- Type: `function`
- Default: `undefined`
The function can return a third-party `HTMLCanvasElement` or `HTMLVideoElement`. For example, it can proxy an existing `video` DOM element.
<div className="run-code">▶ Run Code</div>
```js{4}
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
proxy: () => document.createElement('video')
});
```
===== packages/artplayer-vitepress/docs/en/tool/iframe.md =====
# Iframe communication tool
[简体中文](../../tool/iframe.md)
Control a player inside an iframe and receive child-page notifications. This is a standalone constructor, not an ArtPlayer plugins-array factory. This page describes the unreleased branch; deploy both parent and child pages yourself.
## Install and connect both pages
```sh
yarn add artplayer-tool-iframe
```
The parent uses `import ArtplayerToolIframe from 'artplayer-tool-iframe'`. For scripts, load `dist/artplayer-tool-iframe.js`; its global is `ArtplayerToolIframe`.
The child must also load the tool and call inject. To create a player there, load ArtPlayer and provide a container too. The repository's `/iframe.html` uses:
```html
<div class="artplayer-app" style="width:100%;height:100%"></div>
<script src="./uncompiled/artplayer/index.js"></script>
<script src="./uncompiled/artplayer-tool-iframe/index.js"></script>
<script>ArtplayerToolIframe.inject();</script>
```
These are this site's development paths; replace them with deployed build paths. The [original parent example](https://artplayer.org/?libs=./uncompiled/artplayer-tool-iframe/index.js&example=iframe) below creates an iframe pointing to `/iframe.html`:
<div className="run-code" data-libs="./uncompiled/artplayer-tool-iframe/index.js">▶ Run Code</div>
```js
// npm i artplayer-tool-iframe
// import ArtplayerToolIframe from 'artplayer-tool-iframe';
const $iframe = document.createElement('iframe')
$iframe.allowFullscreen = true
$iframe.width = '100%'
$iframe.height = '100%'
const $container = document.querySelector('.artplayer-app')
$container.innerHTML = ''
$container.appendChild($iframe)
const iframe = new ArtplayerToolIframe({
iframe: $iframe,
url: '/iframe.html',
})
window.addEventListener('artplayer:example:cleanup', () => {
iframe.destroy()
$iframe.remove()
}, { once: true })
iframe.message(({ type, data }) => {
switch (type) {
case 'fullscreenWeb':
if (data) {
$iframe.classList.add('fullscreenWeb')
}
else {
$iframe.classList.remove('fullscreenWeb')
}
break
default:
break
}
})
iframe.commit(() => {
const art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
fullscreen: true,
fullscreenWeb: true,
})
art.on('fullscreenWeb', (state) => {
ArtplayerToolIframe.postMessage({
type: 'fullscreenWeb',
data: state,
})
})
}).catch((error) => {
if (!iframe.destroyed)
console.error(error)
})
```
## Parent instance
Both options are required: `{ iframe: HTMLIFrameElement, url: string }`. Construction sets iframe.src and subscribes to messages; it does not install scripts in the child. The caller creates and removes the iframe element.
| Member | Behavior |
| --- | --- |
| `commit(callback)` | Extract the function body and execute it in the child; returns a response Promise |
| `postMessage({ type, data, id? })` | Send a request and await its response; allocate a numeric id when sending, ignoring a supplied id |
| `message(callback)` | Set one notification callback; the next call replaces it; synchronous void |
| `onMessage(event)` | Parent receiver, normally invoked by the installed native message listener |
| `destroy()` | Synchronous and idempotent; remove owned listeners/navigation observation and reject sent/waiting requests |
| `url` / `$iframe` | Initial configured address and element; changing url alone does not navigate |
| `injected` / `destroyed` | Injection and destruction state |
| `promises` | Pending request object, retaining the historical `resove` and `reject` callback keys |
| `messageCallback` | Current callback; initially a function, also nullable at runtime |
Notifications receive only `{ type, data }`, with the tool as this, without id or private document metadata. Any matching non-error response settles its request; error responses reject with Error. Custom message types remain valid, but the application must implement their child responses. There is no default response timeout.
Before injection, requests poll every200ms; inject does not flush them synchronously. IDs are correlation numbers, not necessarily Date.now. Destruction rejects unfinished requests with `The instance has been destroyed`; handle Promise failures.
## How commit executes
A normal function body can return a structured-cloneable result. Asynchronous results use the historical literal `resolve(...)` convention:
```js
const title = await iframe.commit(() => {
return document.title; // Read in the child page.
});
const answer = await iframe.commit((resolve) => {
setTimeout(() => resolve(42), 100);
});
```
The body is sliced from a string and evaluated with new Function. Closures, parent locals, function arguments and imports are not transferred. Keep braces; do not use expression arrows, async function bodies, top-level await or a renamed asynchronous resolve parameter. Detection is a text match, not a JavaScript parser. Results must support postMessage; a Promise object itself cannot be sent as a normal result. Execution errors send an error response and also reject the child's receiver.
## Child statics and trust
| Member | Behavior |
| --- | --- |
| `ArtplayerToolIframe.iframe` | Runtime readonly getter: whether this page is inside an iframe |
| `inject()` | Announce injection and install the receiver; repeated calls retain one listener set |
| `postMessage({ type, data, id? })` | Send a parent notification/response; id defaults0; returns void |
| `onMessage(event)` | Actually async; handles commit, while custom types need application handling |
The three methods require an iframe context; top-level use throws or rejects. Receivers check the selected window peer and basic packet shape, retaining wildcard targetOrigin rather than pinning the initial URL's origin. Trust the parent and child content. Commit executes code and requires a compatible CSP; it is neither a sandbox nor a payload validator.
## Navigation, cleanup and types
Upgraded peers negotiate document markers to reject obsolete replies and cancel work when document departure is confirmed. These markers are not authentication credentials. A src replacement pauses sends; confirmed cross-document departure cancels old requests, while a matching same-document hash change preserves them. Work queued for the next page is tracked separately. Legacy children cannot identify same-address reloads or internal navigation reliably; ordinary navigation tests do not establish BFCache restoration.
Destroy does not remove the iframe, destroy its player or provide a new static child destroy method. The parent can remove its iframe after releasing the tool. Player events, fullscreen permissions and physical devices require actual integration checks.
Root and `/legacy` preserve the historical class, including required Message.data, readonly fields, void static onMessage and ReturnType-based commit inference. Import accurate erased views from the same entry; there is no `/runtime` subpath:
```ts
import Iframe from 'artplayer-tool-iframe';
import type { ResolverInstance, RuntimeConstructor } from 'artplayer-tool-iframe';
function connectFrame(element: HTMLIFrameElement) {
const Runtime = Iframe as RuntimeConstructor;
const tool = new Runtime({ iframe: element, url: '/iframe.html' }) as ResolverInstance;
tool.message(function (message) { console.log(this.url, message.type, message.data); });
const answer = tool.commit<number>((resolve) => { resolve(42); });
return { tool, answer };
}
```
Public types are Option, Message, Callbacks, Notification, MessageCallback, OutboundMessage, ProtocolMessage, Resolve, ResolverCallback, RuntimeInstance, ResolverInstance and RuntimeConstructor. Views neither validate responses nor alter execution. Modern CommonJS TS can use `import Iframe = require('artplayer-tool-iframe')`; ESM uses the default import. The constructor has no `.default` self-alias. The old `artplayer-plugin-iframe` name, namespace and separate helper are not identical to this tool; renaming the dependency alone does not preserve every old entrypoint.
===== packages/artplayer-vitepress/docs/en/tool/thumbnail.md =====
# Local video thumbnail tool
[简体中文](../../tool/thumbnail.md)
Generate a PNG thumbnail sheet from a selected local video, then download it or use it with the player's thumbnails option. This is a standalone constructor, not the Auto Thumbnail plugin. This page describes the unreleased branch and its approved compatibility modes.
## Install and example
```sh
yarn add artplayer-tool-thumbnail
```
ESM uses `import ArtplayerToolThumbnail from 'artplayer-tool-thumbnail'`. Scripts load `dist/artplayer-tool-thumbnail.js`, exposing `ArtplayerToolThumbnail`. The tool does not depend on the player; the [original example](https://artplayer.org/?libs=./uncompiled/artplayer-tool-thumbnail/index.js&example=tool.thumbnail) below uses this site's DOM and ArtPlayer to display the result:
<div className="run-code" data-libs="./uncompiled/artplayer-tool-thumbnail/index.js">▶ Run Code</div>
```js
if (window.lastThumbnail) {
window.lastThumbnail.destroy();
}
var $popups = document.querySelector('.popups');
var $popinner = document.querySelector('.popinner');
var $artplayer = document.querySelector('.artplayer-app');
$artplayer.innerHTML = 'Drop video file here or click to upload.';
var thumbnail = new ArtplayerToolThumbnail({
fileInput: $artplayer,
number: 60, // 数量
width: 160, // 宽度
column: 10, // 列数
begin: 0, // 开始
end: NaN, // 结束
});
window.lastThumbnail = thumbnail;
thumbnail.on('file', function (file) {
console.log('Read video successfully: ' + file.name);
});
thumbnail.on('video', function (video) {
console.log('Video size: ' + video.videoWidth + ' x ' + video.videoHeight);
console.log('Video duration: ' + video.duration + 's');
thumbnail.start();
});
thumbnail.on('canvas', function (canvas) {
console.log('Build canvas successfully');
console.log('Canvas size: ' + canvas.width + ' x ' + canvas.height);
console.log('Preview density: ' + thumbnail.density + ' p/s');
});
thumbnail.on('update', function (url, percentage) {
console.log('Processing: ' + Math.floor(percentage.toFixed(2) * 100) + '%');
$popups.style.display = 'flex';
$popinner.style.backgroundImage = 'url(' + url + ')';
});
thumbnail.on('download', function (name) {
console.log('Start download preview: ' + name);
});
thumbnail.on('done', function () {
$popups.style.display = 'none';
thumbnail.download();
console.log('Build preview image complete');
[...Artplayer.instances].forEach(function (art) {
art.destroy(true);
});
new Artplayer({
container: $artplayer,
url: thumbnail.videoUrl,
autoSize: true,
poster: thumbnail.thumbnailUrl,
thumbnails: {
url: thumbnail.thumbnailUrl,
number: thumbnail.option.number,
column: thumbnail.option.column,
},
});
console.log('Build player complete');
});
```
File selection loads the video without automatically extracting images; the example calls start from the video event. Application code should handle both synchronous start errors and Promise rejection. The video notification does not guarantee metadata readiness; start waits for it. Subscribe before loading, especially with synchronous workspace-mode notifications.
## Options and defaults
Successful construction needs fileInput: an existing file input or an Element upload wrapper. Missing input throws synchronously even though the type allows omitted constructor options. A wrapper receives an owned transparent input; caller inputs remain caller-owned. Selection/drop reads only the first file.
| Field | Default | Meaning |
| --- | --- | --- |
| `fileInput` | Required for construction | File input or upload wrapper |
| `compatibility` | published-3.5 behavior | Alternatively choose `workspace-4.4` |
| `number` | `60` | Frame count, numerically clamped to10–1000 |
| `width` | `160` | Frame width, clamped to10–1000 |
| `height` | `90` | Fixed default-mode height, clamped to10–1000 |
| `column` | `10` | Columns, clamped to1–1000 |
| `begin` | `0` | Start time in seconds |
| `end` | `NaN` | End time in seconds; NaN/0 uses media duration |
| `delay` | `300` | Default-mode milliseconds, clamped to10–1000 |
Use valid finite dimensions and integer counts/columns; historical numeric checks are not integer validation. Start normalizes the interval against media duration and requires end greater than begin, finite duration and `number / intervalSeconds <= 1`. The default60 frames therefore needs an interval of at least60 seconds; change the count for shorter files.
| Behavior | Default / published-3.5 | workspace-4.4 |
| --- | --- | --- |
| Height | Keep configured height | Derive from video aspect ratio at start and update option.height |
| video event | After src assignment plus delay | Synchronously after src assignment |
| Frame wait | Policy delay after each seek plus frame readiness | Frame readiness without a fixed extra delay |
| done | Another delay × 2 after the last update | No fixed final wait |
| Input value | Retained | Cleared after reading the selected file |
Consumers of unpublished4.4 workspace behavior add `compatibility: 'workspace-4.4'`; this mode ignores delay. Static DEFAULTS returns a fresh default object including published delay on every access, regardless of instance mode. Browser scheduling means delays are not exact timestamps.
## Methods, state and output
| Method | Behavior |
| --- | --- |
| `setup(options?)` | Merge partial options, retain extra fields and transfer input listeners when needed; returns this |
| `loadVideo(file?)` | Accept File; absent input is a no-op; check canPlayType and create a Blob URL |
| `start()` | One extraction job returning `Promise<void>`; duplicates and ready-metadata preflight can throw synchronously |
| `creatScreenshotDate()` | Historical spelling; return `{ time, x, y }[]`, with time in seconds |
| `creatCanvas()` | Historical spelling; create the sheet with black background and footer text |
| `download()` | Trigger PNG download when idle with file/image available; returns this, throws if not ready |
| `inputChange(event)` / `ondrop(event)` | Bound input handlers, normally installed by the tool |
| `errorHandle(condition, message)` | Emit error and throw when the condition fails |
| `destroy()` | Synchronous, idempotent cancellation and owned-resource cleanup |
Static creatVideo creates an offscreen muted/controls video in the document; callers invoking it directly own that extra node. Static ondragover calls preventDefault. Historical creat* names remain unchanged.
Fields include processing, option, video, duration, density, file, videoUrl, thumbnailUrl and optional event registry e. Duration is the selected interval, not necessarily full media duration; density is frames per interval second. File/URLs/density may be absent before their operation. Canvas listeners observe processing=false, update listeners true, and done listeners false.
Frames sample interval midpoints: `begin + (i + 0.5) * duration / number`. Sheet width is width × column; height is ceil(number / column) × height + 30. The30px footer retains source/layout text. Fractional coordinates keep historical behavior. Each frame produces a PNG update; previous thumbnail Blob URLs are revoked, leaving the latest thumbnailUrl. Download naming removes the last extension segment and adds `.png`; extensionless names retain the historical `.png` result.
## Events and cleanup
on/once/emit/off return this. The third on/once argument sets callback this. off(name) removes all listeners for that event; off(name, callback) removes matches. Custom string, number and symbol events are supported. A listener exception stops the remaining callbacks in that dispatch.
| Event | Arguments and timing |
| --- | --- |
| `file` | File, synchronously before video.src assignment |
| `video` | HTMLVideoElement; timing depends on mode |
| `canvas` | HTMLCanvasElement before extraction |
| `update` | Latest URL and0–1 progress |
| `done` | No arguments, before the start Promise resolves |
| `download` | Filename after link click; not proof of completed disk writing |
| `error` | Usually a message string; user callback failures can carry other values |
| `destroy` | No arguments, once after resource cleanup |
Start can wait for the first file selection with no new metadata deadline. Replacing an existing source or destroying the instance rejects old work with AbortError without an error event for cancellation. Source, seek, draw, encoding and callback failures settle the job. Stale callbacks cannot update a newer result.
Destroy removes the owned video/generated input/listeners/Blob URLs and restores wrapper position if the tool still owns that write. Caller inputs and the emitter registry remain. Cleanup attempts all steps, then throws its first failure. Destroyed instances cannot recreate input/source resources; start rejects cancellation. Keep the tool alive while a player depends on videoUrl/thumbnailUrl: those URLs remain tool-owned.
MIME canPlayType, actual decoding, Canvas encoding and Blob URL support are separate conditions. Windows WebKit has a recorded native Blob-loading gap; successful guide navigation or HTTP video playback is not proof of local-file extraction.
## TypeScript
Root and `/legacy` share one class declaration, with no `/runtime` or runtime `.default` self-alias. CommonJS TS supports `import Thumbnail = require('artplayer-tool-thumbnail')`; ESM uses default/type imports:
```ts
import Thumbnail, { type Option } from 'artplayer-tool-thumbnail';
function createTool(input: HTMLInputElement) {
const options: Option = { fileInput: input, number: 10, height: 90 };
const tool = new Thumbnail(options);
tool.on('update', (url, progress) => console.log(url, progress));
tool.on('video', () => {
void (async () => {
try { await tool.start(); }
catch (error) { console.error(error); }
})();
});
return tool;
}
```
Types include SheetOptions, Compatibility, DefaultOptions, Option, ResolvedOption, ScreenshotPoint, Events, EventArgs, Listener and EventRegistry. Known events have precise arguments; custom protocols remain application-defined. The old workspace referenced a missing declaration, so the new types are not claimed as a recovered historical TS baseline. Missing complete old npm archives and rollback acceptance remain separately tracked.
===== Type Definitions Overview =====
===== docs/assets/ts/artplayer-plugin-ads.d.ts =====
// Generated from the package public declaration by yarn build:ts. Do not edit.
/* eslint-disable ts/no-redeclare -- Callable and public type namespace intentionally merge. */
declare namespace artplayerPluginAds {
interface Translations {
close: string
countdown: string
detail: string
canBeClosed: string
}
/** Implemented options. Video takes precedence over HTML. */
interface Option {
html?: string
video?: string
url?: string
/** Seconds before the close button becomes available. @default 5 */
playDuration?: number
/** Total advertisement duration in seconds. @default 10 */
totalDuration?: number
/** Initial ad-video mute state. @default false */
muted?: boolean
/** All four fields replace the default translation object together. */
i18n?: Translations
}
/** Historical published declaration. String durations still fail runtime validation. */
interface LegacyOption extends Omit<Option, 'totalDuration'> {
/** @deprecated Incorrect in the old declaration; use a numeric duration. */
totalDuration?: string
}
/** Historical unpublished workspace declaration; these fields are not runtime aliases. */
interface WorkspaceOption extends Option {
/** @deprecated Ignored by the runtime. Use html or video instead. */
source: string
/** @deprecated Ignored by the runtime. Images are supplied through html. */
type: 'video' | 'image' | 'html'
}
/** Input acceptance for both historical declaration families. */
interface CompatOption extends Omit<Option, 'totalDuration'> {
/** @deprecated The string branch exists for old types only and is rejected at runtime. */
totalDuration?: number | string
/** @deprecated Ignored by the runtime. Use html or video instead. */
source?: string
/** @deprecated Ignored by the runtime. Images are supplied through html. */
type?: 'video' | 'image' | 'html'
}
interface Result {
name: 'artplayerPluginAds'
/** Complete once; before initialization this cancels the pending preroll. */
skip: () => void
/** Pause only the countdown, leaving ad video playback unchanged. */
pause: () => void
/** Resume only the countdown without adding extra timers. */
play: () => void
}
interface Callable {
(option?: Option): (art: Artplayer) => Result
/** @deprecated Compatibility with erroneous old string-duration declarations only. */
(option: LegacyOption): (art: Artplayer) => Result
(option: WorkspaceOption): (art: Artplayer) => Result
(option?: CompatOption): (art: Artplayer) => Result
/** Required final signature keeps Parameters extraction free of top-level undefined. */
(option: CompatOption): (art: Artplayer) => Result
}
interface Factory extends Callable {
/** Same function; supports historical require(package).default calls. */
readonly default: Callable
}
interface RuntimeCallable {
(option?: Option): (art: Artplayer) => Result
(option: Option): (art: Artplayer) => Result
}
/** Accurate typing for the identical implementation at /runtime. */
interface RuntimeFactory extends RuntimeCallable {
readonly default: RuntimeCallable
}
}
declare const artplayerPluginAds: artplayerPluginAds.Factory
export = artplayerPluginAds
export as namespace artplayerPluginAds;
===== docs/assets/ts/artplayer-plugin-ambilight.d.ts =====
// Generated from the package public declaration by yarn build:ts. Do not edit.
/* eslint-disable ts/no-redeclare -- Callable and public type namespace intentionally merge. */
declare namespace artplayerPluginAmbilightDefinitions {
export interface Option {
/** CSS blur radius. @default '50px' */
blur?: string
/** Grid cell opacity. @default 0.5 */
opacity?: number
/** Maximum sampling frequency in frames per second. @default 10 */
frequency?: number
/** Historical input retained for compatibility; runtime uses a fixed z-index of 9. */
zIndex?: number
/** Background color transition duration in seconds. @default 0.3 */
duration?: number
}
export interface Result {
name: 'artplayerPluginAmbilight'
/** Start sampling; does nothing after the player is destroyed. */
start: () => void
/** Stop sampling while retaining the last colors. */
stop: () => void
}
/** Published 1.1.0 factory shape; the options argument remains required. */
export type Callable = (option: Option) => (art: Artplayer) => Result
export type Factory = Callable
/** Accurate optional invocation and CommonJS self alias, exposed by /runtime. */
export interface RuntimeFactory {
(option?: Option): (art: Artplayer) => Result
readonly default: RuntimeFactory
}
export const artplayerPluginAmbilight: (option: Option) => (art: Artplayer) => Result
}
declare const artplayerPluginAmbilight: typeof artplayerPluginAmbilightDefinitions.artplayerPluginAmbilight
declare namespace artplayerPluginAmbilight {
export type Option = artplayerPluginAmbilightDefinitions.Option
export type Result = artplayerPluginAmbilightDefinitions.Result
export type Callable = artplayerPluginAmbilightDefinitions.Callable
export type Factory = artplayerPluginAmbilightDefinitions.Factory
export type RuntimeFactory = artplayerPluginAmbilightDefinitions.RuntimeFactory
}
export = artplayerPluginAmbilight
export as namespace artplayerPluginAmbilight;
===== docs/assets/ts/artplayer-plugin-asr.d.ts =====
// Generated from the package public declaration by yarn build:ts. Do not edit.
/* eslint-disable ts/no-redeclare -- Callable and public type namespace intentionally merge. */
declare namespace artplayerPluginAsrDefinitions {
export interface AudioChunk {
pcm: ArrayBuffer
wav: ArrayBuffer
}
export interface AsrPluginOption {
length?: number
interval?: number
sampleRate?: number
autoHideTimeout?: number
onAudioChunk?: (chunk: AudioChunk) => void | Promise<void>
}
export interface AsrPluginInstance {
name: 'artplayerPluginAsr'
stop: () => void
hide: () => void
append: (subtitle: string) => void
}
/** Historical factory shape, including void stop and callback results. */
export type Factory = (option?: AsrPluginOption) => (art: Artplayer) => AsrPluginInstance
/** Accurate asynchronous view available through the /runtime entry. */
export interface RuntimeOption extends Omit<AsrPluginOption, 'onAudioChunk'> {
/** Capture the media stream without taking ownership of its playback route. */
audioInput?: {
type: 'capture'
}
onAudioChunk?: (chunk: AudioChunk) => string | void | null | Promise<string | void | null>
}
export interface RuntimeResult extends Omit<AsrPluginInstance, 'stop'> {
stop: () => Promise<void>
}
export interface RuntimeFactory {
(option?: RuntimeOption): (art: Artplayer) => RuntimeResult
readonly default: RuntimeFactory
}
export function artplayerPluginAsr(option?: AsrPluginOption): (art: Artplayer) => AsrPluginInstance
}
declare const artplayerPluginAsr: typeof artplayerPluginAsrDefinitions.artplayerPluginAsr
declare namespace artplayerPluginAsr {
export type AudioChunk = artplayerPluginAsrDefinitions.AudioChunk
export type AsrPluginOption = artplayerPluginAsrDefinitions.AsrPluginOption
export type AsrPluginInstance = artplayerPluginAsrDefinitions.AsrPluginInstance
export type Factory = artplayerPluginAsrDefinitions.Factory
export type RuntimeOption = artplayerPluginAsrDefinitions.RuntimeOption
export type RuntimeResult = artplayerPluginAsrDefinitions.RuntimeResult
export type RuntimeFactory = artplayerPluginAsrDefinitions.RuntimeFactory
}
export = artplayerPluginAsr
export as namespace artplayerPluginAsr;
===== docs/assets/ts/artplayer-plugin-audio-track.d.ts =====
// Generated from the package public declaration by yarn build:ts. Do not edit.
/* eslint-disable ts/no-redeclare -- Callable and public type namespace intentionally merge. */
declare namespace artplayerPluginAudioTrackDefinitions {
export interface Option {
/**
* Audio track URL
*/
url: string
/**
* Time offset in seconds between video and audio
* Positive value means audio plays ahead of video
* Negative value means audio plays behind video
* @default 0
*/
offset?: number
/**
* Synchronization threshold in seconds
* @default 0.3
*/
sync?: number
}
export type UpdateOption = Partial<Option>
export interface Result {
name: 'artplayerPluginAudioTrack'
/**
* The audio element
*/
audio: HTMLAudioElement
/**
* Historical update signature. Runtime also accepts partial options.
* Import the /runtime entry for the precise partial-update signature.
*/
update: (option: Option) => void
}
export interface RuntimeResult extends Result {
/** Update selected fields without replacing the audio element. */
update: (option: UpdateOption) => void
}
/** Precise typing for the same runtime factory, without changing legacy inference. */
export type RuntimeFactory = (option: Option) => (art: Artplayer) => RuntimeResult
export function artplayerPluginAudioTrack(option: Option): (art: Artplayer) => Result
}
declare const artplayerPluginAudioTrack: typeof artplayerPluginAudioTrackDefinitions.artplayerPluginAudioTrack
declare namespace artplayerPluginAudioTrack {
export type Option = artplayerPluginAudioTrackDefinitions.Option
export type UpdateOption = artplayerPluginAudioTrackDefinitions.UpdateOption
export type Result = artplayerPluginAudioTrackDefinitions.Result
export type RuntimeResult = artplayerPluginAudioTrackDefinitions.RuntimeResult
export type RuntimeFactory = artplayerPluginAudioTrackDefinitions.RuntimeFactory
}
export = artplayerPluginAudioTrack
export as namespace artplayerPluginAudioTrack;
===== docs/assets/ts/artplayer-plugin-auto-thumbnail.d.ts =====
// Generated from the package public declaration by yarn build:ts. Do not edit.
/* eslint-disable ts/no-redeclare -- Callable and public type namespace intentionally merge. */
declare namespace artplayerPluginAutoThumbnailDefinitions {
export interface Option {
url?: string
width?: number
number?: number
scale?: number
}
export interface Result {
name: 'artplayerPluginAutoThumbnail'
}
export const artplayerPluginAutoThumbnail: (option: Option) => (art: Artplayer) => Result
}
declare const artplayerPluginAutoThumbnail: typeof artplayerPluginAutoThumbnailDefinitions.artplayerPluginAutoThumbnail
declare namespace artplayerPluginAutoThumbnail { }
export = artplayerPluginAutoThumbnail
export as namespace artplayerPluginAutoThumbnail;
===== docs/assets/ts/artplayer-plugin-chapter.d.ts =====
// Generated from the package public declaration by yarn build:ts. Do not edit.
/* eslint-disable ts/no-redeclare -- Callable and public type namespace intentionally merge. */
declare namespace artplayerPluginChapterDefinitions {
export type Chapters = {
start: number
end: number
title: string
}[]
export interface Option {
chapters?: Chapters
}
export interface Result {
name: 'artplayerPluginChapter'
update: (option: Option) => void
}
export const artplayerPluginChapter: (option?: Option) => (art: Artplayer) => Result
}
declare const artplayerPluginChapter: typeof artplayerPluginChapterDefinitions.artplayerPluginChapter
declare namespace artplayerPluginChapter {
export type Chapters = artplayerPluginChapterDefinitions.Chapters
export type Option = artplayerPluginChapterDefinitions.Option
export type Result = artplayerPluginChapterDefinitions.Result
}
export = artplayerPluginChapter
export as namespace artplayerPluginChapter;
===== docs/assets/ts/artplayer-plugin-chromecast.d.ts =====
// Generated from the package public declaration by yarn build:ts. Do not edit.
/* eslint-disable ts/no-redeclare -- Callable and public type namespace intentionally merge. */
declare namespace artplayerPluginChromecastDefinitions {
export interface Option {
url?: string
sdk?: string
icon?: string
mimeType?: string
}
export interface Chromecast {
name: 'artplayerPluginChromecast'
}
/** Published 1.1.0 result; actual registration is asynchronous. */
export type Result = Chromecast
export type Factory = (option: Option) => (art: Artplayer) => Chromecast
export type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'disconnecting'
export interface RuntimeOption extends Option {
onStateChange?: (this: RuntimeOption, state: ConnectionState) => void
onCastAvailable?: (this: RuntimeOption, available: boolean) => void
onCastStart?: (this: RuntimeOption) => void
onError?: (this: RuntimeOption, error: unknown) => void
}
export interface RuntimeResult extends Chromecast {
/** Last raw SDK SessionState, initially null; not the normalized callback state. */
getCastState: () => string | null
/** Whether this controller retains a session; does not prove receiver playback. */
isCasting: () => boolean
}
export interface RuntimeFactory {
(option: RuntimeOption): (art: Artplayer) => Promise<RuntimeResult>
default: RuntimeFactory
}
export const artplayerPluginChromecast: (option: Option) => (art: Artplayer) => Chromecast
}
declare const artplayerPluginChromecast: typeof artplayerPluginChromecastDefinitions.artplayerPluginChromecast
declare namespace artplayerPluginChromecast {
export type Option = artplayerPluginChromecastDefinitions.Option
export type Chromecast = artplayerPluginChromecastDefinitions.Chromecast
export type Result = artplayerPluginChromecastDefinitions.Result
export type Factory = artplayerPluginChromecastDefinitions.Factory
export type ConnectionState = artplayerPluginChromecastDefinitions.ConnectionState
export type RuntimeOption = artplayerPluginChromecastDefinitions.RuntimeOption
export type RuntimeResult = artplayerPluginChromecastDefinitions.RuntimeResult
export type RuntimeFactory = artplayerPluginChromecastDefinitions.RuntimeFactory
}
export = artplayerPluginChromecast
export as namespace artplayerPluginChromecast;
===== docs/assets/ts/artplayer-plugin-danmuku-mask.d.ts =====
// Generated from the package public declaration by yarn build:ts. Do not edit.
/* eslint-disable ts/no-redeclare -- Callable and public type namespace intentionally merge. */
declare namespace artplayerPluginDanmukuMaskDefinitions {
export interface Option {
solutionPath?: string
modelSelection?: number
smoothSegmentation?: boolean
minDetectionConfidence?: number
minTrackingConfidence?: number
selfieMode?: boolean
drawContour?: boolean
foregroundThreshold?: number
opacity?: number
maskBlurAmount?: number
}
export interface Result {
name: 'artplayerPluginDanmukuMask'
start: () => Promise<void>
stop: () => void
}
export const artplayerPluginDanmukuMask: (option?: Option) => (art: Artplayer) => Result
}
declare const artplayerPluginDanmukuMask: typeof artplayerPluginDanmukuMaskDefinitions.artplayerPluginDanmukuMask
declare namespace artplayerPluginDanmukuMask { }
export = artplayerPluginDanmukuMask
export as namespace artplayerPluginDanmukuMask;
===== docs/assets/ts/artplayer-plugin-danmuku.d.ts =====
// Generated from the package public declaration by yarn build:ts. Do not edit.
/* eslint-disable ts/no-redeclare -- Callable and public type namespace intentionally merge. */
declare namespace artplayerPluginDanmukuDefinitions {
export type Mode = 0 | 1 | 2
export type Danmuku = Danmu[] | string // URL
| (() => Promise<Danmu[]>) | Promise<Danmu[]>
export interface Slider {
min?: number
max?: number
steps?: {
name?: string
value?: number | string
show?: boolean
}[]
}
export interface Danmu {
/**
* 弹幕文本
*/
text: string
/**
* 弹幕发送模式: 0: 滚动,1: 顶部,2: 底部
*/
mode?: Mode
/**
* 弹幕颜色
*/
color?: string
/**
* 弹幕出现的时间,单位为秒
*/
time?: number
/**
* 弹幕是否有描边, 默认为 false
*/
border?: boolean
/**
* 弹幕自定义样式
*/
style?: Partial<CSSStyleDeclaration>
}
export interface Option {
/**
* 弹幕数据: 函数,数组,Promise,URL
*/
danmuku: Danmuku
/**
* 弹幕持续时间,范围在[1 ~ 10]
*/
speed?: number
/**
* 弹幕上下边距,支持像素数字和百分比
*/
margin?: [
number | `${number}%`,
number | `${number}%`,
]
/**
* 弹幕透明度,范围在[0 ~ 1]
*/
opacity?: number
/**
* 默认弹幕颜色,可以被单独弹幕项覆盖
*/
color?: string
/**
* 弹幕模式: 0: 滚动,1: 顶部,2: 底部
*/
mode?: Mode
/**
* 弹幕可见的模式
*/
modes?: Mode[]
/**
* 弹幕字体大小,支持像素数字和百分比
*/
fontSize?: number | `${number}%`
/**
* 弹幕是否防重叠
*/
antiOverlap?: boolean
/**
* 是否同步播放速度
*/
synchronousPlayback?: boolean
/**
* 弹幕发射器挂载点, 默认为播放器控制栏中部
*/
mount?: HTMLDivElement | string
/**
* 是否开启弹幕热度图
*/
heatmap?: boolean | {
xMin?: number
xMax?: number
yMin?: number
yMax?: number
scale?: number
opacity?: number
minHeight?: number
sampling?: number
smoothing?: number
flattening?: number
}
/**
* 当播放器宽度小于此值时,弹幕发射器置于播放器底部
*/
width?: number
/**
* 热力图数据
*/
points?: {
time: number
value: number
}[]
/**
* 弹幕载入前的过滤器,只支持返回布尔值
*/
filter?: (danmu: Danmu) => boolean
/**
* 弹幕发送前的过滤器,支持返回 Promise
*/
beforeEmit?: (danmu: Danmu) => boolean | Promise<boolean>
/**
* 弹幕显示前的过滤器,支持返回 Promise
*/
beforeVisible?: (danmu: Danmu) => boolean | Promise<boolean>
/**
* 弹幕是否可见
*/
visible?: boolean
/**
* 是否开启弹幕发射器
*/
emitter?: boolean
/**
* 弹幕输入框最大长度, 范围在[1 ~ 1000]
*/
maxLength?: number
/**
* 输入框锁定时间,范围在[1 ~ 60]
*/
lockTime?: number
/**
* 弹幕主题,只在自定义挂载时生效
*/
theme?: 'light' | 'dark'
/**
* 不透明度配置项
*/
OPACITY?: Slider
/**
* 弹幕速度配置项
*/
SPEED?: Slider
/**
* 显示区域配置项
*/
MARGIN?: Slider
/**
* 弹幕字号配置项
*/
FONT_SIZE?: Slider
/**
* 颜色列表配置项
*/
COLOR?: string[]
}
export interface Result {
name: 'artplayerPluginDanmuku'
/**
* 发送一条实时弹幕
*/
emit: (danmu: Danmu) => Result
/**
* 重载弹幕源,或者切换新弹幕
*/
load: (danmuku?: Danmuku) => Promise<Result>
/**
* 实时改变弹幕配置
*/
config: (option: Option) => Result
/**
* 隐藏弹幕层
*/
hide: () => Result
/**
* 显示弹幕层
*/
show: () => Result
/**
* 挂载弹幕输入框
*/
mount: (el?: HTMLDivElement | string) => void
/**
* 重置弹幕
*/
reset: () => Result
/**
* 弹幕配置
*/
option: Option
/**
* 是否隐藏弹幕层
*/
isHide: boolean
/**
* 是否弹幕层停止状态
*/
isStop: boolean
}
export const artplayerPluginDanmuku: (option: Option) => (art: Artplayer) => Result
}
declare const artplayerPluginDanmuku: typeof artplayerPluginDanmukuDefinitions.artplayerPluginDanmuku
declare namespace artplayerPluginDanmuku {
export type Mode = artplayerPluginDanmukuDefinitions.Mode
export type Danmuku = artplayerPluginDanmukuDefinitions.Danmuku
export type Slider = artplayerPluginDanmukuDefinitions.Slider
export type Danmu = artplayerPluginDanmukuDefinitions.Danmu
export type Option = artplayerPluginDanmukuDefinitions.Option
export type Result = artplayerPluginDanmukuDefinitions.Result
}
export = artplayerPluginDanmuku
export as namespace artplayerPluginDanmuku;
===== docs/assets/ts/artplayer-plugin-dash-control.d.ts =====
// Generated from the package public declaration by yarn build:ts. Do not edit.
/* eslint-disable ts/no-redeclare -- Callable and public type namespace intentionally merge. */
declare namespace artplayerPluginDashControlDefinitions {
export interface QualityLevel {
height: number
width?: number
id?: string | number
qualityIndex?: number
bitrate?: number
bitrateInKbit?: number
}
export interface AudioTrack {
id?: string | number | null
index?: number | null
lang?: string | null
}
export interface Config<Item extends object = object> {
control?: boolean
setting?: boolean
title?: string
auto?: string
/** Called without a receiver or index, with the original SDK object. */
getName?: (item: Item) => string
}
export interface Option<Level extends object = QualityLevel, Track extends object = AudioTrack> {
quality?: Config<Level>
audio?: Config<Track>
}
export interface Result {
name: 'artplayerPluginDashControl'
update: () => void
}
export function artplayerPluginDashControl<Level extends object = QualityLevel, Track extends object = AudioTrack>(option?: Option<Level, Track>): (art: Artplayer) => Result
// Preserve the required last signature for historical Parameters<typeof factory>[0] consumers.
export function artplayerPluginDashControl<Level extends object = QualityLevel, Track extends object = AudioTrack>(option: Option<Level, Track>): (art: Artplayer) => Result
}
declare const artplayerPluginDashControl: typeof artplayerPluginDashControlDefinitions.artplayerPluginDashControl
declare namespace artplayerPluginDashControl {
export type QualityLevel = artplayerPluginDashControlDefinitions.QualityLevel
export type AudioTrack = artplayerPluginDashControlDefinitions.AudioTrack
export type Config<Item extends object = object> = artplayerPluginDashControlDefinitions.Config<Item>
export type Option<Level extends object = QualityLevel, Track extends object = AudioTrack> = artplayerPluginDashControlDefinitions.Option<Level, Track>
export type Result = artplayerPluginDashControlDefinitions.Result
}
export = artplayerPluginDashControl
export as namespace artplayerPluginDashControl;
===== docs/assets/ts/artplayer-plugin-document-pip.d.ts =====
// Generated from the package public declaration by yarn build:ts. Do not edit.
/* eslint-disable ts/no-redeclare -- Callable and public type namespace intentionally merge. */
declare namespace artplayerPluginDocumentPipDefinitions {
export interface Option {
/** Requested window width. @default 480 */
width?: number
/** Requested window height. @default 270 */
height?: number
/** Text displayed in the original player container while the window is active. */
placeholder?: string
/** Use the core video PiP property when Document PiP is unavailable. @default true */
fallbackToVideoPiP?: boolean
}
/** Historical assignable result; preserved for direct and extracted return types. */
export interface Result {
name: 'artplayerPluginDocumentPip'
/** Runtime is a readonly capability snapshot; the published field stays assignable. */
isSupported: boolean
/** Runtime is a readonly live getter; the published field stays assignable. */
isActive: boolean
/** Runtime returns Promise<void>; the published void action remains assignable here. */
open: () => void
/** Runtime returns Promise<void>; the published void action remains assignable here. */
close: () => void
toggle: () => void
}
/** Exact view of an unmodified runtime result. */
export interface AsyncResult extends Omit<Result, 'isSupported' | 'isActive' | 'open' | 'close'> {
readonly isSupported: boolean
readonly isActive: boolean
open: () => Promise<void>
close: () => Promise<void>
}
/** Exact published factory signature, including compatibility with replacement functions. */
export type Factory = (option: Option) => (art: Artplayer) => Result
/** Opt-in runtime view with omitted options, self default and precise async actions. */
export interface RuntimeFactory {
(option?: Option): (art: Artplayer) => AsyncResult
readonly default: RuntimeFactory
}
/** Keep the published callable type; use RuntimeFactory explicitly for its broader runtime shape. */
export function artplayerPluginDocumentPip(option: Option): (art: Artplayer) => Result
}
declare const artplayerPluginDocumentPip: typeof artplayerPluginDocumentPipDefinitions.artplayerPluginDocumentPip
declare namespace artplayerPluginDocumentPip {
export type Option = artplayerPluginDocumentPipDefinitions.Option
export type Result = artplayerPluginDocumentPipDefinitions.Result
export type AsyncResult = artplayerPluginDocumentPipDefinitions.AsyncResult
export type Factory = artplayerPluginDocumentPipDefinitions.Factory
export type RuntimeFactory = artplayerPluginDocumentPipDefinitions.RuntimeFactory
}
export = artplayerPluginDocumentPip
export as namespace artplayerPluginDocumentPip;
===== docs/assets/ts/artplayer-plugin-hls-control.d.ts =====
// Generated from the package public declaration by yarn build:ts. Do not edit.
/* eslint-disable ts/no-redeclare -- Callable and public type namespace intentionally merge. */
declare namespace artplayerPluginHlsControlDefinitions {
export interface QualityLevel {
height: number
name?: string
}
export interface AudioTrack {
id: number
name: string
lang?: string
language?: string
}
export interface Config<Item extends object = object> {
control?: boolean
setting?: boolean
title?: string
auto?: string
/** Plain callback; current-label calls omit index. SDK objects retain their identity. */
getName?: (item: Item, index?: number) => string
}
export interface Option<Level extends object = QualityLevel, Track extends object = AudioTrack> {
quality?: Config<Level>
audio?: Config<Track>
}
export interface Result {
name: 'artplayerPluginHlsControl'
update: () => void
}
export function artplayerPluginHlsControl<Level extends object = QualityLevel, Track extends object = AudioTrack>(option?: Option<Level, Track>): (art: Artplayer) => Result
// Keep the required last signature for historical Parameters<typeof factory>[0] consumers.
export function artplayerPluginHlsControl<Level extends object = QualityLevel, Track extends object = AudioTrack>(option: Option<Level, Track>): (art: Artplayer) => Result
}
declare const artplayerPluginHlsControl: typeof artplayerPluginHlsControlDefinitions.artplayerPluginHlsControl
declare namespace artplayerPluginHlsControl {
export type QualityLevel = artplayerPluginHlsControlDefinitions.QualityLevel
export type AudioTrack = artplayerPluginHlsControlDefinitions.AudioTrack
export type Config<Item extends object = object> = artplayerPluginHlsControlDefinitions.Config<Item>
export type Option<Level extends object = QualityLevel, Track extends object = AudioTrack> = artplayerPluginHlsControlDefinitions.Option<Level, Track>
export type Result = artplayerPluginHlsControlDefinitions.Result
}
export = artplayerPluginHlsControl
export as namespace artplayerPluginHlsControl;
===== docs/assets/ts/artplayer-plugin-jassub.d.ts =====
// Generated from the package public declaration by yarn build:ts. Do not edit.
/* eslint-disable ts/no-redeclare -- Callable and public type namespace intentionally merge. */
declare namespace artplayerPluginJassubDefinitions {
export interface JassubOption {
workerUrl: string
wasmUrl: string
modernWasmUrl: string
subUrl?: string
subContent?: string
timeOffset?: number
debug?: boolean
prescaleFactor?: number
prescaleHeightLimit?: number
maxRenderHeight?: number
fonts?: string[] | Uint8Array[]
availableFonts?: Record<string, Uint8Array | string>
fallbackFont?: string
useLocalFonts?: boolean
libassMemoryLimit?: number
libassGlyphLimit?: number
[key: string]: any
}
export interface JassubInstance {
resize: (force?: boolean, width?: number, height?: number, top?: number, left?: number) => Promise<void>
setVideo: (video: HTMLVideoElement) => Promise<void>
destroy: () => Promise<void>
[key: string]: any
}
export interface Result {
name: 'artplayerPluginJassub'
instance: JassubInstance
}
export const artplayerPluginJassub: (option: JassubOption) => (art: Artplayer) => Result
}
declare const artplayerPluginJassub: typeof artplayerPluginJassubDefinitions.artplayerPluginJassub
declare namespace artplayerPluginJassub {
export type JassubOption = artplayerPluginJassubDefinitions.JassubOption
export type JassubInstance = artplayerPluginJassubDefinitions.JassubInstance
}
export = artplayerPluginJassub
export as namespace artplayerPluginJassub;
===== docs/assets/ts/artplayer-plugin-multiple-subtitles.d.ts =====
// Generated from the package public declaration by yarn build:ts. Do not edit.
/* eslint-disable ts/no-redeclare -- Callable and public type namespace intentionally merge. */
declare namespace artplayerPluginMultipleSubtitlesDefinitions {
export interface TrackOption {
url?: string
name?: string
type?: 'vtt' | 'srt' | 'ass'
encoding?: string
onParser?: (...args: object[]) => object
}
export interface Option {
subtitles: TrackOption[]
}
export interface RuntimeOption {
subtitles?: TrackOption[]
}
export interface LegacyResult {
name: 'multipleSubtitles'
}
export interface Result extends LegacyResult {
tracks: (names?: string[]) => void
reset: () => void
}
/** Historical synchronous extraction; actual registration is asynchronous. */
export type Factory = (option: Option) => (art: Artplayer) => LegacyResult
/** Accurate runtime view available through the /runtime entry. */
export interface RuntimeFactory {
(option: RuntimeOption): (art: Artplayer) => Promise<Result>
default: RuntimeFactory
}
/** Preserve existing parameter extraction and replacement-function compatibility. */
export function artplayerPluginMultipleSubtitles(option: Option): (art: Artplayer) => LegacyResult
}
declare const artplayerPluginMultipleSubtitles: typeof artplayerPluginMultipleSubtitlesDefinitions.artplayerPluginMultipleSubtitles
declare namespace artplayerPluginMultipleSubtitles {
export type TrackOption = artplayerPluginMultipleSubtitlesDefinitions.TrackOption
export type Option = artplayerPluginMultipleSubtitlesDefinitions.Option
export type RuntimeOption = artplayerPluginMultipleSubtitlesDefinitions.RuntimeOption
export type LegacyResult = artplayerPluginMultipleSubtitlesDefinitions.LegacyResult
export type Result = artplayerPluginMultipleSubtitlesDefinitions.Result
export type Factory = artplayerPluginMultipleSubtitlesDefinitions.Factory
export type RuntimeFactory = artplayerPluginMultipleSubtitlesDefinitions.RuntimeFactory
}
export = artplayerPluginMultipleSubtitles
export as namespace artplayerPluginMultipleSubtitles;
===== docs/assets/ts/artplayer-plugin-vast.d.ts =====
// Generated from the package public declaration by yarn build:ts. Do not edit.
/* eslint-disable ts/no-use-before-define, ts/consistent-type-definitions -- Preserve historical export ordering and private aliases. */
export = artplayerPluginVast
export as namespace artplayerPluginVast;
type Option = (params: {
art: Artplayer
id: string
ima: any
imaPlayer: any
$container: HTMLDivElement
playUrl: (url: string) => void
playRes: (res: string) => void
}) => void
type Result = {
name: 'artplayerPluginVast'
}
declare const artplayerPluginVast: (option: Option) => (art: Artplayer) => Result
===== docs/assets/ts/artplayer-plugin-vtt-thumbnail.d.ts =====
// Generated from the package public declaration by yarn build:ts. Do not edit.
/* eslint-disable ts/no-redeclare -- Callable and public type namespace intentionally merge. */
declare namespace artplayerPluginVttThumbnailDefinitions {
export interface Option {
vtt?: string
style?: Partial<CSSStyleDeclaration>
}
export interface Result {
name: 'artplayerPluginVttThumbnail'
}
/** Historical factory type. Actual registration is asynchronous. */
export type Factory = (option: Option) => (art: Artplayer) => Result
/** Accurate runtime view available without casts through the /runtime entry. */
export interface RuntimeFactory {
(option: Option): (art: Artplayer) => Promise<Result>
default: RuntimeFactory
}
/** Preserve historical extraction and replacement-function compatibility. */
export function artplayerPluginVttThumbnail(option: Option): (art: Artplayer) => Result
}
declare const artplayerPluginVttThumbnail: typeof artplayerPluginVttThumbnailDefinitions.artplayerPluginVttThumbnail
declare namespace artplayerPluginVttThumbnail {
export type Option = artplayerPluginVttThumbnailDefinitions.Option
export type Result = artplayerPluginVttThumbnailDefinitions.Result
export type Factory = artplayerPluginVttThumbnailDefinitions.Factory
export type RuntimeFactory = artplayerPluginVttThumbnailDefinitions.RuntimeFactory
}
export = artplayerPluginVttThumbnail
export as namespace artplayerPluginVttThumbnail;
===== docs/assets/ts/artplayer-proxy-canvas.d.ts =====
// Generated from the package public declaration by yarn build:ts. Do not edit.
/* eslint-disable ts/no-redeclare -- Callable and public type namespace intentionally merge. */
declare namespace artplayerProxyCanvasDefinitions {
/** Runs after drawing and bitmap release, before the draw event. */
export type Option = (ctx: CanvasRenderingContext2D, video: HTMLVideoElement) => void
/** Preserve the exact published 1.1.0 return type and its assignability. */
export type Result = HTMLCanvasElement
export type Factory = (option?: Option) => (art: Artplayer) => Result
export type Callable = Factory
/** Explicit view of forwarded media members; native Canvas members win. */
export type MediaCanvas = HTMLCanvasElement & Pick<HTMLVideoElement, Exclude<keyof HTMLVideoElement, keyof HTMLCanvasElement>>
/** Opt-in runtime identity; the historical root factory has no required properties. */
export interface RuntimeFactory extends Factory {
readonly default: RuntimeFactory
}
export const artplayerProxyCanvas: (option?: Option) => (art: Artplayer) => Result
}
declare const artplayerProxyCanvas: typeof artplayerProxyCanvasDefinitions.artplayerProxyCanvas
declare namespace artplayerProxyCanvas {
export type Option = artplayerProxyCanvasDefinitions.Option
export type Result = artplayerProxyCanvasDefinitions.Result
export type Factory = artplayerProxyCanvasDefinitions.Factory
export type Callable = artplayerProxyCanvasDefinitions.Callable
export type MediaCanvas = artplayerProxyCanvasDefinitions.MediaCanvas
export type RuntimeFactory = artplayerProxyCanvasDefinitions.RuntimeFactory
}
export = artplayerProxyCanvas
export as namespace artplayerProxyCanvas;
===== docs/assets/ts/artplayer-proxy-mediabunny.d.ts =====
// Generated from the package public declaration by yarn build:ts. Do not edit.
/* eslint-disable ts/no-redeclare -- Callable and public type namespace intentionally merge. */
declare namespace artplayerProxyMediabunnyDefinitions {
export interface Option {
m3u8?: {
quality?: {
control?: boolean
setting?: boolean
title?: string
auto?: string
getName?: (level: {
id: number
index: number
name: null | string
height: number
bitrate: number
}) => string
}
audio?: {
control?: boolean
setting?: boolean
title?: string
auto?: string
getName?: (track: {
id: number
index: number
name: null | string
lang: string
language: string
bitrate: number
}) => string
}
}
/**
* Timeout for loading media in milliseconds
* @default 0
*/
loadTimeout?: number
/**
* Interval for timeupdate events in milliseconds
* @default 250
*/
timeupdateInterval?: number
/**
* Audio-video synchronization tolerance in seconds
* @default 0.12
*/
avSyncTolerance?: number
/**
* Whether to drop late video frames
* @default false
*/
dropLateFrames?: boolean
/**
* Poster image URL
*/
poster?: string
/**
* Media source (URL, Blob, or ReadableStream)
*/
source?: string | Blob | ReadableStream<Uint8Array>
/**
* Check if server supports range requests before loading
* @default false
*/
preflightRange?: boolean
/**
* Initial volume (0-1)
* @default 0.7
*/
volume?: number
/**
* Initial muted state
* @default false
*/
muted?: boolean
/**
* Autoplay
* @default false
*/
autoplay?: boolean
/**
* Loop playback
* @default false
*/
loop?: boolean
/**
* Cross-origin setting
*/
crossOrigin?: string
}
export type Result = HTMLCanvasElement
export interface HlsLevel {
id: number
index: number
name: string | null
height: number
bitrate: number
/** SDK object; narrow with the SDK version used by your application. */
track: unknown
}
export interface HlsAudio {
id: number
index: number
name: string | null
lang: string
language: string
bitrate: number
/** SDK object; narrow with the SDK version used by your application. */
track: unknown
}
export interface HlsState {
levels: HlsLevel[]
audios: HlsAudio[]
currentLevel: HlsLevel | null
currentAudio: HlsAudio | null
videoMode: 'auto' | 'manual'
audioMode: 'auto' | 'manual'
}
/** Historical RAF estimates, not decoded-frame or network timing measurements. */
export interface SyntheticFrameMetadata {
/** Historical media time in seconds, unlike the native video API. */
presentationTime: number
expectedDisplayTime: number
width: number
height: number
mediaTime: number
presentedFrames: number
processingDuration: number
captureTime: number
receiveTime: number
rtpTimestamp: number
}
export type SyntheticFrameCallback = (now: number, metadata: SyntheticFrameMetadata) => void
export type MediaListener = (event: Event & {
detail: unknown
}) => unknown
/** Opt-in media surface of art.mediabunny; decoder internals are not part of this view. */
export interface MediaBunnyShim {
canvas: HTMLCanvasElement
/** Runtime also accepts SDK sources; the default Option keeps its historical input union. */
src: unknown
readonly currentSrc: unknown
currentTime: number
readonly duration: number
/** Synthetic full-duration/current-time ranges, not measured network buffers. */
readonly buffered: TimeRanges
readonly played: TimeRanges
readonly seekable: TimeRanges
readonly paused: boolean
readonly playing: boolean
readonly ended: boolean
readonly seeking: boolean
readonly readyState: number
readonly networkState: number
readonly error: {
code: number
message: string
} | null
volume: number
muted: boolean
playbackRate: number
readonly videoWidth: number
readonly videoHeight: number
poster: string
/** The following setters are inert; use Option for autoplay/loop/crossOrigin. */
autoplay: boolean
loop: boolean
controls: boolean
playsInline: boolean
crossOrigin: string
preload: string
defaultMuted: boolean
defaultPlaybackRate: number
play: () => Promise<void>
pause: () => void
load: () => void
/** Always returns "maybe"; it does not probe codec/browser support. */
canPlayType: (type: string) => 'maybe'
getM3u8State: () => Promise<HlsState | null>
switchM3u8Quality: (value: unknown) => Promise<void>
switchM3u8Audio: (value: unknown) => Promise<void>
createTimeRanges: (start: number, end: number) => TimeRanges
requestVideoFrameCallback: (callback: SyntheticFrameCallback) => number
cancelVideoFrameCallback: (id: number) => void
addEventListener: (type: string, listener: MediaListener) => void
removeEventListener: (type: string, listener: MediaListener) => void
getBoundingClientRect: () => DOMRect
setAttribute: (name: string, value: unknown) => void
destroy: () => void
}
/** Native Canvas methods win collisions, including DOM event and attribute methods. */
export type MediaBunnyCanvas = HTMLCanvasElement & Omit<MediaBunnyShim, keyof HTMLCanvasElement>
/** The alias is installed by the proxy and removed on player destruction. */
export type MediaBunnyPlayer = Artplayer & {
mediabunny?: MediaBunnyShim
}
export const artplayerProxyMediabunny: (option?: Option) => (art: Artplayer) => Result
}
declare const artplayerProxyMediabunny: typeof artplayerProxyMediabunnyDefinitions.artplayerProxyMediabunny
declare namespace artplayerProxyMediabunny {
export type Option = artplayerProxyMediabunnyDefinitions.Option
export type Result = artplayerProxyMediabunnyDefinitions.Result
export type HlsLevel = artplayerProxyMediabunnyDefinitions.HlsLevel
export type HlsAudio = artplayerProxyMediabunnyDefinitions.HlsAudio
export type HlsState = artplayerProxyMediabunnyDefinitions.HlsState
export type SyntheticFrameMetadata = artplayerProxyMediabunnyDefinitions.SyntheticFrameMetadata
export type SyntheticFrameCallback = artplayerProxyMediabunnyDefinitions.SyntheticFrameCallback
export type MediaListener = artplayerProxyMediabunnyDefinitions.MediaListener
export type MediaBunnyShim = artplayerProxyMediabunnyDefinitions.MediaBunnyShim
export type MediaBunnyCanvas = artplayerProxyMediabunnyDefinitions.MediaBunnyCanvas
export type MediaBunnyPlayer = artplayerProxyMediabunnyDefinitions.MediaBunnyPlayer
}
export = artplayerProxyMediabunny
export as namespace artplayerProxyMediabunny;
===== docs/assets/ts/artplayer-tool-iframe.d.ts =====
// Generated from the package public declaration by yarn build:ts. Do not edit.
/* eslint-disable ts/no-redeclare -- Callable and public type namespace intentionally merge. */
declare namespace ArtplayerToolIframeDefinitions {
export interface Option {
iframe: HTMLIFrameElement
url: string
}
/** Historical open envelope. data remains required in the default class API. */
export interface Message<T = any> {
type: string
data: T
id?: number
}
export interface Callbacks {
resove: (...args: any[]) => any
reject: (...args: any[]) => any
}
/** Public notifications omit the request id and all private session metadata. */
export interface Notification<T = unknown> {
type: string
data: T
}
export type MessageCallback = (this: ArtplayerToolIframe, message: Notification) => void
/** Actual outgoing calls allow an omitted data field. Custom message types remain valid. */
export interface OutboundMessage<T = unknown> {
type: string
data?: T
id?: number
}
/** Known built-in envelopes; use Message/OutboundMessage for application protocols. */
export type ProtocolMessage<T = unknown> = {
type: 'inject'
data?: undefined
id?: number
} | {
type: 'commit'
data: string
id: number
} | {
type: 'response'
data: T
id: number
} | {
type: 'error'
data: unknown
id: number
}
export type Resolve<T> = (value: T | PromiseLike<T>) => void
export type ResolverCallback<T> = (resolve: Resolve<T>) => void
/** Opt-in view. Response T is supplied by the application's protocol, not validated at runtime. */
export interface RuntimeInstance extends Omit<ArtplayerToolIframe, 'messageCallback' | 'postMessage' | 'message'> {
messageCallback: MessageCallback | null
postMessage: <T = unknown>(message: OutboundMessage) => Promise<T>
message: (callback: MessageCallback) => void
}
/** For the existing serialized resolve(...) protocol; callback bodies must use that exact name. */
export interface ResolverInstance extends Omit<RuntimeInstance, 'commit'> {
commit: <T>(callback: ResolverCallback<T>) => Promise<T>
}
/** Opt-in static async/optional-data view. There is no runtime self-default property. */
export interface RuntimeConstructor {
new (option: Option): RuntimeInstance
readonly prototype: RuntimeInstance
readonly iframe: boolean
postMessage: (message: OutboundMessage) => void
onMessage: (event: MessageEvent<OutboundMessage>) => Promise<void>
inject: () => void
}
export class ArtplayerToolIframe {
constructor(option: Option)
static iframe: boolean
static postMessage(message: Message): void
static onMessage(event: MessageEvent & {
data: Message
}): void
static inject(): void
readonly promises: Record<number, Callbacks>
readonly injected: boolean
readonly destroyed: boolean
readonly $iframe: HTMLIFrameElement
readonly url: string
readonly messageCallback: (...args: any[]) => any
onMessage(event: MessageEvent & {
data: Message
}): void
postMessage(message: Message): Promise<any>
commit<T extends (...args: any[]) => any>(callback: T): Promise<ReturnType<T>>
message(callback: (...args: any[]) => any): void
destroy(): void
}
}
declare const ArtplayerToolIframe: typeof ArtplayerToolIframeDefinitions.ArtplayerToolIframe
type ArtplayerToolIframe = ArtplayerToolIframeDefinitions.ArtplayerToolIframe
declare namespace ArtplayerToolIframe {
export type Option = ArtplayerToolIframeDefinitions.Option
export type Message<T = any> = ArtplayerToolIframeDefinitions.Message<T>
export type Callbacks = ArtplayerToolIframeDefinitions.Callbacks
export type Notification<T = unknown> = ArtplayerToolIframeDefinitions.Notification<T>
export type MessageCallback = ArtplayerToolIframeDefinitions.MessageCallback
export type OutboundMessage<T = unknown> = ArtplayerToolIframeDefinitions.OutboundMessage<T>
export type ProtocolMessage<T = unknown> = ArtplayerToolIframeDefinitions.ProtocolMessage<T>
export type Resolve<T> = ArtplayerToolIframeDefinitions.Resolve<T>
export type ResolverCallback<T> = ArtplayerToolIframeDefinitions.ResolverCallback<T>
export type RuntimeInstance = ArtplayerToolIframeDefinitions.RuntimeInstance
export type ResolverInstance = ArtplayerToolIframeDefinitions.ResolverInstance
export type RuntimeConstructor = ArtplayerToolIframeDefinitions.RuntimeConstructor
}
export = ArtplayerToolIframe
export as namespace ArtplayerToolIframe;
===== docs/assets/ts/artplayer-tool-thumbnail.d.ts =====
// Generated from the package public declaration by yarn build:ts. Do not edit.
/** Extract PNG thumbnail sheets from a locally selected video file. */
declare class ArtplayerToolThumbnail {
constructor(option?: ArtplayerToolThumbnail.Option)
static readonly DEFAULTS: ArtplayerToolThumbnail.DefaultOptions
static ondragover(event: DragEvent): void
static creatVideo(): HTMLVideoElement
processing: boolean
option: ArtplayerToolThumbnail.ResolvedOption
video: HTMLVideoElement
duration: number
density: number | undefined
file: File | undefined
videoUrl: string | undefined
thumbnailUrl: string | undefined
e?: ArtplayerToolThumbnail.EventRegistry
setup(option?: ArtplayerToolThumbnail.Option): this
inputChange(event: Event): void
ondrop(event: DragEvent): void
loadVideo(file?: File | null): void
/** Ready-metadata preflight may throw synchronously. Cancellation rejects with AbortError. */
start(): Promise<void>
creatScreenshotDate(): ArtplayerToolThumbnail.ScreenshotPoint[]
creatCanvas(): HTMLCanvasElement
download(): this
errorHandle(condition: unknown, message: string): void
destroy(): void
on<Name extends PropertyKey, Custom extends unknown[], Context>(name: Name, callback: (this: Context, ...args: ArtplayerToolThumbnail.EventArgs<Name, Custom>) => unknown, ctx?: Context): this
once<Name extends PropertyKey, Custom extends unknown[], Context>(name: Name, callback: (this: Context, ...args: ArtplayerToolThumbnail.EventArgs<Name, Custom>) => unknown, ctx?: Context): this
emit<Name extends PropertyKey, Custom extends unknown[]>(name: Name, ...args: ArtplayerToolThumbnail.EventArgs<Name, Custom>): this
off<Name extends PropertyKey, Custom extends unknown[]>(name: Name, callback?: ArtplayerToolThumbnail.Listener<ArtplayerToolThumbnail.EventArgs<Name, Custom>>): this
}
declare namespace ArtplayerToolThumbnail {
interface SheetOptions {
number: number
width: number
height: number
column: number
begin: number
end: number
}
type Compatibility = 'published-3.5' | 'workspace-4.4'
interface DefaultOptions extends SheetOptions {
delay: number
}
/** fileInput must be a file input or an Element wrapper when constructing. */
interface Option extends Partial<SheetOptions> {
fileInput?: Element
compatibility?: Compatibility
/** Published mode delay in milliseconds, clamped to 10-1000. */
delay?: number
[name: string]: unknown
}
interface ResolvedOption extends SheetOptions {
fileInput: HTMLInputElement
compatibility?: Compatibility
delay?: number
[name: string]: unknown
}
interface ScreenshotPoint {
time: number
x: number
y: number
}
interface Events {
file: [
file: File,
]
video: [
video: HTMLVideoElement,
]
canvas: [
canvas: HTMLCanvasElement,
]
update: [
url: string,
progress: number,
]
done: [
]
download: [
name: string,
]
/** Built-in failures are strings; user callbacks may throw any message value. */
error: [
message: unknown,
]
destroy: [
]
}
type EventArgs<Name extends PropertyKey, Custom extends unknown[] = unknown[]> = Name extends keyof Events ? [
...Events[Name],
] : Custom
type Listener<Args extends unknown[]> = ((...args: Args) => unknown) & {
_?: (...args: Args) => unknown
}
/** Heterogeneous listener storage; dispatch through emit to retain event argument checks. */
type EventRegistry = Partial<Record<PropertyKey, {
fn: Listener<never[]>
ctx: unknown
}[]>>
}
export = ArtplayerToolThumbnail
export as namespace ArtplayerToolThumbnail;
===== docs/assets/ts/artplayer.d.ts =====
// Generated from packages/artplayer/public/artplayer.ts by yarn build:ts. Do not edit.
/* eslint-disable ts/no-redeclare -- UMD constructor and named types share the global export. */
declare namespace ArtplayerDefinitions {
export interface ComponentInput extends Omit<ComponentOption, 'html'> {
html?: string | HTMLElement | number
}
export interface Selector {
/**
* Whether the default is selected
*/
default?: boolean
/**
* Html string of selector
*/
html: string | HTMLElement
/**
* Value of selector item
*/
value?: string | number
/**
* Allow custom properties
*/
[key: string]: any
}
export interface Component {
/**
* Component self-increasing id
*/
readonly id: number
/**
* Component parent name
*/
readonly name: string | undefined
/**
* Component parent element
*/
readonly $parent: HTMLElement | undefined
/**
* Whether to show component parent
*/
get show(): boolean
/**
* Whether to show component parent
*/
set show(state: boolean)
/**
* Toggle the component parent
*/
toggle: () => void
/**
* Dynamic add a component
*/
add: {
(option: ComponentOption | ((art: Artplayer) => ComponentOption)): HTMLElement | undefined
(option: ComponentInput | ((art: Artplayer) => ComponentInput)): HTMLElement | undefined
}
/**
* Dynamic remove a component by name
*/
remove: (name: string) => void
/**
* Dynamic update a component
*/
update: {
(option: ComponentOption): HTMLElement | undefined
(option: ComponentInput): HTMLElement | undefined
}
}
export interface ComponentOption {
/**
* Html string or html element of component
*/
html?: string | HTMLElement
/**
* Whether to disable component
*/
disable?: boolean
/**
* Unique name for component
*/
name?: string
/**
* Component sort index
*/
index?: number
/**
* Component style object
*/
style?: Partial<CSSStyleDeclaration>
/**
* Component click event
*/
click?: (this: Artplayer, component: Component, event: Event) => void
/**
* When the component was mounted
*/
mounted?: (this: Artplayer, element: HTMLElement) => void
/**
* When the component was before unmount
*/
beforeUnmount?: (this: Artplayer, element: HTMLElement) => void
/**
* Component tooltip, use in controls
*/
tooltip?: string
/**
* Component position, use in controls
*/
position?: 'top' | 'left' | 'right' | (string & Record<never, never>)
/**
* Custom selector list, use in controls
*/
selector?: Selector[]
/**
* When selector item click, use in controls
*/
onSelect?: (this: Artplayer, selector: Selector, element: HTMLElement, event: Event) => void
}
export interface Config {
readonly properties: readonly [
'audioTracks',
'autoplay',
'buffered',
'controller',
'controls',
'crossOrigin',
'currentSrc',
'currentTime',
'defaultMuted',
'defaultPlaybackRate',
'duration',
'ended',
'error',
'loop',
'mediaGroup',
'muted',
'networkState',
'paused',
'playbackRate',
'played',
'preload',
'readyState',
'seekable',
'seeking',
'src',
'startDate',
'textTracks',
'videoTracks',
'volume',
]
readonly methods: readonly [
'addTextTrack',
'canPlayType',
'load',
'play',
'pause',
]
readonly events: readonly [
'abort',
'canplay',
'canplaythrough',
'durationchange',
'emptied',
'ended',
'error',
'loadeddata',
'loadedmetadata',
'loadstart',
'pause',
'play',
'playing',
'progress',
'ratechange',
'seeked',
'seeking',
'stalled',
'suspend',
'timeupdate',
'volumechange',
'waiting',
]
readonly prototypes: readonly [
'width',
'height',
'videoWidth',
'videoHeight',
'poster',
'webkitDecodedFrameCount',
'webkitDroppedFrameCount',
'playsInline',
'webkitSupportsFullscreen',
'webkitDisplayingFullscreen',
'onenterpictureinpicture',
'onleavepictureinpicture',
'disablePictureInPicture',
'cancelVideoFrameCallback',
'requestVideoFrameCallback',
'getVideoPlaybackQuality',
'requestPictureInPicture',
'webkitEnterFullScreen',
'webkitEnterFullscreen',
'webkitExitFullScreen',
'webkitExitFullscreen',
]
}
/** The event bus exposed by Artplayer.Emitter; no new runtime export. */
export interface Emitter<Events extends {
[Name in keyof Events]: readonly unknown[];
} = Record<PropertyKey, unknown[]>> {
e?: {
[Name in keyof Events]?: {
fn: (...args: [
...Events[Name],
]) => unknown
ctx: unknown
}[];
}
on: <Name extends keyof Events, Context>(name: Name, fn: (this: Context, ...args: [
...Events[Name],
]) => unknown, ctx?: Context) => this
once: <Name extends keyof Events, Context>(name: Name, fn: (this: Context, ...args: [
...Events[Name],
]) => unknown, ctx?: Context) => this
emit: <Name extends keyof Events>(name: Name, ...args: [
...Events[Name],
]) => this
off: <Name extends keyof Events>(name: Name, fn?: (...args: [
...Events[Name],
]) => unknown) => this
}
export interface CssVar {
'--art-theme': string
'--art-font-color': string
'--art-background-color': string
'--art-text-shadow-color': string
'--art-transition-duration': string
'--art-padding': string
'--art-border-radius': string
'--art-progress-height': string
'--art-progress-color': string
'--art-progress-top-gap': string
'--art-hover-color': string
'--art-loaded-color': string
'--art-state-size': string
'--art-state-opacity': number
'--art-bottom-height': string
'--art-bottom-offset': string
'--art-bottom-gap': string
'--art-highlight-width': string
'--art-highlight-color': string
'--art-control-height': string
'--art-control-opacity': number
'--art-control-icon-size': string
'--art-control-icon-scale': number
'--art-volume-height': string
'--art-volume-handle-size': string
'--art-lock-size': string
'--art-indicator-scale': number
'--art-indicator-size': string
'--art-fullscreen-web-index': 9999
'--art-settings-icon-size': string
'--art-settings-max-height': string
'--art-selector-max-height': string
'--art-contextmenus-min-width': string
'--art-subtitle-font-size': string
'--art-subtitle-gap': string
'--art-subtitle-bottom': string
'--art-subtitle-border': string
'--art-widget-background': string
'--art-tip-background': string
'--art-scrollbar-size': string
'--art-scrollbar-background': string
'--art-scrollbar-background-hover': string
'--art-mini-progress-height': string
}
export type I18nKeys = 'en' | 'zh-cn' | 'zh-tw' | 'pl' | 'cs' | 'es' | 'fa' | 'fr' | 'id' | 'ru' | 'tr' | 'ar' | 'vi' | (string & Record<never, never>)
export interface I18nValue {
'Context Menu'?: string
'Lock'?: string
'Video Info': string
'Close': string
'Video Load Failed': string
'Volume': string
'Progress'?: string
'Back'?: string
'Settings'?: string
'Play': string
'Pause': string
'Rate': string
'Mute': string
'Video Flip': string
'Horizontal': string
'Vertical': string
'Reconnect': string
'Show Setting': string
'Hide Setting': string
'Screenshot': string
'Play Speed': string
'Aspect Ratio': string
'Default': string
'Normal': string
'Open': string
'Switch Video': string
'Switch Subtitle': string
'Fullscreen': string
'Exit Fullscreen': string
'Web Fullscreen': string
'Exit Web Fullscreen': string
'Mini Player': string
'PIP Mode': string
'Exit PIP Mode': string
'PIP Not Supported': string
'Fullscreen Not Supported': string
'Subtitle Offset': string
'Last Seen': string
'Jump Play': string
'AirPlay': string
'AirPlay Not Available': string
}
export type I18n = Partial<Record<I18nKeys, Partial<I18nValue>>>
export interface Icons {
readonly loading: HTMLDivElement
readonly state: HTMLDivElement
readonly play: HTMLDivElement
readonly pause: HTMLDivElement
readonly check: HTMLDivElement
readonly volume: HTMLDivElement
readonly volumeClose: HTMLDivElement
readonly screenshot: HTMLDivElement
readonly setting: HTMLDivElement
readonly pip: HTMLDivElement
readonly arrowLeft: HTMLDivElement
readonly arrowRight: HTMLDivElement
readonly playbackRate: HTMLDivElement
readonly aspectRatio: HTMLDivElement
readonly config: HTMLDivElement
readonly lock: HTMLDivElement
readonly flip: HTMLDivElement
readonly unlock: HTMLDivElement
readonly fullscreenOff: HTMLDivElement
readonly fullscreenOn: HTMLDivElement
readonly fullscreenWebOff: HTMLDivElement
readonly fullscreenWebOn: HTMLDivElement
readonly switchOn: HTMLDivElement
readonly switchOff: HTMLDivElement
readonly error: HTMLDivElement
readonly close: HTMLDivElement
readonly airplay: HTMLDivElement
readonly [key: string]: HTMLDivElement
}
export type PluginFactory<Host = Artplayer, Result = unknown> = (this: Host, art: Host) => Result
/** Augment this interface with installed plugin results; augmentation does not register a plugin. */
export interface Plugins {
/** Legacy signature: synchronous factories return this registry; Promise factories return a Promise of it. */
add: (plugin: PluginFactory) => Promise<Plugins>
[name: string]: unknown
}
export interface Quality {
/**
* Whether the default is selected
*/
default?: boolean
/**
* Html string of quality
*/
html: string | HTMLElement
/**
* Video quality url
*/
url: string
}
export interface SettingOption extends Omit<Setting, 'html' | 'icon' | 'tooltip'> {
html: string
icon: string | undefined
tooltip: string | undefined
$item: HTMLDivElement
$icon: HTMLDivElement | undefined
$html: HTMLDivElement
$tooltip: HTMLDivElement | undefined
$switch: HTMLDivElement | undefined
$range: HTMLInputElement | undefined
$parent: SettingOption | undefined
$parents: SettingOption[]
$option: SettingOption[]
$events: Array<() => void>
$formatted: boolean
}
export interface Setting {
/**
* Html string or html element of setting name
*/
html: string | HTMLElement
/**
* Html string or html element of setting icon
*/
icon?: string | HTMLElement
/**
* The width of setting
*/
width?: number
/**
* The tooltip of setting
*/
tooltip?: string | HTMLElement
/**
* Whether the default is selected
*/
default?: boolean
/**
* Custom selector list
*/
selector?: Setting[]
/**
* When the setting was mounted
*/
mounted?: (this: Artplayer, panel: HTMLDivElement, item: Setting) => void
/**
* When selector item click
*/
onSelect?: (this: Artplayer, item: SettingOption, element: HTMLDivElement, event: Event) => void
/**
* Custom switch item
*/
switch?: boolean
/**
* When switch item click
*/
onSwitch?: (this: Artplayer, item: SettingOption, element: HTMLDivElement, event: Event) => void
/**
* Custom range item
*/
range?: [
value?: number,
min?: number,
max?: number,
step?: number,
]
/**
* When range item change
*/
onRange?: (this: Artplayer, item: SettingOption, element: HTMLDivElement, event: Event) => void
/**
* When range item change in real time
*/
onChange?: (this: Artplayer, item: SettingOption, element: HTMLDivElement, event: Event) => void
/**
* When range item change in real time
*/
onClick?: (this: Artplayer, item: SettingOption, element: HTMLDivElement, event: Event) => void
/**
* Allow custom properties
*/
[key: string]: any
}
export interface Subtitle {
/**
* The subtitle url
*/
url?: string
/**
* The subtitle name
*/
name?: string
/**
* The subtitle type
*/
type?: 'vtt' | 'srt' | 'ass' | (string & Record<never, never>)
/**
* The subtitle style object
*/
style?: Partial<CSSStyleDeclaration>
/**
* The subtitle encoding, default utf-8
*/
encoding?: string
/**
* Whether use escape, default true
*/
escape?: boolean
/**
* Change the vtt text
*/
onVttLoad?: (vtt: string) => string
}
export type CustomType = 'flv' | 'm3u8' | 'hls' | 'ts' | 'mpd' | 'torrent' | (string & Record<never, never>)
export interface Thumbnails {
/**
* The thumbnail image url
*/
url: string
/**
* The thumbnail item number
*/
number?: number
/**
* The thumbnail column size
*/
column?: number
/**
* The thumbnail width
*/
width?: number
/**
* The thumbnail height
*/
height?: number
/**
* The thumbnail scale
*/
scale?: number
}
/** Constructor input; Option retains its historical required URL/read types. */
export interface OptionInput extends Omit<Option$1, 'url' | 'controls' | 'layers' | 'contextmenu'> {
url?: string
controls?: ComponentInput[]
layers?: ComponentInput[]
contextmenu?: ComponentInput[]
}
export interface Option$1 {
/**
* The player id
*/
id?: string
/**
* The container mounted by the player
*/
container: string | HTMLDivElement
/**
* Video url
*/
url: string
/**
* Video poster image url
*/
poster?: string
/**
* Video url type
*/
type?: CustomType
/**
* Player color theme
*/
theme?: string
/**
* Player language
*/
lang?: keyof I18n
/**
* Player default volume
*/
volume?: number
/**
* Whether live broadcast mode
*/
isLive?: boolean
/**
* Whether video muted
*/
muted?: boolean
/**
* Whether video auto play
*/
autoplay?: boolean
/**
* Whether player auto resize
*/
autoSize?: boolean
/**
* Whether player auto run mini mode
*/
autoMini?: boolean
/**
* Whether video auto loop
*/
loop?: boolean
/**
* Whether show video flip button
*/
flip?: boolean
/**
* Whether show video playback rate button
*/
playbackRate?: boolean
/**
* Whether show video aspect ratio button
*/
aspectRatio?: boolean
/**
* Whether show video screenshot button
*/
screenshot?: boolean
/**
* Whether show video setting button
*/
setting?: boolean
/**
* Whether to enable player hotkey
*/
hotkey?: boolean
/**
* Whether show video pip button
*/
pip?: boolean
/**
* Do you want to run only one player at a time
*/
mutex?: boolean
/**
* Whether use backdrop in UI
*/
backdrop?: boolean
/**
* Whether show video window fullscreen button
*/
fullscreen?: boolean
/**
* Whether show video web fullscreen button
*/
fullscreenWeb?: boolean
/**
* Whether to enable player subtitle offset
*/
subtitleOffset?: boolean
/**
* Whether to enable player mini progress bar
*/
miniProgressBar?: boolean
/**
* Whether use SSR function
*/
useSSR?: boolean
/**
* Whether use playsInline in mobile
*/
playsInline?: boolean
/**
* Whether use lock in mobile
*/
lock?: boolean
/**
* Whether use gesture in mobile
*/
gesture?: boolean
/**
* Whether use fast forward in mobile
*/
fastForward?: boolean
/**
* Whether use auto playback
*/
autoPlayback?: boolean
/**
* Whether use auto orientation in mobile
*/
autoOrientation?: boolean
/**
* Whether use airplay
*/
airplay?: boolean
/**
* Custom video proxy
*/
proxy?: (this: Artplayer, art: Artplayer) => HTMLCanvasElement | HTMLVideoElement | undefined
/**
* Custom plugin list
*/
plugins?: PluginFactory[]
/**
* Custom layer list
*/
layers?: ComponentOption[]
/**
* Custom contextmenu list
*/
contextmenu?: ComponentOption[]
/**
* Custom control list
*/
controls?: ComponentOption[]
/**
* Custom setting list
*/
settings?: Setting[]
/**
* Custom video quality list
*/
quality?: Quality[]
/**
* Custom highlight list
*/
highlight?: {
/**
* The highlight time
*/
time: number
/**
* The highlight text
*/
text: string
}[]
/**
* Custom thumbnail
*/
thumbnails?: Thumbnails
/**
* Custom subtitle option
*/
subtitle?: Subtitle
/**
* Other video attribute
*/
moreVideoAttr?: Partial<Readonly<{
[K in keyof HTMLVideoElement as HTMLVideoElement[K] extends (...args: unknown[]) => unknown ? never : K]: HTMLVideoElement[K];
}>>
/**
* Custom i18n
*/
i18n?: I18n
/**
* Custom default icons
*/
icons?: {
[key in keyof Icons]?: HTMLElement | string;
}
/**
* Custom css variables
*/
cssVar?: Partial<CssVar>
/**
* Custom video type function
*/
customType?: Partial<Record<CustomType, (this: Artplayer, video: HTMLVideoElement, url: string, art: Artplayer) => unknown | Promise<unknown>>>
}
export type AspectRatio = 'default' | '4:3' | '16:9' | (`${number}:${number}` & Record<never, never>)
export type PlaybackRate = 0.5 | 0.75 | 1 | 1.25 | 1.5 | 1.75 | 2 | (number & Record<never, never>)
export type Flip = 'normal' | 'horizontal' | 'vertical' | (string & Record<never, never>)
export type State = 'standard' | 'mini' | 'pip' | 'fullscreen' | 'fullscreenWeb'
export class Player {
get aspectRatio(): AspectRatio
set aspectRatio(ratio: AspectRatio)
get state(): State
set state(state: State)
get type(): CustomType
set type(name: CustomType)
get playbackRate(): PlaybackRate
set playbackRate(rate: PlaybackRate)
get currentTime(): number
set currentTime(time: number)
get duration(): number
get played(): number
get playing(): boolean
get flip(): Flip
set flip(state: Flip)
get fullscreen(): boolean
set fullscreen(state: boolean)
get fullscreenWeb(): boolean
set fullscreenWeb(state: boolean)
get loaded(): number
get loadedTime(): number
get mini(): boolean
set mini(state: boolean)
get pip(): boolean
set pip(state: boolean)
get poster(): string
set poster(url: string)
get rect(): DOMRect
get bottom(): number
get height(): number
get left(): number
get right(): number
get top(): number
get width(): number
get x(): number
get y(): number
set seek(time: number)
get seek(): number
set forward(time: number)
get forward(): number
set backward(time: number)
get backward(): number
get url(): string
set url(url: string)
get volume(): number
set volume(percentage: number)
get muted(): boolean
set muted(state: boolean)
get theme(): string
set theme(theme: string)
get subtitleOffset(): number
set subtitleOffset(time: number)
get switch(): string
set switch(url: string)
get quality(): Quality[]
set quality(quality: Quality[])
get thumbnails(): Thumbnails
set thumbnails(thumbnails: Thumbnails)
pause(): void
play(): Promise<void>
/** Legacy signature; runtime preserves pause's synchronous result or play's Promise. */
toggle(): void
attr(key: string, value?: unknown): unknown
cssVar<T extends keyof CssVar>(key: T, value?: CssVar[T]): CssVar[T]
switchUrl(url: string): Promise<void>
switchQuality(url: string): Promise<void>
getDataURL(): Promise<string>
getBlobUrl(): Promise<string>
screenshot(name?: string): Promise<string>
airplay(): void
autoSize(): void
autoHeight(): void
reset(): void
}
export type Bar = 'loaded' | 'played' | 'hover'
/** Actual built-in subtitle update payloads; legacy Events keeps its scalar types. */
export interface SubtitleUpdateEvents {
subtitleBeforeUpdate: [
cues: VTTCue[],
]
subtitleAfterUpdate: [
cues: VTTCue[],
]
}
export interface Events {
'document:click': [
event: Event,
]
'document:mouseup': [
event: Event,
]
'document:keydown': [
event: Event,
]
'document:touchend': [
event: Event,
]
'document:touchcancel': [
event: Event,
]
'document:touchmove': [
event: Event,
]
'document:mousemove': [
event: Event,
]
'document:pointerup': [
event: Event,
]
'document:contextmenu': [
event: Event,
]
'document:pointermove': [
event: Event,
]
'document:visibilitychange': [
event: Event,
]
'document:webkitfullscreenchange': [
event: Event,
]
'window:resize': [
event: Event,
]
'window:scroll': [
event: Event,
]
'window:orientationchange': [
event: Event,
]
'video:abort': [
event: Event,
]
'video:canplay': [
event: Event,
]
'video:canplaythrough': [
event: Event,
]
'video:complete': [
event: Event,
]
'video:durationchange': [
event: Event,
]
'video:emptied': [
event: Event,
]
'video:encrypted': [
event: Event,
]
'video:ended': [
event: Event,
]
'video:error': [
error: Error,
]
'video:loadeddata': [
event: Event,
]
'video:loadedmetadata': [
event: Event,
]
'video:loadstart': [
event: Event,
]
'video:pause': [
event: Event,
]
'video:play': [
event: Event,
]
'video:playing': [
event: Event,
]
'video:progress': [
event: Event,
]
'video:ratechange': [
event: Event,
]
'video:seeked': [
event: Event,
]
'video:seeking': [
event: Event,
]
'video:stalled': [
event: Event,
]
'video:suspend': [
event: Event,
]
'video:timeupdate': [
event: Event,
]
'video:volumechange': [
event: Event,
]
'video:waiting': [
event: Event,
]
'info': [
state: boolean,
]
'layer': [
state: boolean,
]
'loading': [
state: boolean,
]
'mask': [
state: boolean,
]
'subtitle': [
state: boolean,
]
'contextmenu': [
state: boolean,
]
'control': [
state: boolean,
]
'setting': [
state: boolean,
]
'hotkey': [
event: KeyboardEvent,
]
'destroy': [
]
'subtitleOffset': [
offset: number,
]
/** Legacy contextual type; annotate listeners with VTTCue[] for the runtime payload. */
'subtitleBeforeUpdate': [
cue: VTTCue,
]
/** Legacy contextual type; annotate listeners with VTTCue[] for the runtime payload. */
'subtitleAfterUpdate': [
cue: VTTCue,
]
'subtitleLoad': [
cues: VTTCue[],
option: Subtitle,
]
'focus': [
event: Event,
]
'blur': [
event: Event,
]
'dblclick': [
event: Event,
]
'click': [
event: Event,
]
'hover': [
state: boolean,
event: Event,
]
'mousemove': [
event: Event,
]
'resize': [
]
'view': [
state: boolean,
]
'lock': [
state: boolean,
]
'aspectRatio': [
aspectRatio: AspectRatio,
]
'autoHeight': [
height: number,
]
'autoSize': [
size: {
width: number
height: number
},
]
'ready': [
]
'airplay': [
]
'raf': [
]
'error': [
error: Error,
reconnectTime: number,
]
'flip': [
flip: Flip,
]
'fullscreen': [
state: boolean,
]
'fullscreenError': [
event: Event,
]
'fullscreenWeb': [
state: boolean,
]
'mini': [
state: boolean,
]
'pause': [
]
'pip': [
state: boolean,
]
'play': [
]
'screenshot': [
dataUri: string,
]
'seek': [
currentTime: number,
time: number,
]
'restart': [
url: string,
]
'muted': [
state: boolean,
]
'setBar': [
type: Bar,
percentage: number,
event?: Event | undefined,
]
'keydown': [
event: KeyboardEvent,
]
}
/** Accurate playback method view; assign an existing player without a runtime wrapper. */
export interface PlaybackControls {
play: () => Promise<void>
pause: () => void
toggle: () => Promise<void> | void
}
export interface Template {
readonly html: string
readonly $container: HTMLDivElement
readonly $player: HTMLDivElement
readonly $video: HTMLVideoElement
readonly $track: HTMLTrackElement
readonly $poster: HTMLDivElement
readonly $subtitle: HTMLDivElement
readonly $danmuku: HTMLDivElement
readonly $bottom: HTMLDivElement
readonly $progress: HTMLDivElement
readonly $controls: HTMLDivElement
readonly $controlsLeft: HTMLDivElement
readonly $controlsCenter: HTMLDivElement
readonly $controlsRight: HTMLDivElement
readonly $layer: HTMLDivElement
readonly $loading: HTMLDivElement
readonly $notice: HTMLDivElement
readonly $noticeInner: HTMLDivElement
readonly $mask: HTMLDivElement
readonly $state: HTMLDivElement
readonly $setting: HTMLDivElement
readonly $info: HTMLDivElement
readonly $infoPanel: HTMLDivElement
readonly $infoClose: HTMLDivElement
readonly $contextmenu: HTMLDivElement
}
export interface Utils {
isBrowser: boolean
userAgent: string
isMobile: boolean
isSafari: boolean
isIOS: boolean
isIOS13: boolean
query: <T extends Element = Element>(selector: string, parent?: Document | HTMLElement) => T | null
queryAll: <T extends Element = Element>(selector: string, parent?: Document | HTMLElement) => T[]
addClass: (target: HTMLElement, className: string) => void
removeClass: (target: HTMLElement, className: string) => void
hasClass: (target: HTMLElement, className: string) => boolean
append: (target: HTMLElement, child: HTMLElement | string) => Element | ChildNode
remove: (target: HTMLElement) => HTMLElement
replaceElement: (newChild: HTMLElement, oldChild: HTMLElement) => HTMLElement
siblings: (target: HTMLElement) => HTMLElement[]
inverseClass: (target: HTMLElement, className: string) => void
createElement: <K extends keyof HTMLElementTagNameMap>(tag: K) => HTMLElementTagNameMap[K]
setStyle: <T extends keyof CSSStyleDeclaration>(element: HTMLElement, key: T, value: string | CSSStyleDeclaration[T]) => HTMLElement
setStyles: (element: HTMLElement, styles: Partial<CSSStyleDeclaration>) => HTMLElement
getStyle: {
(element: HTMLElement, key: keyof CSSStyleDeclaration, numberType?: true): number
(element: HTMLElement, key: keyof CSSStyleDeclaration, numberType: false): string
}
setStyleText: (id: string, cssText: string) => void
getRect: (el: HTMLElement) => {
top: number
left: number
width: number
height: number
}
tooltip: (target: HTMLElement, msg: string, pos?: string) => void
isInViewport: (target: HTMLElement, offset?: number) => boolean
includeFromEvent: (event: Event, target: HTMLElement) => boolean
getSafeAreaInsets: () => {
top: number
right: number
bottom: number
left: number
}
srtToVtt: (srtText: string) => string
vttToBlob: (vttText: string) => string
assToVtt: (assText: string) => string
getExt: (url: string) => string
download: (url: string, name: string) => void
loadImg: (url: string, scale?: number) => Promise<HTMLImageElement>
errorHandle: <T extends boolean>(condition: T, msg: string) => T extends true ? T : never
silencePromise: <T>(value: T) => T extends Promise<infer R> ? Promise<R | undefined> : T
def: {
/** Historical string-key signature; runtime returns obj. */
(obj: object, name: string, value: unknown): void
<T>(obj: T, name: PropertyKey, value: PropertyDescriptor & ThisType<T>): T
}
has: (obj: object, name: PropertyKey) => boolean
get: (obj: object, name: PropertyKey) => PropertyDescriptor | undefined
mergeDeep: <T extends object[]>(...args: T) => T[number]
sleep: (ms?: number) => Promise<void>
/** Historical return type; runtime discards the callback result and ignores context. */
debounce: <F extends (...args: any[]) => any>(func: F, wait: number, context?: object) => (...args: Parameters<F>) => ReturnType<F>
/** Historical return type; runtime discards the callback result. */
throttle: <F extends (...args: any[]) => any>(func: F, wait: number) => (...args: Parameters<F>) => ReturnType<F>
clamp: (num: number, a: number, b: number) => number
secondToTime: (second: number) => string
escape: (str: string) => string
unescape: (str: string) => string
capitalize: (str: string) => string
ArtPlayerError: new (message?: string, context?: ((...args: never[]) => unknown) | (abstract new (...args: never[]) => object)) => Error
getIcon: (key?: string, html?: string | HTMLElement) => HTMLElement
getComposedPath: (event: Event) => EventTarget[]
supportsFlex: () => boolean
}
export class Artplayer extends Player {
constructor(option: Option$1, readyCallback?: (this: Artplayer, art: Artplayer) => unknown)
constructor(option: OptionInput, readyCallback?: (this: Artplayer, art: Artplayer) => unknown)
static readonly instances: Artplayer[]
static readonly version: string
static readonly env: 'development' | 'production'
static readonly build: string
static readonly config: Config
static readonly utils: Utils
static readonly scheme: Record<keyof Option$1, unknown>
static readonly Emitter: new <Events extends {
[Name in keyof Events]: readonly unknown[];
} = Record<PropertyKey, unknown[]>>(...args: unknown[]) => Emitter<Events>
static readonly validator: <T extends object>(option: T, scheme: object) => T
static readonly kindOf: (item: unknown) => string
static readonly html: Artplayer['template']['html']
static readonly option: Option$1
static STYLE: string
static DEBUG: boolean
static CONTEXTMENU: boolean
static NOTICE_TIME: number
static SETTING_WIDTH: number
static SETTING_ITEM_WIDTH: number
static SETTING_ITEM_HEIGHT: number
static RESIZE_TIME: number
static SCROLL_TIME: number
static SCROLL_GAP: number
static AUTO_PLAYBACK_MAX: number
static AUTO_PLAYBACK_MIN: number
static AUTO_PLAYBACK_TIMEOUT: number
static RECONNECT_TIME_MAX: number
static RECONNECT_SLEEP_TIME: number
static CONTROL_HIDE_TIME: number
static DBCLICK_TIME: number
static DBCLICK_FULLSCREEN: boolean
static MOBILE_DBCLICK_PLAY: boolean
static MOBILE_CLICK_PLAY: boolean
static AUTO_ORIENTATION_TIME: number
static INFO_LOOP_TIME: number
static FAST_FORWARD_VALUE: number
static FAST_FORWARD_TIME: number
static TOUCH_MOVE_RATIO: number
static VOLUME_STEP: number
static SEEK_STEP: number
static PLAYBACK_RATE: number[]
static ASPECT_RATIO: string[]
static FLIP: string[]
static FULLSCREEN_WEB_IN_BODY: boolean
static LOG_VERSION: boolean
static USE_RAF: boolean
static REMOVE_SRC_WHEN_DESTROY: boolean
readonly id: number
readonly option: Option$1
readonly isLock: boolean
readonly isReady: boolean
readonly isFocus: boolean
readonly isInput: boolean
readonly isRotate: boolean
readonly isDestroy: boolean
flv?: unknown
m3u8?: unknown
hls?: unknown
ts?: unknown
mpd?: unknown
torrent?: unknown
on<T extends keyof Events>(name: T, fn: (...args: Events[T]) => unknown, ctx?: object): this
on<T extends keyof SubtitleUpdateEvents>(name: T, fn: (...args: SubtitleUpdateEvents[T]) => unknown, ctx?: object): this
on(name: string, fn: (...args: unknown[]) => unknown, ctx?: object): this
once<T extends keyof Events>(name: T, fn: (...args: Events[T]) => unknown, ctx?: object): this
once<T extends keyof SubtitleUpdateEvents>(name: T, fn: (...args: SubtitleUpdateEvents[T]) => unknown, ctx?: object): this
once(name: string, fn: (...args: unknown[]) => unknown, ctx?: object): this
emit<T extends keyof Events>(name: T, ...args: Events[T]): this
emit<T extends keyof SubtitleUpdateEvents>(name: T, ...args: SubtitleUpdateEvents[T]): this
emit(name: string, ...args: unknown[]): this
off<T extends keyof Events>(name: T, callback?: (...args: Events[T]) => unknown): this
off<T extends keyof SubtitleUpdateEvents>(name: T, callback?: (...args: SubtitleUpdateEvents[T]) => unknown): this
off(name: string, callback?: (...args: unknown[]) => unknown): this
query: Artplayer['template']['query']
proxy: Artplayer['events']['proxy']
video: Artplayer['template']['$video']
e: {
[K in keyof Events]?: {
fn: (...args: Events[K]) => unknown
ctx: unknown
}[];
}
destroy(removeHtml?: boolean): void
reset(): void
readonly template: {
get html(): string
query: <T extends Element = Element>(selector: string) => T | null
} & Template
readonly events: {
proxy: {
(target: EventTarget, eventName: string, handler: (event: Event) => void, options?: boolean | AddEventListenerOptions): () => void
(target: EventTarget, eventName: string[], handler: (event: Event) => void, options?: boolean | AddEventListenerOptions): Array<() => void>
}
hover: (element: HTMLElement, mouseenter?: (event: Event) => any, mouseleave?: (event: Event) => any) => void
remove: (destroyEvent: () => void) => void
destroy: () => void
bindGlobalEvents: (source?: {
window?: Window
document?: Document
}) => void
}
readonly storage: {
name: string
settings: Record<string, unknown>
get: {
(key: string): unknown
(): Record<string, unknown>
}
set: (key: string, value: unknown) => void
del: (key: string) => void
clear: () => void
}
readonly icons: Icons
readonly i18n: {
languages: I18n
language: Partial<Record<string, string>>
init: () => void
get: (key: string) => string
update: (language: Partial<I18n>) => void
}
readonly notice: {
timer: number | null
get show(): string | Error | false | ''
set show(msg: string | Error | false | '')
destroy: () => void
}
readonly layers: Record<string, HTMLElement | undefined> & Component
readonly controls: Record<string, HTMLElement | undefined> & Component
readonly contextmenu: Record<string, HTMLElement | undefined> & Component
readonly subtitle: {
get url(): string
set url(url: string)
get textTrack(): TextTrack | undefined
get activeCues(): VTTCue[]
get cues(): VTTCue[]
style: (name: string | Partial<CSSStyleDeclaration>, value?: string) => void
switch: (url: string, option?: Subtitle) => Promise<string>
init: (subtitle: Subtitle) => Promise<string | null | undefined>
} & Component
readonly info: Component
readonly loading: Component
readonly hotkey: {
keys: Record<string, ((event: KeyboardEvent) => any)[]>
add: (key: string, callback: (this: Artplayer, event: KeyboardEvent) => any) => Artplayer['hotkey']
remove: (key: string, callback: (event: KeyboardEvent) => any) => Artplayer['hotkey']
}
readonly mask: Component
readonly setting: {
option: SettingOption[]
updateStyle: (width?: number) => void
/** Legacy return signature; a missing runtime entry is null. */
find: (name: string) => SettingOption | undefined
/** Legacy return signature; runtime returns the formatted input item. */
add: (setting: Setting) => Artplayer['setting']
/** Legacy return signature; runtime returns the updated or added item. */
update: (settings: Setting) => Artplayer['setting']
/** Legacy return signature; runtime returns undefined. */
remove: (name: string) => Artplayer['setting']
} & Component
readonly plugins: Plugins
}
}
declare const Artplayer: typeof ArtplayerDefinitions.Artplayer
type Artplayer = ArtplayerDefinitions.Artplayer
declare namespace Artplayer {
export type Config = ArtplayerDefinitions.Config
export type Emitter<Events extends {
[Name in keyof Events]: readonly unknown[];
} = Record<PropertyKey, unknown[]>> = ArtplayerDefinitions.Emitter<Events>
export type I18n = ArtplayerDefinitions.I18n
export type Icons = ArtplayerDefinitions.Icons
export type PluginFactory<Host = Artplayer, Result = unknown> = ArtplayerDefinitions.PluginFactory<Host, Result>
export type Plugins = ArtplayerDefinitions.Plugins
export type SettingOption = ArtplayerDefinitions.SettingOption
export type Setting = ArtplayerDefinitions.Setting
export type Subtitle = ArtplayerDefinitions.Subtitle
export type OptionInput = ArtplayerDefinitions.OptionInput
export type Player = ArtplayerDefinitions.Player
export type SubtitleUpdateEvents = ArtplayerDefinitions.SubtitleUpdateEvents
export type Events = ArtplayerDefinitions.Events
export type PlaybackControls = ArtplayerDefinitions.PlaybackControls
export type Template = ArtplayerDefinitions.Template
export type Utils = ArtplayerDefinitions.Utils
export type Option = ArtplayerDefinitions.Option$1
}
export = Artplayer
export as namespace Artplayer;
===== docs/assets/ts/artplayer-i18n.d.ts =====
declare module 'artplayer/i18n/*' {
const language: NonNullable<Artplayer.I18n['en']>
export default language
}
===== Examples Summary =====
===== docs/assets/example/ads.js =====
// npm i artplayer-plugin-ads
// import artplayerPluginAds from 'artplayer-plugin-ads';
const art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
autoSize: true,
fullscreen: true,
fullscreenWeb: true,
plugins: [
artplayerPluginAds({
// html广告,假如是视频广告则忽略该值
html: '<img src="/assets/sample/poster.jpg">',
// 视频广告的地址
video: '/assets/sample/test1.mp4',
// 广告跳转网址,为空则不跳转
url: 'http://artplayer.org',
// 必须观看的时长,期间不能被跳过,单位为秒
// 当该值大于或等于totalDuration时,不能提前关闭广告
// 当该值等于或小于0时,则随时都可以关闭广告
playDuration: 5,
// 广告总时长,单位为秒
totalDuration: 10,
// 多语言支持
i18n: {
close: '关闭广告',
countdown: '%s秒',
detail: '查看详情',
canBeClosed: '%s秒后可关闭广告',
},
}),
],
})
// 广告被点击
art.on('artplayerPluginAds:click', (ads) => {
console.info('广告被点击', ads)
})
// 广告被跳过
art.on('artplayerPluginAds:skip', (ads) => {
console.info('广告被跳过', ads)
})
===== docs/assets/example/ambilight.js =====
// npm i artplayer-plugin-ambilight
// import artplayerPluginAmbilight from 'artplayer-plugin-ambilight';
const art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
autoSize: true,
plugins: [
artplayerPluginAmbilight({
blur: '50px',
opacity: 1,
frequency: 10,
duration: 0.3,
}),
],
})
===== docs/assets/example/asr.js =====
const art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/steve-jobs.mp4',
autoSize: true,
fullscreen: true,
fullscreenWeb: true,
moreVideoAttr: {
// crossOrigin: 'anonymous',
},
plugins: [
artplayerPluginAsr({
length: 2,
interval: 40,
sampleRate: 16000,
autoHideTimeout: 10000,
// Use your AI tool to convert pcm into subtitles
onAudioChunk: ({ pcm }) => startAsr(pcm),
}),
],
})
let ws = null
let loading = false
function stopAsr() {
try {
ws.send(JSON.stringify({ type: 'end' }))
ws.close()
}
catch {}
ws = null
loading = false
}
async function startAsr(buffer) {
if (loading)
return
if (!ws) {
loading = true
const api = 'https://api.aimu.app/asr/tencent?engine_model_type=16k_en'
const { url } = await (await fetch(api)).json()
ws = new WebSocket(url)
ws.binaryType = 'arraybuffer'
ws.onmessage = (event) => {
const { code, result, message } = JSON.parse(event.data)
if (code === 0) {
art.plugins.artplayerPluginAsr.append(result?.voice_text_str)
}
else {
console.error(code, message)
stopAsr()
}
}
loading = false
}
if (ws?.readyState === WebSocket.OPEN) {
ws.send(buffer)
}
}
art.on('destroy', stopAsr)
===== docs/assets/example/asr.local.js =====
/* global Artplayer, artplayerPluginAsr */
// Local audio capture demo. The subtitles below are simulated, not recognized speech.
// No audio is uploaded; only the sample media is loaded from this local site.
const statistics = document.createElement('div')
statistics.textContent = 'Local ASR demo: press play. No recognition service is used.'
let chunks = 0
let pcmBytes = 0
let wavBytes = 0
const art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/steve-jobs.mp4',
autoSize: true,
fullscreen: true,
fullscreenWeb: true,
layers: [{
name: 'asr-local-statistics',
html: statistics,
style: {
position: 'absolute',
top: '12px',
left: '12px',
right: '12px',
padding: '8px 12px',
background: 'rgba(0, 0, 0, 0.65)',
color: '#fff',
fontSize: '12px',
whiteSpace: 'pre-line',
pointerEvents: 'none',
},
}],
controls: [{
name: 'asr-local-stop',
position: 'right',
html: 'Stop ASR',
tooltip: 'Stop capture; pause and play to restart',
async click() {
await art.plugins.artplayerPluginAsr.stop()
if (!art.isDestroy)
statistics.textContent = 'Local capture stopped. Pause and play to restart. Nothing was uploaded.'
},
}],
plugins: [artplayerPluginAsr({
length: 2,
interval: 250,
sampleRate: 16000,
autoHideTimeout: 5000,
onAudioChunk({ pcm, wav }) {
if (art.isDestroy)
return
chunks++
pcmBytes += pcm.byteLength
wavBytes += wav.byteLength
const sampleRate = new DataView(wav).getUint32(24, true)
const samples = new DataView(pcm)
let peak = 0
for (let offset = 0; offset < pcm.byteLength; offset += 2)
peak = Math.max(peak, Math.abs(samples.getInt16(offset, true)))
const duration = (pcmBytes / 2 / sampleRate).toFixed(2)
statistics.textContent = [
'Local capture only - simulated subtitles, no speech recognition',
`Chunks: ${chunks} | ${sampleRate} Hz mono PCM16 | ${duration} seconds captured`,
`PCM: ${pcmBytes} bytes | WAV: ${wavBytes} bytes | Current peak: ${peak}`,
].join('\n')
return `Simulated local subtitle: audio chunk ${chunks} received.`
},
})],
})
===== docs/assets/example/audio.track.js =====
// npm i artplayer-plugin-audio-track
// import artplayerPluginAudioTrack from 'artplayer-plugin-audio-track';
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/sprite-fight.mp4',
plugins: [
artplayerPluginAudioTrack({
url: '/assets/sample/sprite-fight.aac',
offset: 0,
sync: 0.3,
}),
],
});
===== docs/assets/example/auto.thumbnail.js =====
// npm i artplayer-plugin-auto-thumbnail
// import artplayerPluginAutoThumbnail from 'artplayer-plugin-auto-thumbnail';
const art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
plugins: [
artplayerPluginAutoThumbnail({
//
}),
],
})
===== docs/assets/example/canvas.js =====
// npm i artplayer-proxy-canvas
// import artplayerProxyCanvas from 'artplayer-proxy-canvas';
const art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
poster: '/assets/sample/poster.jpg',
volume: 0.5,
autoplay: false,
autoSize: false,
screenshot: true,
setting: true,
loop: true,
flip: true,
pip: true,
playbackRate: true,
aspectRatio: true,
fullscreen: true,
fullscreenWeb: true,
miniProgressBar: true,
autoPlayback: true,
autoOrientation: true,
subtitle: {
url: '/assets/sample/subtitle.srt',
},
thumbnails: {
url: '/assets/sample/thumbnails.png',
number: 60,
column: 10,
scale: 0.85,
},
proxy: artplayerProxyCanvas(),
})
===== docs/assets/example/chapter.js =====
// npm i artplayer-plugin-chapter
// import artplayerPluginChapter from 'artplayer-plugin-chapter';
const art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
autoSize: true,
fullscreen: true,
fullscreenWeb: true,
miniProgressBar: true,
autoOrientation: true,
thumbnails: {
url: '/assets/sample/thumbnails.png',
number: 60,
column: 10,
},
plugins: [
artplayerPluginChapter({
chapters: [
{ start: 0, end: 18, title: 'One more chance' },
{ start: 18, end: 36, title: '谁でもいいはずなのに' },
{ start: 36, end: 54, title: '夏の想い出がまわる' },
{ start: 54, end: 72, title: 'こんなとこにあるはずもないのに' },
{ start: 72, end: Infinity, title: '终わり' },
],
}),
],
})
===== docs/assets/example/chromecast.js =====
// npm i artplayer-plugin-chromecast
// import artplayerPluginChromecast from 'artplayer-plugin-chromecast';
const art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
fullscreen: true,
fullscreenWeb: true,
plugins: [
artplayerPluginChromecast({
// sdk: '', // The URL of the Cast SDK
// mimeType: '', // The MIME type of the media
}),
],
})
===== docs/assets/example/danmuku.js =====
// npm i artplayer-plugin-danmuku
// import artplayerPluginDanmuku from 'artplayer-plugin-danmuku';
// 使用文档 https://artplayer.org/document/plugin/danmuku.html
const art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
autoSize: true,
fullscreen: true,
fullscreenWeb: true,
autoOrientation: true,
plugins: [
artplayerPluginDanmuku({
danmuku: '/assets/sample/danmuku.xml',
// 以下为非必填
speed: 5, // 弹幕持续时间,范围在[1 ~ 10]
margin: [10, '25%'], // 弹幕上下边距,支持像素数字和百分比
opacity: 1, // 弹幕透明度,范围在[0 ~ 1]
color: '#FFFFFF', // 默认弹幕颜色,可以被单独弹幕项覆盖
mode: 0, // 默认弹幕模式: 0: 滚动,1: 顶部,2: 底部
modes: [0, 1, 2], // 弹幕可见的模式
fontSize: 25, // 弹幕字体大小,支持像素数字和百分比
antiOverlap: true, // 弹幕是否防重叠
synchronousPlayback: false, // 是否同步播放速度
mount: undefined, // 弹幕发射器挂载点, 默认为播放器控制栏中部
heatmap: true, // 是否开启热力图
width: 512, // 当播放器宽度小于此值时,弹幕发射器置于播放器底部
points: [], // 热力图数据
filter: danmu => danmu.text.length <= 100, // 弹幕载入前的过滤器
beforeVisible: () => true, // 弹幕显示前的过滤器,返回 true 则可以发送
visible: true, // 弹幕层是否可见
emitter: true, // 是否开启弹幕发射器
maxLength: 200, // 弹幕输入框最大长度, 范围在[1 ~ 1000]
lockTime: 5, // 输入框锁定时间,范围在[1 ~ 60]
theme: 'dark', // 弹幕主题,支持 dark 和 light,只在自定义挂载时生效
OPACITY: {}, // 不透明度配置项
FONT_SIZE: {}, // 弹幕字号配置项
MARGIN: {}, // 显示区域配置项
SPEED: {}, // 弹幕速度配置项
COLOR: [], // 颜色列表配置项
// 手动发送弹幕前的过滤器,返回 true 则可以发送,可以做存库处理
beforeEmit(danmu) {
return new Promise((resolve) => {
console.log(danmu)
setTimeout(() => {
resolve(true)
}, 1000)
})
},
}),
],
})
===== docs/assets/example/danmuku.mask.js =====
// npm i artplayer-plugin-danmuku-mask
// import artplayerPluginDanmukuMask from 'artplayer-plugin-danmuku-mask';
// npm i @mediapipe/selfie_segmentation
// 把 node_modules/@mediapipe/selfie_segmentation 目录复制到你的项目下
const art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/steve-jobs.mp4',
autoSize: true,
fullscreen: true,
fullscreenWeb: true,
autoOrientation: true,
plugins: [
artplayerPluginDanmuku({
danmuku: '/assets/sample/danmuku.xml',
}),
artplayerPluginDanmukuMask({
solutionPath: '/assets/@mediapipe/selfie_segmentation',
}),
],
})
===== docs/assets/example/dash.control.js =====
// npm i dashjs
// npm i artplayer-plugin-dash-control
// import dashjs from 'dashjs';
// import artplayerPluginDashControl from 'artplayer-plugin-dash-control';
const useDash = dashjs.supportsMediaSource()
let dash
function destroyDash() {
const previous = dash
dash = undefined
if (previous)
previous.destroy()
}
const art = new Artplayer({
container: '.artplayer-app',
url: 'https://media.axprod.net/TestVectors/v7-Clear/Manifest_1080p.mpd',
setting: true,
plugins: useDash
? [
artplayerPluginDashControl({
quality: {
// Show quality choices in the controls
control: true,
// Show quality choices in settings
setting: true,
// Get the quality name from level
getName: level => `${level.height}P`,
// I18n
title: 'Quality',
auto: 'Auto',
},
audio: {
// Show audios in control
control: true,
// Show audios in setting
setting: true,
// Get the audio name from track
getName: track => track.lang?.toUpperCase() || String(track.id ?? 'Audio'),
// I18n
title: 'Audio',
auto: 'Auto',
},
}),
]
: [],
customType: {
mpd: function playMpd(video, url, art) {
destroyDash()
if (useDash) {
dash = dashjs.MediaPlayer().create()
art.dash = dash
dash.initialize(video, url, art.option.autoplay)
}
else {
art.notice.show = 'Unsupported playback format: mpd'
}
},
},
})
art.on('destroy', destroyDash)
===== docs/assets/example/dash.js =====
// npm i dashjs
// import dashjs from 'dashjs';
function playMpd(video, url, art) {
if (dashjs.supportsMediaSource()) {
if (art.dash)
art.dash.destroy()
const dash = dashjs.MediaPlayer().create()
dash.initialize(video, url, art.option.autoplay)
art.dash = dash
art.on('destroy', () => dash.destroy())
}
else {
art.notice.show = 'Unsupported playback format: mpd'
}
}
const art = new Artplayer({
container: '.artplayer-app',
url: 'https://dash.akamaized.net/akamai/bbb_30fps/bbb_30fps.mpd',
type: 'mpd',
customType: {
mpd: playMpd,
},
})
art.on('ready', () => {
console.info(art.dash)
})
===== docs/assets/example/document.pip.js =====
// npm i artplayer-plugin-document-pip
// import artplayerPluginDocumentPip from 'artplayer-plugin-document-pip';
const art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
plugins: [
artplayerPluginDocumentPip({
width: 480,
height: 270,
fallbackToVideoPiP: true,
placeholder: `Playing in Document Picture-in-Picture`,
}),
],
})
art.on('document-pip', (state) => {
console.log('Document Picture-in-Picture', state)
})
===== docs/assets/example/flv.js =====
// npm i flv.js
// import flvjs from 'flv.js';
function playFlv(video, url, art) {
if (flvjs.isSupported()) {
if (art.flv)
art.flv.destroy()
const flv = flvjs.createPlayer({ type: 'flv', url })
flv.attachMediaElement(video)
flv.load()
art.flv = flv
art.on('destroy', () => flv.destroy())
}
else {
art.notice.show = 'Unsupported playback format: flv'
}
}
const art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.flv',
type: 'flv',
customType: {
flv: playFlv,
},
})
art.on('ready', () => {
console.info(art.flv)
})
===== docs/assets/example/hls.control.js =====
// npm i hls.js
// npm i artplayer-plugin-hls-control
// import Hls from 'hls.js';
// import artplayerPluginHlsControl from 'artplayer-plugin-hls-control';
const useHls = Hls.isSupported()
let hls
function destroyHls() {
const previous = hls
hls = undefined
if (previous)
previous.destroy()
}
const art = new Artplayer({
container: '.artplayer-app',
url: 'https://playertest.longtailvideo.com/adaptive/elephants_dream_v4/index.m3u8',
setting: true,
plugins: useHls
? [
artplayerPluginHlsControl({
quality: {
// Show quality choices in the controls
control: true,
// Show quality choices in settings
setting: true,
// Get the quality name from level
getName: level => `${level.height}P`,
// I18n
title: 'Quality',
auto: 'Auto',
},
audio: {
// Show audios in control
control: true,
// Show audios in setting
setting: true,
// Get the audio name from track
getName: track => track.name || track.lang || 'Audio',
// I18n
title: 'Audio',
auto: 'Auto',
},
}),
]
: [],
customType: {
m3u8: function playM3u8(video, url, art) {
destroyHls()
if (useHls) {
hls = new Hls()
art.hls = hls
hls.loadSource(url)
hls.attachMedia(video)
}
else if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = url
}
else {
art.notice.show = 'Unsupported playback format: m3u8'
}
},
},
})
art.on('destroy', destroyHls)
===== docs/assets/example/hls.js =====
// npm i hls.js
// import Hls from 'hls.js';
function playM3u8(video, url, art) {
if (Hls.isSupported()) {
if (art.hls)
art.hls.destroy()
const hls = new Hls()
hls.loadSource(url)
hls.attachMedia(video)
art.hls = hls
art.on('destroy', () => hls.destroy())
}
else if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = url
}
else {
art.notice.show = 'Unsupported playback format: m3u8'
}
}
const art = new Artplayer({
container: '.artplayer-app',
url: 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8',
type: 'm3u8',
customType: {
m3u8: playM3u8,
},
})
art.on('ready', () => {
console.info(art.hls)
})
===== docs/assets/example/iframe.js =====
// npm i artplayer-tool-iframe
// import ArtplayerToolIframe from 'artplayer-tool-iframe';
const $iframe = document.createElement('iframe')
$iframe.allowFullscreen = true
$iframe.width = '100%'
$iframe.height = '100%'
const $container = document.querySelector('.artplayer-app')
$container.innerHTML = ''
$container.appendChild($iframe)
const iframe = new ArtplayerToolIframe({
iframe: $iframe,
url: '/iframe.html',
})
window.addEventListener('artplayer:example:cleanup', () => {
iframe.destroy()
$iframe.remove()
}, { once: true })
iframe.message(({ type, data }) => {
switch (type) {
case 'fullscreenWeb':
if (data) {
$iframe.classList.add('fullscreenWeb')
}
else {
$iframe.classList.remove('fullscreenWeb')
}
break
default:
break
}
})
iframe.commit(() => {
const art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
fullscreen: true,
fullscreenWeb: true,
})
art.on('fullscreenWeb', (state) => {
ArtplayerToolIframe.postMessage({
type: 'fullscreenWeb',
data: state,
})
})
}).catch((error) => {
if (!iframe.destroyed)
console.error(error)
})
===== docs/assets/example/index.js =====
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
poster: '/assets/sample/poster.jpg',
volume: 0.5,
isLive: false,
muted: false,
autoplay: false,
pip: true,
autoSize: true,
autoMini: true,
screenshot: true,
setting: true,
loop: true,
flip: true,
playbackRate: true,
aspectRatio: true,
fullscreen: true,
fullscreenWeb: true,
subtitleOffset: true,
miniProgressBar: true,
mutex: true,
backdrop: true,
playsInline: true,
autoPlayback: true,
airplay: true,
theme: '#23ade5',
lang: navigator.language.toLowerCase(),
moreVideoAttr: {
crossOrigin: 'anonymous',
},
settings: [
{
width: 200,
html: 'Subtitle',
tooltip: 'Bilingual',
icon: '<img width="22" height="22" src="/assets/img/subtitle.svg">',
selector: [
{
html: 'Display',
tooltip: 'Show',
switch: true,
onSwitch(item) {
item.tooltip = item.switch ? 'Hide' : 'Show'
art.subtitle.show = !item.switch
return !item.switch
},
},
{
default: true,
html: 'Bilingual',
url: '/assets/sample/subtitle.srt',
},
{
html: 'Chinese',
url: '/assets/sample/subtitle.cn.srt',
},
{
html: 'Japanese',
url: '/assets/sample/subtitle.jp.srt',
},
],
onSelect(item) {
art.subtitle.switch(item.url, {
name: item.html,
})
return item.html
},
},
{
html: 'Switcher',
icon: '<img width="22" height="22" src="/assets/img/state.svg">',
tooltip: 'OFF',
switch: false,
onSwitch(item) {
item.tooltip = item.switch ? 'OFF' : 'ON'
console.info('You clicked on the custom switch', item.switch)
return !item.switch
},
},
{
html: 'Slider',
icon: '<img width="22" height="22" src="/assets/img/state.svg">',
tooltip: '5x',
range: [5, 1, 10, 0.1],
onRange(item) {
return `${item.range[0]}x`
},
},
{
html: 'Button',
icon: '<img width="22" height="22" src="/assets/img/state.svg">',
tooltip: 'tooltip',
onClick() {
return 'Button clicked'
},
},
],
contextmenu: [
{
html: 'Custom menu',
click(contextmenu) {
console.info('You clicked on the custom menu')
contextmenu.show = false
},
},
],
layers: [
{
html: '<img width="100" src="/assets/sample/layer.png">',
click() {
window.open('https://aimu.app')
console.info('You clicked on the custom layer')
},
style: {
position: 'absolute',
top: '20px',
right: '20px',
opacity: '.9',
},
},
],
quality: [
{
default: true,
html: 'SD 480P',
url: '/assets/sample/video.mp4?q=480',
},
{
html: 'HD 720P',
url: '/assets/sample/video.mp4?q=720',
},
],
thumbnails: {
url: '/assets/sample/thumbnails.png',
number: 60,
column: 10,
scale: 0.85,
},
subtitle: {
url: '/assets/sample/subtitle.srt',
type: 'srt',
style: {
color: '#fe9200',
fontSize: '20px',
},
encoding: 'utf-8',
},
highlight: [
{
time: 15,
text: 'One more chance',
},
{
time: 30,
text: '谁でもいいはずなのに',
},
{
time: 45,
text: '夏の想い出がまわる',
},
{
time: 60,
text: 'こんなとこにあるはずもないのに',
},
{
time: 75,
text: '终わり',
},
],
controls: [
{
position: 'right',
html: 'Control',
index: 1,
tooltip: 'Control Tooltip',
style: {
marginRight: '20px',
},
click() {
console.info('You clicked on the custom control')
},
},
],
icons: {
loading: '<img src="/assets/img/ploading.gif">',
state: '<img width="150" height="150" src="/assets/img/state.svg">',
indicator: '<img width="16" height="16" src="/assets/img/indicator.svg">',
},
})
===== docs/assets/example/jassub.js =====
// https://github.com/ThaUnknown/jassub
// npm i artplayer-plugin-jassub
// import artplayerPluginJassub from 'artplayer-plugin-jassub';
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/jassub/FGOBD.mp4',
autoSize: true,
fullscreen: true,
fullscreenWeb: true,
plugins: [
artplayerPluginJassub({
subUrl: '/assets/jassub/FGOBD.ass',
workerUrl: '/assets/jassub/jassub-worker.js',
wasmUrl: '/assets/jassub/jassub-worker.wasm',
modernWasmUrl: '/assets/jassub/jassub-worker-modern.wasm',
availableFonts: {
'liberation sans': '/assets/jassub/default.woff2'
},
fonts: [
'/assets/jassub/fonts/Averia Sans Libre Light.ttf',
'/assets/jassub/fonts/Averia Serif Simple Light.ttf',
'/assets/jassub/fonts/Gramond.ttf'
],
timeOffset: -0.041
}),
],
});
===== docs/assets/example/mediabunny.js =====
// npm i artplayer-proxy-mediabunny
// import artplayerProxyMediabunny from 'artplayer-proxy-mediabunny';
const art = new Artplayer({
container: '.artplayer-app',
url: 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8',
autoSize: true,
setting: true,
loop: true,
flip: true,
playbackRate: true,
fullscreen: true,
fullscreenWeb: true,
miniProgressBar: true,
autoPlayback: true,
autoOrientation: true,
proxy: artplayerProxyMediabunny({
m3u8: {
quality: {
control: true,
setting: true,
getName: level => level.height ? `${level.height}P` : level.name,
title: 'Quality',
auto: 'Auto',
},
audio: {
control: true,
setting: true,
getName: track => track.name || track.language,
title: 'Audio',
auto: 'Auto',
},
},
}),
})
===== docs/assets/example/mobile.js =====
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
poster: '/assets/sample/poster.jpg',
autoSize: true,
loop: true,
mutex: true,
setting: true,
flip: true,
lock: true,
fastForward: true,
playbackRate: true,
aspectRatio: true,
theme: '#ff0057',
fullscreen: true,
fullscreenWeb: true,
miniProgressBar: true,
autoOrientation: true,
airplay: true,
moreVideoAttr: {
'x5-video-player-type': 'h5',
'x5-video-player-fullscreen': false,
'x5-video-orientation': 'portraint',
'preload': 'metadata',
},
thumbnails: {
url: '/assets/sample/thumbnails.png',
number: 60,
column: 10,
scale: 0.6,
},
subtitle: {
name: '中日双语',
url: '/assets/sample/subtitle.srt',
style: {
color: '#48aff0',
fontSize: '16px',
},
},
layers: [
{
html: `<img width="50" src="/assets/sample/layer.png">`,
click() {
art.notice.show = '你点击了自定义层'
},
style: {
position: 'absolute',
top: '10px',
right: '10px',
opacity: '.9',
},
},
],
icons: {
loading: '<img src="/assets/img/ploading.gif">',
state: '<img width="150" height="150" src="/assets/img/state.svg">',
indicator: '<img width="16" height="16" src="/assets/img/indicator.svg">',
},
settings: [
{
width: 200,
html: '切换字幕',
tooltip: '双语',
icon: '<img width="22" height="22" src="/assets/img/subtitle.svg">',
selector: [
{
html: '开关',
switch: true,
tooltip: '显示',
onSwitch(item) {
item.tooltip = item.switch ? '隐藏' : '显示'
art.subtitle.show = !item.switch
return !item.switch
},
},
{
default: true,
html: '双语',
url: '/assets/sample/subtitle.srt',
},
{
html: '中文',
url: '/assets/sample/subtitle.cn.srt',
},
{
html: '日文',
url: '/assets/sample/subtitle.jp.srt',
},
],
onSelect(item) {
art.subtitle.switch(item.url, {
name: item.html,
})
return item.html
},
},
],
})
===== docs/assets/example/mpegts.js =====
// npm i mpegts
// import mpegts from 'mpegts';
function playFlv(video, url, art) {
if (mpegts.isSupported()) {
if (art.flv)
art.flv.destroy()
const flv = mpegts.createPlayer({
type: 'flv',
url,
})
flv.attachMediaElement(video)
flv.load()
flv.play()
art.flv = flv
art.on('destroy', () => flv.destroy())
}
else {
art.notice.show = 'Unsupported playback format: flv'
}
}
const art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.flv',
type: 'flv',
customType: {
flv: playFlv,
},
})
art.on('ready', () => {
console.info(art.flv)
})
===== docs/assets/example/multiple.subtitles.js =====
// npm i artplayer-plugin-multiple-subtitles
// import artplayerPluginMultipleSubtitles from 'artplayer-plugin-multiple-subtitles';
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
setting: true,
plugins: [
artplayerPluginMultipleSubtitles({
subtitles: [
{
name: 'chinese',
url: '/assets/sample/subtitle.cn.srt',
},
{
name: 'japanese',
url: '/assets/sample/subtitle.jp.srt',
},
],
}),
],
settings: [
{
width: 200,
html: 'Subtitle',
tooltip: 'Double',
icon: '<img width="22" height="22" src="/assets/img/subtitle.svg">',
selector: [
{
html: 'Display',
tooltip: 'Show',
switch: true,
onSwitch(item) {
item.tooltip = item.switch ? 'Hide' : 'Show'
// 显示/隐藏字幕
// Show/hide subtitles
art.subtitle.show = !item.switch
return !item.switch
},
},
{
html: 'Reverse',
tooltip: 'Off',
switch: false,
onSwitch(item) {
item.tooltip = item.switch ? 'Off' : 'On'
// 修改字幕顺序
// Change the order of subtitles
if (item.switch) {
art.plugins.multipleSubtitles.tracks(['chinese', 'japanese'])
}
else {
art.plugins.multipleSubtitles.tracks(['japanese', 'chinese'])
}
return !item.switch
},
},
{
default: true,
html: 'Double',
name: 'double',
},
{
html: 'Chinese',
name: 'chinese',
},
{
html: 'Japanese',
name: 'japanese',
},
],
onSelect(item) {
if (item.name === 'double') {
// 重置字幕
// Reset subtitles
art.plugins.multipleSubtitles.reset()
}
else {
// 显示单个字幕
// Show single subtitle
art.plugins.multipleSubtitles.tracks([item.name])
}
return item.html
},
},
],
})
// 自定义你自己的样式,请勿复制以下代码
// Customize your own style, please do not copy the following code
const style = `
.art-subtitle-chinese {
color: red;
font-size: 18px;
}
.art-subtitle-japanese {
color: yellow;
font-size: 12px;
}
`
const $style = document.getElementById('artplayer-subtitle-style')
if ($style) {
$style.textContent = style
}
else {
const $style = document.createElement('style')
$style.id = 'artplayer-subtitle-style'
$style.textContent = style
document.head.appendChild($style)
}
===== docs/assets/example/setting.test.js =====
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
setting: true,
flip: true,
playbackRate: true,
aspectRatio: true,
subtitleOffset: true,
settings: [
{
width: 200,
html: 'Subtitle',
name: 'subtitle',
tooltip: 'Bilingual',
icon: '<img width="22" height="22" src="/assets/img/subtitle.svg">',
selector: [
{
html: 'Display',
tooltip: 'Show',
switch: true,
onSwitch(item) {
item.tooltip = item.switch ? 'Hide' : 'Show'
art.subtitle.show = !item.switch
return !item.switch
},
},
{
default: true,
html: 'Bilingual',
url: '/assets/sample/subtitle.srt',
},
{
html: 'Chinese',
url: '/assets/sample/subtitle.cn.srt',
},
{
html: 'Japanese',
url: '/assets/sample/subtitle.jp.srt',
},
],
onSelect(item) {
art.subtitle.switch(item.url, {
name: item.html,
})
return item.html
},
mounted(...args) {
console.info(args)
},
},
{
html: 'Switcher',
icon: '<img width="22" height="22" src="/assets/img/state.svg">',
tooltip: 'OFF',
switch: false,
onSwitch(item) {
item.tooltip = item.switch ? 'OFF' : 'ON'
console.info('You clicked on the custom switch', item.switch)
return !item.switch
},
mounted(...args) {
console.info(args)
},
},
{
html: 'Slider',
icon: '<img width="22" height="22" src="/assets/img/state.svg">',
tooltip: '5x',
range: [5, 1, 10, 0.1],
onRange(item) {
return `${item.range[0]}x`
},
mounted(...args) {
console.info(args)
},
},
],
}, async () => {
const { sleep } = Artplayer.utils
art.setting.show = true
console.log(art.setting.builtin)
console.log(art.setting.find('aspect-ratio'))
console.log(art.setting.find('aspect-ratio2'))
await sleep(1000)
art.setting.resize()
await sleep(1000)
art.setting.inactivate(art.setting.find('subtitle'))
art.setting.remove('aspect-ratio')
try {
art.setting.remove('aspect-ratio2')
}
catch (error) {
console.log(error.message)
}
await sleep(1000)
art.setting.update({
name: 'subtitle-offset',
html: 'new offset',
range: [5, -11, 11, 1],
})
await sleep(1000)
art.setting.find('subtitle-offset').range = [0, -0, 10, 1]
await sleep(1000)
art.setting.update({
name: 'subtitle-offset2',
html: 'new offset 2',
range: [5, -11, 11, 1],
onChange(item) {
return `${item.range[0]}s`
},
})
await sleep(1000)
art.setting.update({
name: 'flip',
html: 'new flip',
tooltip: 'OFF',
switch: false,
})
await sleep(1000)
art.setting.find('flip').switch = true
await sleep(1000)
art.setting.update({
name: 'flip2',
html: 'new flip2',
tooltip: 'OFF',
switch: true,
})
await sleep(1000)
try {
art.setting.add({
name: 'flip2',
html: 'new flip2',
tooltip: 'OFF',
switch: true,
})
}
catch (error) {
console.log(error.message)
}
})
===== docs/assets/example/thumbnail.js =====
// npm i artplayer-plugin-thumbnail
// import artplayerPluginThumbnail from 'artplayer-plugin-thumbnail';
const art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
plugins: [
artplayerPluginThumbnail({
width: 160,
number: 100,
scale: 1,
}),
],
})
===== docs/assets/example/tool.thumbnail.js =====
if (window.lastThumbnail) {
window.lastThumbnail.destroy();
}
var $popups = document.querySelector('.popups');
var $popinner = document.querySelector('.popinner');
var $artplayer = document.querySelector('.artplayer-app');
$artplayer.innerHTML = 'Drop video file here or click to upload.';
var thumbnail = new ArtplayerToolThumbnail({
fileInput: $artplayer,
number: 60, // 数量
width: 160, // 宽度
column: 10, // 列数
begin: 0, // 开始
end: NaN, // 结束
});
window.lastThumbnail = thumbnail;
thumbnail.on('file', function (file) {
console.log('Read video successfully: ' + file.name);
});
thumbnail.on('video', function (video) {
console.log('Video size: ' + video.videoWidth + ' x ' + video.videoHeight);
console.log('Video duration: ' + video.duration + 's');
thumbnail.start();
});
thumbnail.on('canvas', function (canvas) {
console.log('Build canvas successfully');
console.log('Canvas size: ' + canvas.width + ' x ' + canvas.height);
console.log('Preview density: ' + thumbnail.density + ' p/s');
});
thumbnail.on('update', function (url, percentage) {
console.log('Processing: ' + Math.floor(percentage.toFixed(2) * 100) + '%');
$popups.style.display = 'flex';
$popinner.style.backgroundImage = 'url(' + url + ')';
});
thumbnail.on('download', function (name) {
console.log('Start download preview: ' + name);
});
thumbnail.on('done', function () {
$popups.style.display = 'none';
thumbnail.download();
console.log('Build preview image complete');
[...Artplayer.instances].forEach(function (art) {
art.destroy(true);
});
new Artplayer({
container: $artplayer,
url: thumbnail.videoUrl,
autoSize: true,
poster: thumbnail.thumbnailUrl,
thumbnails: {
url: thumbnail.thumbnailUrl,
number: thumbnail.option.number,
column: thumbnail.option.column,
},
});
console.log('Build player complete');
});
===== docs/assets/example/vast.js =====
// Depends on:
// https://glomex.github.io/vast-ima-player/
// https://developers.google.com/interactive-media-ads/docs/sdks/html5/client-side
// Google's IMA SDK are blocked by your Ad blocker.
// Please Turn Off Your Ad Blocker.
// npm i artplayer-plugin-vast
// import artplayerPluginVast from 'artplayer-plugin-vast';
var art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
fullscreen: true,
fullscreenWeb: true,
plugins: [
artplayerPluginVast(({ playUrl, imaPlayer, ima }) => {
// Play the ad when the video is played
art.once('play', () => {
playUrl('https://artplayer.org/assets/vast/linear-ad.xml')
})
}),
],
})
===== docs/assets/example/vtt.thumbnail.js =====
// npm i artplayer-plugin-vtt-thumbnail
// import artplayerPluginVttThumbnail from 'artplayer-plugin-vtt-thumbnail';
const art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/bbb-video.mp4',
plugins: [
artplayerPluginVttThumbnail({
vtt: '/assets/sample/bbb-thumbnails.vtt',
}),
],
})
===== docs/assets/example/webtorrent.js =====
// npm i webtorrent
// import WebTorrent from 'webtorrent';
async function playTorrent(video, url, art) {
if (WebTorrent.WEBRTC_SUPPORT) {
if (art.torrent)
art.torrent.destroy()
art.torrent = new WebTorrent()
await navigator.serviceWorker.register('/webtorrent.sw.min.js')
art.torrent.loadWorker(navigator.serviceWorker.controller)
art.torrent.add(url, (torrent) => {
const file = torrent.files.find((file) => {
return file.name.endsWith('.mp4')
})
file.streamTo(video)
})
art.on('destroy', () => art.torrent.destroy())
}
else {
art.notice.show = 'Unsupported playback format: torrent'
}
}
const art = new Artplayer({
container: '.artplayer-app',
url: 'magnet:?xt=urn:btih:08ada5a7a6183aae1e09d831df6748d566095a10&dn=Sintel&tr=udp%3A%2F%2Fexplodie.org%3A6969&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.empire-js.us%3A1337&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.fastcast.nz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com&ws=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2F&xs=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2Fsintel.torrent',
type: 'torrent',
customType: {
torrent: playTorrent,
},
})
art.on('ready', () => {
console.info(art.torrent)
})
===== Third-party Type Notices =====
===== docs/assets/ts/artplayer-plugin-vast.LICENSE.txt =====
@glomex/vast-ima-player
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2020 glomex GmbH
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@alugha/ima
# The MIT License (MIT)
**Copyright 2020 Alugha GmbH**
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.