@video-lab/vue
| Install | |
|---|---|
npm install @video-lab/vue |
|
| Latest Version: | 4.1.5 |
| Vue: | 3 |
| License: | MIT |
| Last Updated: | Sep 30, 2026 |
| Links: | Repository · npm |
@video-lab/vue
Video Lab Player 的 Vue 3 inline 接入包。它在宿主页面直接创建播放内核,提供统一的
props、Vue emits 与 template ref 命令;不经过 iframe,因此首帧与交互开销最低。
同时导出 PlayerProvider / usePlayerI18n,可在应用入口设置语言和播放器默认值,由下面的多个实例继承。用法与字段见包内 API 参考。
本包解决的是“播放一个已授权媒体,并把可播放状态、错误、恢复和 QoE 事实交给宿主”。它不请求业务 接口、不保存 token、不提供品牌控制栏,也不替宿主把每个事件发送到网络。
先判断是否该用本包
| 你的场景 | 选择 |
|---|---|
| Vue 页面,追求最低首帧开销或需要深度组合页面 UI | 本包:@video-lab/vue |
| Vue 页面,需要 CSS / 脚本隔离或播放器独立部署 | @video-lab/vue-frame |
| React 页面 | @video-lab/react / @video-lab/react-frame |
| CMS / Markdown 只能嵌一个 URL | 静态 iframe + @video-lab/embed-helper |
inline 会把播放内核打进宿主产物(约 300 KB gzip,含 xgplayer、hls.js、flv.js)。若这不是能接受的 取舍,请改用 vue-frame;两种 Vue 组件的播放 props、事件名称和业务语义保持对齐。
安装
pnpm add @video-lab/vue
安装包附带宿主接入 AI Skill。复制整个
skills/host-integration/ 到宿主 AI 的 Skill 目录,按
Vue inline 参考起步,并核对
supportedPackages 与安装版本;画中画、封面、Loading、QoE 和 403 可查
功能配方。完整 API 仍以本 README 与导出类型为准。
默认简体中文;:locale="language" 可响应式切换英文等语种。越南语按需安装 @video-lab/locales,从 @video-lab/locales/vi-VN 导入 viVN,传入 { locale: 'vi-VN', messages: { 'vi-VN': viVN } }。实例 setLocale() 也会更新控件与覆盖层;其他语种和少量覆盖见包内 API 参考。
peer dependency 为 vue ^3.4.0(支持 Vue 3.4 / 3.5,最低 3.4.0)。组件复用宿主 Vue,不内嵌第二份运行时。用 vue-tsc 做类型检查时请使用 TypeScript 5.x:vue-tsc@3 尚不兼容 TypeScript 7(报 ERR_PACKAGE_PATH_NOT_EXPORTED)。
宿主接入前要准备什么
- 一个已经授权、能被浏览器跨域访问的
source。SDK 只接收最终 URL /MediaSource,不接收内容 ID、业务 headers 或 token。 - 宿主自己的首屏骨架/海报、业务终态 CTA、错误文案和遥测传输队列。
- 若媒体 URL 会过期,一个仅返回完整新
MediaSource的业务服务函数;URL 续签不是播放器发业务 HTTP。 - 一次 CSS 引入:
@video-lab/vue/style.css。少了它媒体可能能播放,但控件布局不完整。
最小可跑示例
解决什么: 在 Vue 3 页面播放 HLS,并获得“播放器可用”和“媒体失败”两个最基本的事实。
<script setup lang="ts">
import { ref } from 'vue'
import {
VideoPlayer,
type PlayerError,
type VideoPlayerExpose,
} from '@video-lab/vue'
// 必须引一次:xgplayer 控件与布局样式。
import '@video-lab/vue/style.css'
const player = ref<VideoPlayerExpose | null>(null)
function onReady({ duration }: { duration: number }) {
console.info('播放器已获得 metadata,时长:', duration)
}
function onError(error: PlayerError) {
console.error(error.code, error.message)
}
</script>
<template>
<div style="width: 100%; aspect-ratio: 16 / 9">
<VideoPlayer
ref="player"
source="https://media.example.com/lesson.m3u8"
playsinline
@ready="onReady"
@error="onError"
/>
</div>
</template>
ready 说明 metadata 与能力清单可用,不等于首帧已可见;播放是否仍可继续应始终由
@playable-change 判断。模板 ref 可调用 await player.value?.play()、player.value?.seek(30) 和
player.value?.setPlaybackRate(1.5)。
生产场景示例:Loading、403 换源、终态 CTA 与 QoE
解决什么: 这个完整组件展示四件常见且容易接错的事:
- 播放器区域的首次与运行时 Loading 都由
#loading接管,宿主只为页面级 CTA 消费聚合状态; - 签名过期时让 SDK 调用
resolveSource,业务服务返回新源或明确拒绝; - 不可恢复状态才显示重试 / 业务 CTA,不能对旧 403 URL 无限重试;
- 完整
PlayerEvent写进一个TelemetrySession,会话结束输出 QoE summary,而不是将timeupdate逐条发 HTTP。
<script setup lang="ts">
import { onBeforeUnmount, ref } from 'vue'
import {
VideoPlayer,
type MediaSource,
type PlayerError,
type ResolveSource,
type VideoPlayerExpose,
} from '@video-lab/vue'
import { createTelemetrySession } from '@video-lab/telemetry'
import '@video-lab/vue/style.css'
const BUILD_SHA = 'web-2026.09.16'
const player = ref<VideoPlayerExpose | null>(null)
const showRetry = ref(false)
const showAccessCta = ref(false)
const source: MediaSource = {
url: 'https://media.example.com/course/master.m3u8',
type: 'hls',
live: false,
}
// 由宿主 BFF 完成登录、会员、地区与签名校验;null 是明确没有替代源。
async function requestAuthorizedSource(signal: AbortSignal): Promise<MediaSource | null> {
if (signal.aborted) return null
return {
url: 'https://media.example.com/course/refreshed-master.m3u8',
type: 'hls',
live: false,
}
}
const resolveSource: ResolveSource = ({ signal }) => requestAuthorizedSource(signal)
const telemetry = createTelemetrySession({
application: { name: 'course-web', release: BUILD_SHA },
sink: {
// 这里必须是同步、快速且有界的 enqueue;网络批量发送由宿主队列在后台完成。
onRecord(record) {
enqueueTelemetry({ kind: 'record', value: record })
},
async onSummary(summary) {
enqueueTelemetry({ kind: 'summary', value: summary })
await flushTelemetry()
},
},
})
function enqueueTelemetry(_item: unknown) {}
async function flushTelemetry() {}
function onPlayableChange(payload: {
playable: boolean
recoverable: boolean
reason: string
action?: string
}) {
showRetry.value = !payload.playable && !payload.recoverable && payload.action === 'retry'
}
function onError(error: PlayerError) {
// E_AUTH_EXPIRED 时 resolveSource 已启动;这里不再对旧 URL 手工重连。
if (error.code === 'E_AUTH_EXPIRED') return
showAccessCta.value = error.code === 'E_MEDIA_NOT_SUPPORTED'
}
async function retry() {
try {
await player.value?.retry()
} catch (error) {
console.warn('重试命令未被接受', error)
}
}
onBeforeUnmount(() => telemetry.finish({ reason: 'destroyed' }))
</script>
<template>
<section>
<VideoPlayer
ref="player"
:source="source"
:resolve-source="resolveSource"
:show-error-overlay="false"
@playable-change="onPlayableChange"
@error="onError"
@player-event="telemetry.record"
>
<template #loading="{ phase, reason }">
<p role="status">{{ phase === 'initial' ? '正在加载视频…' : `正在恢复播放(${reason})…` }}</p>
</template>
</VideoPlayer>
<button v-if="showRetry" type="button" @click="retry">重试播放</button>
<a v-if="showAccessCta" href="/account/upgrade">查看可用播放方式</a>
</section>
</template>
上例里的 enqueueTelemetry 是宿主传输层的边界,不是空实现就可以省略:它至少要有最大队列、批量大小、
有限重试、并发上限、P0/P1/P2 优先级和服务端幂等。完整频率策略见
@video-lab/telemetry 安装包 README 的“高频与网络安全宿主必须做的事”。
场景到 API:应监听什么,宿主做什么
| 场景 | 首选 API | 宿主动作 | 不要做 |
|---|---|---|---|
| 首屏封面 + 品牌 Loading | poster + #loading |
SDK 从挂载显示到 firstframe;宿主只提供视觉 |
用 ready / play 提前撤封面,或手拼首帧状态 |
| 运行时卡顿 / 自动恢复 | #loading |
SDK 在可恢复等待时再次显示同一视觉 | 用 waiting、playing、内核错误手工拼状态 |
| 不可恢复播放错误 | @playable-change 的 action + @error |
退出 Loading,按 retry / play / replace-source 呈现 CTA |
将每个 error 都画成同一个错误页 |
| 403 / 签名失效 | resolveSource + 完整 @player-event |
服务端返回完整新源;拒绝时进入业务终态 | 向 SDK 注入 header,或无限重试旧 URL |
| 自动播放被浏览器阻止 | @autoplay-blocked / action: 'play' |
显示用户手势的播放或静音播放按钮 | 当作网络错误自动重连 |
| QoE / 错误 / 恢复归因 | @player-event → telemetry.record |
同一播放会话只创建一个 telemetry session | 对 @time-update 一次事件一次 HTTP |
| 清晰度、字幕菜单 | @ready + setQuality / setSubtitle |
以 ready.quality、ready.subtitles 生成菜单,以 change 事件回显 |
假设每个源都有档位或字幕 |
| 自定义业务按钮 / 快捷键 | telemetry.recordIntent |
在按钮实际触发处记录允许的 intent | 把用户 ID、自由 tags 或正文塞进 telemetry |
Props:创建播放器时能决定什么
多数 props 是初始化配置。带“需重建”的字段在运行时修改不会重新配置已创建的内核;要改变它们,请换
组件 :key 重建一次播放会话。
| 分组 | Props | 作用与注意点 |
|---|---|---|
| 媒体与授权 | source、resolveSource |
source 必填,字符串或 MediaSource;resolveSource 仅在 E_AUTH_EXPIRED 时请求完整替代源,返回 null 表示业务无替代源。若不提供,宿主须在 @playable-change 的 action='replace-source' 时展示业务入口并取得新源,否则用户会停在没有重试按钮的授权错误态。 |
| 起播行为 | autoplay、muted、loop、playsinline、preload、startTime |
建议预览卡片同时 autoplay muted;startTime、preload 是创建期语义,不能用挂载后 seek() 完整替代。 |
| 初始媒体表现 | volume、playbackRate、poster、pauseImage |
volume 取 0–1,playbackRate 取 0.25–4;封面和暂停图可传 URL 或配置对象。 |
| SDK UI | controls、controlVisibility、showDefaultLoadingOverlay、showErrorOverlay、interactive |
controls=false 整体关闭控件;后三项适合交给宿主品牌 UI 接管。controlVisibility 与两个 overlay 配置变更需重建。 |
| 功能扩展 | preset、locale、danmaku、pageFullscreen |
preset 只填默认值,未传布尔 prop 才会生效;字幕/弹幕数据和网页全屏布局仍由宿主提供。 |
| 调试与一致性 | debug |
inline 没有 iframe 通信层,保留该字段只为跨接入面 props 对齐;它在 inline 中无作用。 |
source 对象适合表达 type、live、HLS 候选、字幕等播放事实;这些字段的完整形状以
MediaSource 类型为准。不要把“免费 / 会员 / 1080p”
伪造成 sources[] 候选:协议候选表示媒体封装,权益应由服务端签发对应的播放源。
controlVisibility 可分别隐藏 play、progress、time、volume、playbackRate、fullscreen、
cssFullscreen、pip 八个内置入口;false 是隐藏,省略 / true 使用平台默认,controls=false 优先。
preset 只补未传字段,因此希望预设接管 muted 等布尔值时,必须省略该 prop,而不是传 false。
Events:所有公开事实如何分工
Vue 模板采用 kebab-case,例如 TypeScript 中的 timeUpdate 在模板写成 @time-update。@player-event
是唯一完整事件出口;其余为方便渲染 UI 的拆分 emits。PlayerEvent 用 event 字段区分类型(如 e.event === 'ready'),数据在 payload;它没有 type 字段。
| 事件组 | 事件 | 什么时候用 |
|---|---|---|
| 完整事实流 | player-event |
PlayerEvent.delivery 带生产端 ID、序号与时间,可用于 QoE 排序、去重与迟到事件识别。recovery、sourceswitch 只在完整流中出现。 |
| 基础播放 | ready、play、pause、ended、time-update、volume-change |
渲染控制栏和业务播放意图。ready 同时给 quality 与可选 subtitles。不要把每次 time-update 上网。 |
| 用户操作结果 | seeking、seeked、quality-change、subtitle-change、user-action |
更新进度跳转、菜单选中状态与 SDK 内部控件动作。user-action 不覆盖宿主自定义按钮,后者需显式 recordIntent()。 |
| 瞬时媒体信号 | waiting、playing、stalled |
waiting / playing 可做即时局部反馈;stalled 是带时长的已测卡顿事实,适合上报,不应用来取代聚合 UI 状态。 |
| 用户可见结论 | playable-change、autoplay-blocked、error |
UI 只以 playable-change 的 playable、recoverable、action 收口;error 是稳定码与诊断,autoplay 需要用户手势。 |
| 诊断与 QoE | kernel-health、audio-health、buffer-health、context-change、source-route、first-frame、frame-freeze |
进入 telemetry / 排障。它们分别描述内核诊断、FLV 音频连续性、缓冲趋势、播放上下文、实际选源、首帧和 PC 帧冻结。不可用量具会报告 unknown,不是零。 |
| 平台与布局 | compat-warning、page-fullscreen-change |
前者提示已知兼容性或版本问题;后者反映宿主网页全屏的实际布局状态。 |
playable-change 的关键规则是:!playable && recoverable 才是运行时 Loading;degraded 虽然质量变差,
仍是 playable=true,不能盖住画面;action='play' 要用户手势,action='replace-source' 要业务服务给新源。
Template ref:命令、返回值与正确时机
inline 直连播放内核,因此只有需要浏览器异步许可的命令是 Promise;其他命令同步生效。retry() 的
Promise 仅表示调度器接受或合并请求,恢复成功仍要等待后续 recovery / playable-change 事实。
| 目的 | 方法 | 返回与使用方式 |
|---|---|---|
| 播放、浏览器全屏、网页全屏、恢复 | play()、enterFullscreen()、exitFullscreen()、setPageFullscreen()、retry() |
Promise<void>;await 并处理 rejection。setPageFullscreen 还需要稳定的 pageFullscreen 宿主适配器。 |
| 直接控制播放 | pause()、seek(time)、setMuted()、setVolume()、setPlaybackRate() |
同步;调用后等待相应事件更新 UI,而不是猜测状态。 |
| 功能菜单 | `setQuality(level | 'auto')、setSubtitle(id |
| 弹幕 | pushDanmaku()、setDanmakuEnabled()、clearDanmaku() |
同步渲染命令;审核、连接、发送和限流始终属于宿主 / 服务端。 |
| 查询与销毁 | getCurrentTime()、getDuration()、getPlaybackContext()、getPlayerHandle()、destroy() |
前四个同步读取;getPlaybackContext() 在未挂载/销毁后为 null。普通 Vue 卸载会自动清理,手工 destroy 仅用于显式会话收口。 |
命令 rejection 与播放期间 @error 是两条独立事实:前者不保证产生 error,后者也不一定来自某次命令。
生命周期、样式与边界
- 组件销毁会释放播放器;页面/路由离开时同时
telemetry.finish({ reason: 'destroyed' }),避免丢失 QoE summary。 - 提供
#loading会自动替换默认 Loading;只有把 Loading DOM 完全放在组件外时才设置:show-default-loading-overlay="false"。错误 CTA 仍应消费@playable-change和@error。 - 不需要配置 Vite 的
isCustomElement。本包在运行时渲染覆盖层,Vue 编译器不直接处理这些元素。 - 不支持 DASH 或 PlayReady;不应直接操作 xgplayer 私有 API。高级
getPlayerHandle()是逃生舱,不是稳定的业务常规接口。
验收清单与继续阅读
- 已导入
@video-lab/vue/style.css,且真实签名 URL / CORS 环境可播放。 - 播放器区域用
#loading统一接管首次/运行时视觉,终态 CTA 消费playable-change。 - 403 返回新完整源或明确业务 CTA,没有无限重试旧 URL。
- QoE 走
player-event、单会话TelemetrySession和有界传输队列。 - 真实验收了弱网、自动播放受阻、无字幕 / 无档位、错误终态和路由卸载。
深入场景、错误码、换源和恢复口径见:
- 五种模式与起步
- 业务场景与功能配方
- 离线 API、错误与恢复语义
- 网页全屏:见包内 API 参考的“网页全屏”一节
- 遥测:安装
@video-lab/telemetry后读该包 README
Poster 上叠自定义 Loading
<VideoPlayer
:source="selectedRoom.source"
:poster="{ url: selectedRoom.videoPoster, fit: 'cover', loading: 'eager' }"
>
<template #loading="{ phase, reason }">
<BusinessLoading transparent :phase="phase" :reason="reason" />
</template>
</VideoPlayer>
initial 从挂载保持到 firstframe,runtime 表示首帧后的可恢复等待。提供 #loading 后会替换
默认 Loading,无需再传 :show-default-loading-overlay="false"。slot 是否存在属于构造期策略;
要切换实现时重新挂载组件。
封面和暂停画面可用 #poster、#pause 替换内置图片;SDK 管首帧、换源、明确暂停与播放后的显隐,#pause 内的按钮调用组件 ref 的 play()。内置控件或公开 pause() 对应的实际暂停会显示暂停画面,网络恢复、换源或系统打断的被动暂停不会显示。内置 pauseImage 的整图可点击,中央有半透明播放提示。不传 poster / pauseImage 即不使用;null 仅用于清除 Provider 默认图片。
带业务流程的启动占位由宿主维护,且只能出现一次
普通 Poster + Loading 使用上面的 #loading 即可。带倒计时、跳过或开播门控的业务启动占位仍由宿主维护,写它时有一条必须注意:
firstframe 不是「一辈子只发一次」的事件,它是「每个会话发一次」。 换源(load() 换地址、跨内核被拒后重建)、手动 retry()、iframe 重建都会开启新会话,firstframe 随之再发一次,poster 也会重新显示。如果把占位的显隐直接绑在 firstframe 上而不记状态,换源时启动占位会再盖一次——对一个已经在看的用户来说,画面上突然又出现「加载中」。
正确写法是宿主自己维护一个只置位、不复位的标记:
// 只认第一次:此后换源、重连、卡顿都不再显示启动占位
function onFirstFrame() {
startupDone.value = true
}
模板里给播放器绑定 @first-frame="onFirstFrame",并按 startupDone 条件渲染占位
(<MyStartupPlaceholder v-if="!startupDone" />)。
要点:
- 绑
firstframe,不要绑ready或play:后两者只表示「可以播 / 开始播这个动作」,画面还没出来(实测 FLV 上ready到首帧可差 6 秒以上)。 - 不要用固定时长的倒计时来撤:起播耗时跨度很大(实测 HLS 0.4s、FLV 6.6s),计时器撤早了会露黑、撤晚了让用户白等。倒计时可以数,但撤不撤要看事件。
- 不要在换源时复位:换源期间的覆盖由 SDK 的恢复态遮罩接管,宿主再盖一层是重复。
- 只有组件真正卸载重挂(用户离开播放页又回来)才算新的一次首次进入,那时可以复位。
- 失败分支:收到不可恢复终态(
playablechange的playable:false, recoverable:false)时撤掉占位,让位给错误 UI,否则会一直盖着。
Related Packages
Version History
| Version | Released | License |
|---|---|---|
| 4.1.5 | MIT | |
| 4.1.4 | MIT | |
| 4.1.3 | MIT | |
| 4.1.2 | MIT | |
| 4.1.1 | MIT | |
| 4.1.0 | MIT | |
| 4.0.1 | MIT | |
| 4.0.0 | MIT | |
| 3.1.0 | MIT | |
| 3.0.1 | MIT | |
| 3.0.0 | MIT | |
| 2.0.0 | MIT | |
| 1.0.1 | MIT |