docs(site): [SITE-04] document thumbnail extraction and subtitle selection

This commit is contained in:
Harvey Zhao committed 2026-09-16 02:57:15 +08:00
1 parent 94adb77fd6
commit 3317230f2e
94 files changed
+4654 -1908

No files matched your search

@@ -234,7 +234,8 @@ fixtures and historical CommonJS alias. Task04's migration acceptance is recorde
in `refactor/changes/2026-09-15-PKG-AUTO-THUMB-04-migration.md`; unresolved runtime
behavior remains with task03. Task05 requires both and verifies old/final cores and actual
devices; task06 covers installed package entries and the real demo/editor. The
package version remains unchanged. Use
initial migration left the version unchanged; release preparation now sets the
next major in package.json. Use
`refactor/baselines/auto-thumbnail-contract.md` and `auto-thumbnail-failures.md` as
the historical evidence map; do not regenerate those baselines from this source.
+12 -2
View File
@@ -19,6 +19,8 @@ not a VitePress page. Keep both URL surfaces compatible.
| `docs/plugin/ambilight.md`, `docs/en/plugin/ambilight.md` | Sampling options, start/stop ownership, pixel access and compatible type entries | `/document/plugin/ambilight.html`, `/document/en/plugin/ambilight.html` |
| `docs/plugin/document-pip.md`, `docs/en/plugin/document-pip.md` | Window ownership, video fallback, events and compatible async type views | `/document/plugin/document-pip.html`, `/document/en/plugin/document-pip.html` |
| `docs/plugin/asr.md`, `docs/en/plugin/asr.md` | PCM/WAV callbacks, subtitle rendering, audio routing, lifecycle and runtime types | `/document/plugin/asr.html`, `/document/en/plugin/asr.html` |
| `docs/plugin/auto-thumbnail.md`, `docs/en/plugin/auto-thumbnail.md` | Progressive JPEG extraction, limits, decoder ownership and asynchronous runtime types | `/document/plugin/auto-thumbnail.html`, `/document/en/plugin/auto-thumbnail.html` |
| `docs/plugin/multiple-subtitles.md`, `docs/en/plugin/multiple-subtitles.md` | Track loading, selection, shared subtitle configuration and compatible type views | `/document/plugin/multiple-subtitles.html`, `/document/en/plugin/multiple-subtitles.html` |
| `docs/.vitepress/config.js` | Navigation, base URL, output path, page head | Repository `docs/document/` |
| `docs/vite.config.ts` | Search plugin configuration | VitePress/Vite integration; real search acceptance remains pending |
| `docs/public/main.js` | Run Code links and first-visit language redirect | Copied into the built site |
@@ -32,11 +34,19 @@ and run `node refactor/scripts/demos.mjs --check`. Preserve the frozen BASE-04
inventory. Also refresh and check the current site inventory; the two inventories
serve different purposes. Neither path check replaces browser navigation tests.
The generated site lives in `docs/document/`. The current inventory contains 44 Markdown pages: 22 Chinese
and 22 English. Danmuku, HLS, DASH, Audio Track, VTT Thumbnail, Chapter, Ambilight, Document PiP and ASR have dedicated guides and navigation in both languages. Other plugins currently
The generated site lives in `docs/document/`. The current inventory contains 48 Markdown pages: 24 Chinese
and 24 English. Danmuku, HLS, DASH, Audio Track, VTT Thumbnail, Chapter, Ambilight, Document PiP, ASR, Auto Thumbnail and Multiple Subtitles have dedicated guides and navigation in both languages. Other plugins currently
use their package README, architecture notes and linked editor examples;
navigation links alone are not complete API documentation.
Auto Thumbnail and Multiple Subtitles preserve their original runnable examples.
Both registrars are asynchronous, but their Promises cover different work: Auto
Thumbnail installs subscriptions before extraction, while Multiple Subtitles
downloads and merges tracks without waiting for host subtitle loading. Their
guides explain these boundaries, retained historical declarations and opt-in
runtime types. Navigation verification does not prove decoded thumbnail pixels
or native subtitle display; keep those package-specific checks separate.
Document PiP keeps its required root factory and void historical result types;
the guide explains the accurate opt-in views without inventing a runtime subpath.
ASR documents the actual `/runtime` entry, nonterminal stop, direct/capture audio
@@ -73,7 +73,8 @@ const config = {
{ text: 'HLS 控制', link: '/plugin/hls-control' },
{ text: 'DASH 控制', link: '/plugin/dash-control' },
{ text: 'VTT 缩略图', link: '/plugin/vtt-thumbnail' },
{ text: '多重字幕', link: 'https://artplayer.org/?libs=./uncompiled/artplayer-plugin-multiple-subtitles/index.js&example=multiple.subtitles' },
{ text: '自动缩略图', link: '/plugin/auto-thumbnail' },
{ text: '多重字幕', link: '/plugin/multiple-subtitles' },
{ text: 'ASS 字幕 - JASSUB', link: 'https://artplayer.org/?libs=./uncompiled/artplayer-plugin-jassub/index.js&example=jassub' },
{ text: 'Chromecast', link: 'https://artplayer.org/?libs=./uncompiled/artplayer-plugin-chromecast/index.js&example=chromecast' },
{ text: 'VAST 广告', link: 'https://artplayer.org/?libs=./uncompiled/artplayer-plugin-vast/index.js&example=vast' },
@@ -146,7 +147,8 @@ const config = {
{ text: 'HLS Control', link: '/en/plugin/hls-control' },
{ text: 'DASH Control', link: '/en/plugin/dash-control' },
{ text: 'VTT Thumbnail', link: '/en/plugin/vtt-thumbnail' },
{ text: 'Multiple Subtitles', link: 'https://artplayer.org/?libs=./uncompiled/artplayer-plugin-multiple-subtitles/index.js&example=multiple.subtitles' },
{ text: 'Automatic Thumbnails', link: '/en/plugin/auto-thumbnail' },
{ text: 'Multiple Subtitles', link: '/en/plugin/multiple-subtitles' },
{ text: 'ASS Subtitle - JASSUB', link: 'https://artplayer.org/?libs=./uncompiled/artplayer-plugin-jassub/index.js&example=jassub' },
{ text: 'Chromecast', link: 'https://artplayer.org/?libs=./uncompiled/artplayer-plugin-chromecast/index.js&example=chromecast' },
{ text: 'VAST', link: 'https://artplayer.org/?libs=./uncompiled/artplayer-plugin-vast/index.js&example=vast' },
@@ -0,0 +1,85 @@
# 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`.
@@ -0,0 +1,200 @@
# 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.
@@ -0,0 +1,85 @@
# 自动缩略图
[English](../en/plugin/auto-thumbnail.md)
使用独立视频元素读取画面,在浏览器内逐步生成 JPEG 雪碧图,并更新播放器进度条的缩略图配置。本页描述未发布的重构分支;在线示例及未固定版本的 npm/CDN 包不等于当前候选。
## 安装和示例
```sh
yarn add artplayer artplayer-plugin-auto-thumbnail
```
```js
import Artplayer from 'artplayer';
import artplayerPluginAutoThumbnail from 'artplayer-plugin-auto-thumbnail';
```
使用 script 时先加载 ArtPlayer,再加载 `dist/artplayer-plugin-auto-thumbnail.js`,全局名为 `artplayerPluginAutoThumbnail`。以下代码保留[原在线示例](https://artplayer.org/?libs=./uncompiled/artplayer-plugin-auto-thumbnail/index.js&example=auto.thumbnail)。工厂参数对象必填,使用 `{}` 选择默认值。
<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({
//
}),
],
})
```
## 配置与生成方式
| 字段 | 类型 | 默认值 | 含义 |
| --- | --- | --- | --- |
| `url` | `string` | 当前 `art.option.url` | 独立解码器读取的媒体地址;显式非空地址优先 |
| `width` | `number` | `160` | 每格缩略图宽度,单位像素 |
| `number` | `number` | `100` | 目标采样数量,通常使用正整数 |
| `scale` | `number` | `1` | 传给播放器的预览显示缩放,不降低生成画布尺寸 |
| `height` | `number` | 按视频宽高比计算 | 历史参数,实际忽略;精确 runtime 类型接受它 |
这些默认值沿用 `value || default` 规则,例如 `width: 0` 回退为 160。width 和 number 回退后需要为可转换成有限正数的值;旧 JavaScript 数值字符串和小数计数行为保留,但类型示例使用数字与正整数。生成高度为 `Math.floor(width * videoHeight / videoWidth)`,每行固定十列。媒体必须有有限的正时长和尺寸,不适用于直播无限时长。
采样时刻为 `duration * index / number`,从 index 0 开始,不包含媒体终点。时长与画布尺寸在独立视频 metadata 时固定;每次画出一格后编码整张 JPEG,再发布新的 `art.thumbnails` 配置。因此生成期间后面的格子可能尚未填充,并不是所有缩略图同时就绪。
较大的 width 和 number 会增加画布、解码和反复编码成本;尺寸校验不是浏览器内存预算保证。选择适合视频长度和目标设备的配置,或使用预先制作的 [VTT 缩略图](./vtt-thumbnail.md)。
## 注册、切源和清理
工厂返回异步注册函数,结果仅包含 `name: 'artplayerPluginAutoThumbnail'`。注册 Promise 在安装播放器事件后完成,不等待缩略图生成,也不表示媒体已成功读取。没有公开的进度、生成完成、update、stop 或 destroy 方法。
每次 `video:loadedmetadata` 启动新一轮提取,并重新读取原配置对象;之后修改原对象会影响下一轮。插件不补发安装之前已经过去的 metadata 事件。`restart` 取消旧提取,后续 metadata 才启动新任务。旧的异步帧和编码回调不能覆盖新任务。
独立解码器的 metadata、每帧就绪和每次 JPEG 编码各有 30 秒等待上限,不是整个任务的总时限。媒体、画布或编码失败会清理当前任务,通过 `console.warn` 报告,并保留最后一张可用预览;已完成的注册 Promise 不会因此重新拒绝。
完成或取消后释放独立视频与画布;最后的图片 URL 保留至被新的可用图替换或播放器销毁。只撤销插件生成的 URL,不撤销应用自己的外部缩略图地址。销毁同时移除监听器;直接调用已保留的注册函数且播放器已销毁时,只返回名称,不分配解码器,这不改变核心 `plugins.add()` 的销毁检查。
## 媒体访问和浏览器边界
独立视频固定使用 `crossOrigin = 'anonymous'`,静音且不主动播放。媒体必须允许浏览器原生读取和 Canvas 像素访问,跨域服务需要正确的 CORS 响应。播放器的自定义加载器、SDK、请求头或代理不会自动安装到这个独立解码器;必要时用 `url` 提供它能直接读取的媒体。
独立视频会以隐藏但具有渲染尺寸的节点挂到文档中;这不表示新增可见播放器。真实解码和首帧正确性仍依赖浏览器。当前 Windows WebKit 首帧问题尚未完成验收,不能把类型、导航或其它浏览器通过当作该问题已解决,也不代表真实 Safari/手机已验证。
## TypeScript 兼容入口
根入口和 `/legacy` 保留 npm 1.1.0 的同步结果声明,工厂对象必填,根 Option 没有 height。需要实际 Promise、旧 height 参数或 `.default` 自引用类型时,使用同一实现的 `/runtime`:
```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 导出 `Option`、`Result`、`Factory`、`RuntimeFactory`;Factory 是普通异步工厂,RuntimeFactory 还描述可写的 `.default` 自引用。实际 CommonJS 支持直接调用及 `.default(...)`。更早 1.0.x 的 `export =` 和 height 声明与 1.1.0 不同,需要旧导入形式时可迁到 runtime 的 `import = require`。根 NodeNext 模块形状保留,准确的可调用默认导入使用 runtime;经典 Node10 默认导入需要 `esModuleInterop`。
@@ -0,0 +1,200 @@
# 多重字幕
[English](../en/plugin/multiple-subtitles.md)
下载多个字幕文件,保留各自 cue 的时刻,将选中轨道合并到播放器字幕中。可以同时显示不同语言,并按名称选择或调整轨道顺序。本页描述当前未发布的重构分支;线上示例和未固定版本的包不等于当前候选。
## 安装和示例
```sh
yarn add artplayer artplayer-plugin-multiple-subtitles
```
```js
import Artplayer from 'artplayer';
import artplayerPluginMultipleSubtitles from 'artplayer-plugin-multiple-subtitles';
```
使用 script 时先加载 ArtPlayer,再加载 `dist/artplayer-plugin-multiple-subtitles.js`,全局名为 `artplayerPluginMultipleSubtitles`。下面保留[在线示例](https://artplayer.org/?libs=./uncompiled/artplayer-plugin-multiple-subtitles/index.js&example=multiple.subtitles)的原始选择菜单和样式代码;菜单由应用配置,插件不自动创建菜单。
<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)
}
```
## 字幕配置
工厂需要配置对象,`subtitles` 是轨道数组。运行时 `{}` 等同空数组;旧根类型仍要求显式提供 `subtitles`。每个 `TrackOption` 的字段如下:
| 字段 | 类型 | 默认或行为 |
| --- | --- | --- |
| `url` | `string` | 字幕地址,使用浏览器 fetch;历史类型虽可选,实际用例应提供有效地址 |
| `name` | `string` | 轨道选择和 CSS 名称;建议提供唯一、简单的名称,例如 `chinese` |
| `type` | `'vtt' \| 'srt' \| 'ass'` | 未指定时读取 URL 扩展名;显式值优先 |
| `encoding` | `string` | 默认 `'utf-8'`,传给 TextDecoder |
| `onParser` | `(...args: object[]) => object` | 历史声明保留,实际不会调用 |
所有文件并行下载,再解码并合并。跨域文件需要服务端允许 fetch。SRT、ASS 使用核心转换工具生成 VTT;ASS 不保留完整 ASS 排版和动画,需要完整 ASS 渲染时使用 JASSUB。未知类型转为空内容,解析器尽力处理文本,格式诊断不一定拒绝全部可用 cue。
HTTP 非成功响应、解码或转换抛出的错误会使注册拒绝,并释放同批请求。元数据按下载完成后的原配置读取,不是深拷贝;避免在安装过程中修改轨道对象或数组。
## 注册与选择
实际注册异步完成,返回 `{ name: 'multipleSubtitles', tracks, reset }`,结果存放在 `art.plugins.multipleSubtitles`,不是全局工厂名。需要直接使用返回结果时先 await 注册;结果就绪不代表播放器已经完成字幕加载。
| 调用 | 结果 |
| --- | --- |
| `tracks(['chinese', 'japanese'])` | 按调用方名称顺序选择轨道 |
| `tracks(['japanese'])` | 仅显示选中的轨道 |
| `tracks()` 或 `tracks([])` | 清空选择 |
| `reset()` | 恢复所有最初下载的轨道及其原顺序 |
tracks 和 reset 都同步返回 `undefined`。未知名称保留历史同步 TypeError;重复名称按每次查找的第一条轨道处理,不会自动去重。请使用已经配置的唯一名称。轨道按选择顺序合并,但并不改写不同语言 cue 的时间,不能用轨道顺序对齐本来错位的字幕。
每次选择都会创建新的 VTT Blob URL 并初始化播放器字幕,释放上一份插件拥有的 URL。异步字幕安装失败会警告并清理失败资源,void 方法不能用于 await 字幕就绪。切换视频不会重新下载轨道,reset 也不是重新请求服务器;插件没有公开的 update、reload 或独立 destroy。
## 样式与资源归属
字幕内容使用 `.art-subtitle-<name>` 包装,例如示例的 `.art-subtitle-chinese` 和 `.art-subtitle-japanese`。名称会进入 HTML 类名,请使用应用定义的简单标识。选择时插件设置 `art.option.subtitle.escape = false` 并接管该次字幕 URL、type 和 onVttLoad;与其它同时管理字幕的插件组合时,需要明确由谁最后写入。
普通 cue 文本、实体和合法标记分别处理;内联时间戳保留 cue 数据,但显示仍为整段字幕,不新增卡拉 OK 逐字高亮。较旧核心的多 active cue 显示有兼容处理;依赖自定义字幕 DOM 的监听器应在插件注册后安装,以免被后续字幕视图更新覆盖。
播放器销毁会取消请求、移除插件监听器、释放生成的 URL。下载中的注册会结算为不再工作的结果,保留的 tracks/reset 方法变成空操作。销毁不会恢复共享的 escape 配置,以免覆盖其他消费者之后的设置。实际语言字体、旧核心组合和 Safari/手机显示仍需相应环境验收。
## TypeScript 兼容入口
根入口和 `/legacy` 保留最新已发布 1.2.0 的工厂类型:必填 `{ subtitles: TrackOption[] }`,同步 `LegacyResult` 仅含 name,以兼容历史参数提取与替换函数。实际 Promise 和选择方法使用同一实现的 `/runtime`:
```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;
}
```
根命名类型为 `TrackOption`、`Option`、`RuntimeOption`、`LegacyResult`、`Result`、`Factory`、`RuntimeFactory`;runtime 导出 TrackOption、RuntimeOption、Result、RuntimeFactory。RuntimeFactory 还描述可写 `.default` 自引用;旧 Factory 不要求该属性。
1.0/1.1 的 export-assignment 声明与 1.2 默认模块形状冲突,根按已批准规则保留 1.2。NodeNext ESM 根类型的工厂在 `root.default`,准确的默认调用使用 runtime。经典 CommonJS 无 interop 时可使用 runtime 的 `import = require`;经典默认导入需 `esModuleInterop`。历史 JavaScript `.default(...)` 调用继续支持,不将旧声明错误宣传为原来不存在的运行时调用方式。
@@ -50,5 +50,5 @@
}
// <stdin>
installNavigation(window, ["advanced/built-in.html", "advanced/built-in", "advanced/class.html", "advanced/class", "advanced/event.html", "advanced/event", "advanced/global.html", "advanced/global", "advanced/plugin.html", "advanced/plugin", "advanced/property.html", "advanced/property", "component/contextmenu.html", "component/contextmenu", "component/controls.html", "component/controls", "component/layers.html", "component/layers", "component/setting.html", "component/setting", "", "index.html", "plugin/ambilight.html", "plugin/ambilight", "plugin/asr.html", "plugin/asr", "plugin/audio-track.html", "plugin/audio-track", "plugin/chapter.html", "plugin/chapter", "plugin/danmuku.html", "plugin/danmuku", "plugin/dash-control.html", "plugin/dash-control", "plugin/document-pip.html", "plugin/document-pip", "plugin/hls-control.html", "plugin/hls-control", "plugin/vtt-thumbnail.html", "plugin/vtt-thumbnail", "start/i18n.html", "start/i18n", "start/option.html", "start/option"]);
installNavigation(window, ["advanced/built-in.html", "advanced/built-in", "advanced/class.html", "advanced/class", "advanced/event.html", "advanced/event", "advanced/global.html", "advanced/global", "advanced/plugin.html", "advanced/plugin", "advanced/property.html", "advanced/property", "component/contextmenu.html", "component/contextmenu", "component/controls.html", "component/controls", "component/layers.html", "component/layers", "component/setting.html", "component/setting", "", "index.html", "plugin/ambilight.html", "plugin/ambilight", "plugin/asr.html", "plugin/asr", "plugin/audio-track.html", "plugin/audio-track", "plugin/auto-thumbnail.html", "plugin/auto-thumbnail", "plugin/chapter.html", "plugin/chapter", "plugin/danmuku.html", "plugin/danmuku", "plugin/dash-control.html", "plugin/dash-control", "plugin/document-pip.html", "plugin/document-pip", "plugin/hls-control.html", "plugin/hls-control", "plugin/multiple-subtitles.html", "plugin/multiple-subtitles", "plugin/vtt-thumbnail.html", "plugin/vtt-thumbnail", "start/i18n.html", "start/i18n", "start/option.html", "start/option"]);
})();