@video-lab/vue

Video Lab Player Vue 3 inline 组件:直连 player-core 的高性能接入方式
2,152
Install
npm install @video-lab/vue
Latest Version:4.1.5
Vue:3
License:MIT
Last Updated:Sep 30, 2026
Links: Repository  ·  npm
Maintainer: jianian

@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)。

宿主接入前要准备什么

  1. 一个已经授权、能被浏览器跨域访问的 source。SDK 只接收最终 URL / MediaSource,不接收内容 ID、业务 headers 或 token。
  2. 宿主自己的首屏骨架/海报、业务终态 CTA、错误文案和遥测传输队列。
  3. 若媒体 URL 会过期,一个仅返回完整新 MediaSource 的业务服务函数;URL 续签不是播放器发业务 HTTP。
  4. 一次 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 和有界传输队列。
  • 真实验收了弱网、自动播放受阻、无字幕 / 无档位、错误终态和路由卸载。

深入场景、错误码、换源和恢复口径见:

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

@video-lab/vue-frame

Video Lab Player Vue 3 iframe 组件:隔离沙箱接入,不引入播放内核

2,154
@video-lab/vue-config

Video Lab Player 的 Vue 全局配置与语言上下文

1,370

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