docs(core): [SITE-04] clarify plugin registration and lifecycle

This commit is contained in:
Harvey Zhao committed 2026-09-16 07:09:58 +08:00
1 parent 5bf1a36183
commit de45f5b978
94 files changed
+3270 -2260

No files matched your search

@@ -1,6 +1,49 @@
# 编写插件
但你已经知道播放器的`属性`, `方法`和`事件`后,再编写插件是非常简单的事
了解播放器的`属性`、`方法`和`事件`后,就可以编写插件。
## 注册与生命周期 {#plugin-contract}
`plugins` 配置接收工厂函数。工厂被调用时,唯一参数和普通函数的 `this` 都是播放器;箭头函数使用自己的词法 `this`。带配置的插件通常先调用外层函数,得到真正的工厂,如 `plugins: [adsPlugin(options)]`。工厂直接返回的对象会成为注册结果,不会被复制。
同步工厂在 `add` 返回前完成注册,`art.plugins.add(factory)` 返回注册管理器本身。只有与播放器同一 JavaScript realm 的 `Promise` 才会被等待;此时返回的 Promise 最终兑现为管理器,而不是插件结果。普通 thenable 和其他窗口的 Promise 保持原样作为同步结果。需要等待异步插件时,应在构造之后 `await art.plugins.add(factory)`,然后读取对应名称。
构造配置中的工厂按数组顺序启动,但不会逐个等待异步工厂;播放器 `ready` 也不等待它们。构造时前面的工厂执行期间,`art.plugins` 尚未赋值,不应在工厂里依赖它访问前一个插件。同步工厂抛错会使构造或直接 `add` 失败;直接 `add` 的异步拒绝由调用者处理,构造配置中的异步拒绝会记录警告。
名称取第一个真值:结果的 `name`、工厂函数名、`plugin` 加当前注册计数。异步匿名工厂的回退名称使用完成时的计数,因此推荐显式返回稳定的字符串名称。`id` 在调用工厂前递增,失败也占用计数;内置插件也参与计数。`art` 指向宿主;`next(factory, result)` 是直接提交结果的底层方法,不调用工厂、不递增计数、不等待 Promise,通常无需手动调用。
结果以不可写、不可配置、不可枚举的自有属性保存,因此 `Object.keys(art.plugins)` 不会列出插件,重复名称会抛错。不要使用 `art`、`id`、`add`、`next` 等管理器成员名;名称与原型方法冲突可能遮蔽方法。管理器没有通用的移除、替换或自动调用插件 `destroy` 的机制。
插件应自行监听播放器 `destroy`,清理请求、定时器、Worker 和外部资源。异步工厂也要在首次等待之前注册清理逻辑,并在等待结束后检查是否已销毁。销毁后的迟到结果不会注册,也不会被自动清理;已经挂起的 `add` 仍返回其原 Promise,若工厂成功则兑现为管理器。销毁期间或之后的新 `add` 会在执行工厂前抛错。
## TypeScript {#plugin-types}
根入口保留历史 `Plugins.add` 的 Promise 返回声明;它不表示同步运行时变成异步。`artplayer/runtime` 的 `PluginRegistration` 按工厂返回类型区分同步管理器与 Promise;`unknown` 返回值保留二者联合。跨 realm 的 Promise 无法仅靠 TS 类型判定,仍遵守上面的运行时规则。
模块扩展只声明结果类型,不负责安装插件。准确入口的 `Plugins` 也承接根入口已有插件的命名扩展。构造选项的 `PluginFactory` 使用初始化阶段的 `PluginHost`,避免假定工厂执行时实例已完全构造;实例化之后的 `add` 可以使用完整播放器。
```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;
```
## 示例
可以在实例化的时候加载插件的函数
@@ -122,4 +165,4 @@ var art = new Artplayer({
})
],
});
```
```
@@ -1,6 +1,49 @@
# Writing Plugins
Once you are familiar with the player's `properties`, `methods`, and `events`, writing a plugin becomes a very straightforward task.
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.
@@ -122,4 +165,4 @@ var art = new Artplayer({
})
],
});
```
```