docs(vtt): [SITE-VTT-01] add bilingual guides and sprite pixel checks

This commit is contained in:
Harvey Zhao committed 2026-09-15 19:06:44 +08:00
1 parent d3349e83f2
commit 9ba3c82f99
71 files changed
+6207 -1683

No files matched your search

@@ -171,3 +171,7 @@ archive and source hashes. Missing or stale maps fail without source fallback.
Without a map, source and deliberate artifact checks retain their existing behavior.
This is desktop main-entry coverage, not all module forms, physical devices or
release readiness. See ../../refactor/changes/2026-09-15-CI-01-subtitles-installed.md.
## Documentation regression
The bilingual VitePress vtt-thumbnail guides must track parseVtt.ts, preview.ts and the asynchronous registration boundary. The plugin has no update/reload method; changing the main video does not change its VTT index. document-vtt.spec.js executes the original demo with local VTT/SVG fixtures and the unchanged demo video, verifies real screenshot crop pixels, shared-boundary selection, gaps and teardown on both cores. Exact boundary input uses public setBar with a MouseEvent, while region selection uses real mouse movement. Run yarn test:browser document-vtt.spec.js document-site.spec.js --workers=1 after rebuilding docs. This local dist guide test is separate from installed package and physical touch acceptance.
@@ -67,3 +67,7 @@ desktop tests and emulated touch checks do not certify physical mobile devices.
## License
MIT © Harvey Zhao
## Usage guides
See the [Chinese guide](../artplayer-vitepress/docs/plugin/vtt-thumbnail.md) and [English guide](../artplayer-vitepress/docs/en/plugin/vtt-thumbnail.md) for sprite format, whole-second inclusive boundaries, asynchronous registration, error handling and accurate runtime types. Both retain the original editor example; branch-only behavior is marked as unpublished.
+10 -2
View File
@@ -14,17 +14,25 @@ not a VitePress page. Keep both URL surfaces compatible.
| `docs/plugin/hls-control.md`, `docs/en/plugin/hls-control.md` | HLS SDK setup, menus, lifecycle and types | `/document/plugin/hls-control.html`, `/document/en/plugin/hls-control.html` |
| `docs/plugin/dash-control.md`, `docs/en/plugin/dash-control.md` | DASH SDK generations, menus, lifecycle and types | `/document/plugin/dash-control.html`, `/document/en/plugin/dash-control.html` |
| `docs/plugin/audio-track.md`, `docs/en/plugin/audio-track.md` | Separate audio synchronization, ownership and update types | `/document/plugin/audio-track.html`, `/document/en/plugin/audio-track.html` |
| `docs/plugin/vtt-thumbnail.md`, `docs/en/plugin/vtt-thumbnail.md` | Sprite index format, timing boundaries, asynchronous registration and types | `/document/plugin/vtt-thumbnail.html`, `/document/en/plugin/vtt-thumbnail.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 |
| `docs/public/style.css` | Documentation presentation | Copied into the built site |
Edit Markdown and public sources here. Do not edit the generated repository
`docs/document/`. The current inventory contains 34 Markdown pages: 17 Chinese
and 17 English. Danmuku, HLS, DASH and Audio Track have dedicated guides and navigation in both languages. Other plugins currently
`docs/document/`. The current inventory contains 36 Markdown pages: 18 Chinese
and 18 English. Danmuku, HLS, DASH, Audio Track and VTT Thumbnail 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.
VTT Thumbnail guides preserve the original vtt.thumbnail demo. The document-vtt
suite checks actual screenshot crop pixels, a shared time boundary, a gap and
cleanup using the original video, controlled VTT/SVG resources and real progress
hover on both cores. The exact endpoint uses public setBar with a MouseEvent to
avoid pointer pixel rounding while satisfying the core's event contract.
This does not establish physical touch or full playback acceptance.
Audio Track guide examples match the actual audio.track demo. The dedicated
document-audio browser suite substitutes local Range-served media, then checks
native video pixels and AAC playback, pause, offset seek, volume/rate, source
@@ -72,7 +72,7 @@ const config = {
{ text: 'Iframe 控制', link: 'https://artplayer.org/?libs=./uncompiled/artplayer-tool-iframe/index.js&example=iframe' },
{ text: 'HLS 控制', link: '/plugin/hls-control' },
{ text: 'DASH 控制', link: '/plugin/dash-control' },
{ text: 'VTT 缩略图', link: 'https://artplayer.org/?libs=./uncompiled/artplayer-plugin-vtt-thumbnail/index.js&example=vtt.thumbnail' },
{ text: 'VTT 缩略图', link: '/plugin/vtt-thumbnail' },
{ text: '多重字幕', link: 'https://artplayer.org/?libs=./uncompiled/artplayer-plugin-multiple-subtitles/index.js&example=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' },
@@ -144,7 +144,7 @@ const config = {
{ text: 'Iframe Control', link: 'https://artplayer.org/?libs=./uncompiled/artplayer-tool-iframe/index.js&example=iframe' },
{ text: 'HLS Control', link: '/en/plugin/hls-control' },
{ text: 'DASH Control', link: '/en/plugin/dash-control' },
{ text: 'VTT Thumbnail', link: 'https://artplayer.org/?libs=./uncompiled/artplayer-plugin-vtt-thumbnail/index.js&example=vtt.thumbnail' },
{ 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: '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' },
@@ -0,0 +1,117 @@
# 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"></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.
## 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.
@@ -0,0 +1,117 @@
# VTT 缩略图
[English](../en/plugin/vtt-thumbnail.md)
读取一份 WebVTT 缩略图索引,在进度条悬停时显示对应的精灵图区域。图片和索引需要提前生成;插件不扫描视频,也不需要额外 SDK。
本页描述当前重构分支。解析和生命周期修复、`/runtime` 精确类型尚未发布;未固定版本的 npm/CDN 安装不能当作本分支代码。
## 安装
```sh
yarn add artplayer artplayer-plugin-vtt-thumbnail
```
```js
import Artplayer from 'artplayer';
import artplayerPluginVttThumbnail from 'artplayer-plugin-vtt-thumbnail';
```
通过 script 使用时先加载 ArtPlayer,再加载插件的 `dist/artplayer-plugin-vtt-thumbnail.js`。全局名称为 `artplayerPluginVttThumbnail`。固定依赖版本,并确保页面能读取 VTT、加载图片;跨域 VTT 请求需要服务器允许 CORS。
## 完整示例
下面与[在线缩略图示例](https://artplayer.org/?libs=./uncompiled/artplayer-plugin-vtt-thumbnail/index.js&example=vtt.thumbnail)使用同一份代码。站点提供媒体和 `.artplayer-app` 容器,自己的应用需要替换对应地址。
<div className="run-code" data-libs="./uncompiled/artplayer-plugin-vtt-thumbnail/index.js"></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',
}),
],
})
```
## 配置
`artplayerPluginVttThumbnail(option)` 的配置对象必须提供。
| 字段 | 类型 | 行为 |
| --- | --- | --- |
| `vtt` | `string`,类型中可选 | VTT 文件地址。实际使用应提供有效地址;省略会请求空地址,即当前页面,不是禁用开关。 |
| `style` | `Partial<CSSStyleDeclaration>`,可选 | 缩略图控件的初始内联样式,例如 `borderRadius: '4px'`。 |
显示缩略图时,插件会更新 display、width、height、left、backgroundImage 和 backgroundPosition;不要依赖初始 style 永久覆盖这些值。尺寸和裁剪来自每条索引,插件不自动缩放精灵图。
## 索引格式与图片路径
```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
```
`x,y,w,h` 分别是图片内的左边位置、上边位置、宽度和高度,单位像素。x/y 必须非负,w/h 必须大于零,四项必须是有限数值。每条 cue 使用一行带裁剪片段的图片地址,不能直接把普通字幕文字当作缩略图索引。
相对图片地址按**传入的 VTT 地址所在目录**拼接:`/assets/sample/bbb-thumbnails.vtt` 中的 `bbb-sprite.jpg` 对应 `/assets/sample/bbb-sprite.jpg`。以 `/` 或已支持协议开头的完整地址直接使用。不以 HTTP 重定向后的 response URL 重新计算目录;有重定向或复杂相对路径时,建议索引使用明确的图片地址。
解析器支持 BOM、常见换行符、可选 cue 标识符和时间行设置,并跳过 NOTE、STYLE、REGION 块。它用于缩略图索引,不是完整 WebVTT 字幕排版实现。
## 时间与显示边界
为保留旧行为,开始和结束时间向下取整到秒;区间的两端都包含在内,并选择文件顺序中第一条匹配项。例如在上面的精确 5 秒边界仍显示第一张,超过 5 秒才显示第二张。不要假设它提供毫秒级精确区间或“结束时间不包含”的字幕语义。
桌面悬停按进度条百分比乘以视频总时长选择图片;没有匹配项时隐藏,预览靠近边缘时会向内对齐。移动端使用带输入事件的进度拖动路径,并在最后一次拖动更新后约 500ms 隐藏。桌面测试不能替代真实触摸设备验收。
## 异步注册、错误和销毁
注册会请求并解析 VTT,实际返回 Promise,成功结果只有 `name: 'artplayerPluginVttThumbnail'`。构造器 plugins 数组中的安装是异步的,不要在构造后立即假设结果已注册。
需要明确等待和处理请求或解析失败时,可在构造播放器后调用一次 `art.plugins.add()`:
```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();
```
请求失败或格式错误会拒绝注册,格式错误包含行号。注册完成只说明 VTT 已加载解析并创建控件,不表示所有图片已解码;图片在浏览器显示时加载,图片请求失败不会变成已经完成的注册 Promise 的拒绝。
插件没有 update、reload 或独立 destroy 方法。切换主视频不会自动重新请求 VTT;切换到另一套视频和缩略图时,可销毁并重建播放器。不要把 `controls.remove()` 当作完整卸载,也不要用重复安装模拟一个没有提供的更新接口。
销毁播放器会取消待处理请求(环境支持 AbortController 时),结算取消的注册、移除自己的监听器和计时器,并移除仍由本次安装持有的控件。取消结算仍返回名称对象,因此名称本身不是图片可用的证明。迟到请求不会重新挂载界面。
## TypeScript 兼容
根入口和 `/legacy` 保留最新已发布 1.1.0 的旧同步返回类型与函数替换形状,实际注册始终是异步的。上面的 `/runtime` 使用相同 JavaScript 实现,准确声明 Promise 和运行时 `.default` 自引用别名,并提供 `Option`、`Result`、`Factory`、`RuntimeFactory` 类型。
1.0.x 的旧 `export =` 与 1.1.0 的默认导出声明无法同时保持相同类型提取。依赖较早 CommonJS 声明的 TypeScript 代码应迁移到 `/runtime`;NodeNext ESM 消费者也优先使用此入口,避免根入口保留的历史命名空间形状。合法旧 JavaScript 调用和历史文件入口继续保留。
浏览器验证覆盖新旧核心的进度条裁剪和清理;完整移动端、插件组合与发布产物验收仍以项目记录为准。
@@ -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/audio-track.html", "plugin/audio-track", "plugin/danmuku.html", "plugin/danmuku", "plugin/dash-control.html", "plugin/dash-control", "plugin/hls-control.html", "plugin/hls-control", "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/audio-track.html", "plugin/audio-track", "plugin/danmuku.html", "plugin/danmuku", "plugin/dash-control.html", "plugin/dash-control", "plugin/hls-control.html", "plugin/hls-control", "plugin/vtt-thumbnail.html", "plugin/vtt-thumbnail", "start/i18n.html", "start/i18n", "start/option.html", "start/option"]);
})();