@video-lab/vue-frame

Video Lab Player Vue 3 iframe 组件:隔离沙箱接入,不引入播放内核
2,154
Install
npm install @video-lab/vue-frame
Latest Version:4.1.5
Vue:3
License:MIT
Last Updated:Sep 30, 2026
Links: Repository  ·  npm
Maintainer: jianian

@video-lab/vue-frame

Video Lab Player 的 Vue 3 iframe 接入包。它在宿主页面创建 iframe、完成握手与消息桥接,再把统一的 播放 props、事件和 template ref 命令暴露为 Vue API。播放器内核与宿主样式 / 脚本隔离,iframe artifact 可以独立部署;代价是首次握手和每个命令都要跨窗口异步执行。

同时导出 PlayerProvider / usePlayerI18n,可在应用入口设置语言、播放器默认值和 Frame 连接参数;无需安装 @video-lab/vue。用法与字段见包内 API 参考。

本包解决的是“以隔离方式播放已授权媒体,并将媒体事实、桥接状态、错误、恢复与 QoE 交给宿主”。它不发 业务 HTTP、不保存 token、不内置品牌 UI,也不替宿主将每条事件直接请求上报服务。

先判断是否该用本包

你的场景 选择
Vue 宿主需要 CSS / 脚本隔离,或播放器 artifact 要独立部署 本包:@video-lab/vue-frame
Vue 页面追求最低首帧开销,需要直接组合播放器 DOM @video-lab/vue
CMS / Markdown 只能嵌 URL 静态 iframe + @video-lab/embed-helper
React 页面 @video-lab/react-frame / @video-lab/react

本包的 props、事件名称和播放语义与 Vue inline 保持对齐;额外需要理解的仅是 origin、iframe 版本、 握手 / 失联和“命令全是 Promise”。静态 iframe URL 不等同于本包:它没有命令 RPC、实例级握手和 frame_disconnected 生命周期。

安装

pnpm add @video-lab/vue-frame

安装包附带宿主接入 AI Skill。复制整个 skills/host-integration/ 到宿主 AI 的 Skill 目录,按 Vue iframe 参考起步,并核对 supportedPackages 与安装版本;画中画、封面、Loading、QoE 和 403 可查 功能配方。完整 API 仍以本 README 与导出类型为准。

默认简体中文;:locale="language" 可运行时切换英文等语种。越南语按需安装 @video-lab/locales,从 @video-lab/locales/vi-VN 导入 viVN,传入 { locale: 'vi-VN', messages: { 'vi-VN': viVN } };不需安装 @video-lab/vue。实例 setLocale() 会同步 iframe 内播放器语言。完整接法见包内 API 参考。

用 vue-tsc 做类型检查时请使用 TypeScript 5.x:vue-tsc@3 尚不兼容 TypeScript 7(报 ERR_PACKAGE_PATH_NOT_EXPORTED)。

peer dependency 为 vue ^3.4.0(支持 Vue 3.4 / 3.5,最低 3.4.0)。组件复用宿主 Vue,不内嵌第二份运行时。

宿主接入前要准备什么

  1. 已授权且 CORS 可访问的媒体 source;SDK 不接收内容 ID、业务 headers 或 token。
  2. iframe artifact 的受控部署根地址。显式传 origin,或在构建环境配置 VITE_VIDEO_LAB_EMBED_ORIGIN;两者都没有会在初始化失败。
  3. 一份部署兼容策略:需要固定 artifact 时传 iframeVersion(格式为 v<semver>),并验证 host 与 iframe 的协议主版本一致。
  4. 宿主可按品牌自定义 Loading、终态 CTA 和遥测传输队列;默认 Loading、错误层及可执行按钮已内置。
  5. 若签名 URL 会过期,一个只返回完整新 MediaSource 的业务服务函数;续签不由 iframe 发业务 HTTP。

最小可跑示例

解决什么: 让 Vue 页面以明确的 iframe 部署地址播放 HLS,并同时获得 metadata、媒体错误和握手后的可用实例。

<script setup lang="ts">
import { ref } from 'vue'
import {
  VideoPlayerFrame,
  type PlayerError,
  type VideoPlayerFrameExpose,
} from '@video-lab/vue-frame'

// 来自受控部署配置,不是媒体 URL,也不能照抄到生产环境。
const playerOrigin = 'https://player.example.internal'
const player = ref<VideoPlayerFrameExpose | null>(null)

function onReady({ duration }: { duration: number }) {
  console.info('iframe 内播放器已获得 metadata,时长:', duration)
}

function onError(error: PlayerError) {
  console.error(error.code, error.message)
}
</script>

<template>
  <div style="width: 100%; aspect-ratio: 16 / 9">
    <VideoPlayerFrame
      ref="player"
      source="https://media.example.com/lesson.m3u8"
      :origin="playerOrigin"
      playsinline
      @ready="onReady"
      @error="onError"
    >
      <template #ssr-placeholder>
        <div style="width: 100%; height: 100%; background: #000" />
      </template>
    </VideoPlayerFrame>
  </div>
</template>

组件只在 onMounted 后创建 iframe;SSR 阶段会渲染 #ssr-placeholder,因此 Nuxt 不需要额外包 <ClientOnly>。ready 表示 metadata 可用,不是首帧成功;运行时播放状态统一看 @playable-change。 所有 ref 命令都跨 iframe,例如 await player.value?.play()、await player.value?.seek(30)。

生产场景示例:Loading、403 换源、QoE 与 iframe 崩溃恢复

解决什么: 这个完整示例包含生产页面必须分开的四类状态:

  • poster 上的宿主侧 #loading,由 SDK 统一管理首次与运行时显隐;
  • E_AUTH_EXPIRED 后自动调用 resolveSource,由业务服务返回完整新源或拒绝;
  • 完整 PlayerEvent 进入单个 TelemetrySession,由宿主有界队列批量发送;
  • 已握手 iframe 运行时失联时,内置按钮调用同一 performErrorAction(),由组件销毁旧连接并重建 iframe;不会对失联句柄反复发送命令。
<script setup lang="ts">
import { onBeforeUnmount, ref } from 'vue'
import {
  VideoPlayerFrame,
  type MediaSource,
  type PlayerError,
  type ResolveSource,
  type VideoPlayerFrameExpose,
} from '@video-lab/vue-frame'
import { createTelemetrySession } from '@video-lab/telemetry'

const BUILD_SHA = 'web-2026.09.16'
const playerOrigin = 'https://player.example.internal'
const player = ref<VideoPlayerFrameExpose | null>(null)

const source: MediaSource = {
  url: 'https://media.example.com/live/master.m3u8',
  type: 'hls',
  live: true,
}

async function requestAuthorizedSource(signal: AbortSignal): Promise<MediaSource | null> {
  if (signal.aborted) return null
  return {
    url: 'https://media.example.com/live/refreshed-master.m3u8',
    type: 'hls',
    live: true,
  }
}

const resolveSource: ResolveSource = ({ signal }) => requestAuthorizedSource(signal)

const telemetry = createTelemetrySession({
  application: { name: 'live-web', release: BUILD_SHA },
  sink: {
    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
}) {
  // 宿主业务可记录最终状态;SDK 默认按钮会按最新状态选择可执行动作。
  if (!payload.playable && !payload.recoverable) console.info('播放器终态', payload.reason)
}

function onError(error: PlayerError) {
  // E_AUTH_EXPIRED 已交给 resolveSource,绝不在这里重连旧 URL。
  if (error.code === 'E_AUTH_EXPIRED') return
  if (error.code === 'E_FRAME_CRASHED') console.info('iframe 已失联', error.code)
}

async function performErrorAction() {
  try {
    await player.value?.performErrorAction()
  } catch (error) {
    console.warn('错误动作未被接受', error)
  }
}

onBeforeUnmount(() => telemetry.finish({ reason: 'destroyed' }))
</script>

<template>
  <section>
    <VideoPlayerFrame
      ref="player"
      :source="source"
      :origin="playerOrigin"
      :resolve-source="resolveSource"
      @playable-change="onPlayableChange"
      @error="onError"
      @player-event="telemetry.record"
    >
      <template #loading="{ text }">
        <p role="status">{{ text }}</p>
      </template>
    </VideoPlayerFrame>
  </section>
</template>

示例使用已本地化的 Loading 文案和 SDK 默认错误按钮。自建业务按钮可调用上面的 performErrorAction();返回 started、busy 或 unavailable,动作真正失败时 Promise 拒绝。showErrorOverlay=false 会隐藏默认按钮,但不影响事件与自建动作。

enqueueTelemetry 代表宿主传输边界:必须同步、快速、有界;网络只在队列的后台 flush 中批量完成。 它至少要有队列上限、batch 上限、有限重试、并发上限、优先级丢弃和服务端幂等。完整策略见 @video-lab/telemetry 安装包 README 的“高频与网络安全宿主必须做的事”。

iframe 特有生命周期:看见什么,应该怎么处理

阶段或故障 宿主收到的事实 用户界面与动作 排查方向
SSR / 尚未 mounted #ssr-placeholder,尚未创建 iframe 显示品牌骨架或封面;不要调用 ref 命令 页面渲染生命周期
iframe 正在加载 / 握手 playable-change { reason: 'frame_disconnected', recoverable: true } 仍是可恢复启动,显示 Loading,不显示“已崩溃” origin、网络、artifact 首次加载
加载或握手超时 E_LOAD_FAILED / E_HANDSHAKE_TIMEOUT 后 frame_disconnected + recoverable=false 默认按钮或 performErrorAction() 手动重建 iframe 部署根、iframe URL、网络
CSP 阻断或主版本不兼容 E_ENV_CSP_BLOCKED / E_HANDSHAKE_VERSION_MISMATCH 后 frame_disconnected + recoverable=false 无无效的重建按钮;修正部署环境或版本 CSP、host / artifact 版本
已握手后 iframe 运行时失联 E_FRAME_CRASHED,并有 frame_disconnected + recoverable=false 默认按钮或 performErrorAction() 手动重建;不复用旧 RPC 内存、后台恢复、iframe 运行时异常
普通媒体网络 / 解码错误 error、recovery、playable-change 仍按统一 playable 状态显示 Loading、重试或换源 CDN、媒体、内核、授权;不要误报为 iframe 崩溃
命令被拒绝 / 超时 ref 方法 Promise rejection await + catch,同时继续订阅事件流 连接、版本、参数或命令时机

初始握手与运行时崩溃都可能出现 frame_disconnected,唯一可靠区分是 recoverable:true 表示仍在 建立或恢复,false 才是宿主要停止等待并提供重建入口的终态。静态 iframe URL 拿不到这条状态,必须由 嵌入页面自己监听标签 load / error。

场景到 API:应监听什么,宿主做什么

场景 首选 API 宿主动作 不要做
首屏封面 + 品牌 Loading poster + #loading 宿主 DOM 从首次渲染显示到 firstframe,覆盖 iframe 初始化空档 用 ready / play 提前撤封面,或在 iframe 内等待品牌 UI
运行时卡顿 / 自动恢复 #loading SDK 在可恢复等待时再次显示同一视觉 从 waiting、playing、原始错误自行拼优先级
403 / 签名失效 resolveSource + @player-event 服务端返回完整新源;拒绝时进入业务终态 向 iframe 传业务 header 或无限重连旧 URL
不可恢复媒体错误 action + @error 按 retry / play / replace-source 呈现 CTA 将所有 error 都判为 iframe 崩溃
iframe 加载失败 / 运行时崩溃 frame_disconnected + 对应错误码 默认按钮或 performErrorAction() 手动重建 在已失联句柄上循环调用命令
QoE / 错误 / 恢复归因 @player-event → telemetry.record 每次组件会话一个 telemetry session 将 @time-update 逐条发 HTTP
清晰度、字幕菜单 @ready + setQuality / setSubtitle 按本次 ready 能力生成菜单,以 change 事件回显 假设所有源有可切档位 / 字幕
自定义业务按钮 telemetry.recordIntent 在实际点击 / API 调用处写入受限 intent 上传用户 ID、自由 tags、token 或正文

Props:创建 iframe 与播放器时能决定什么

多数 props 是初始化配置。默认 Loading 与错误层的显隐、文案、按钮色可在运行时更新;controlVisibility 等播放器构造配置要改变时仍需更换组件 :key。

分组 Props 作用与注意点
iframe 部署 origin、iframeVersion、debug origin 是 artifact 部署根,不是媒体 URL;iframeVersion 固定版本目录且必须是 v<semver>;debug 输出 Penpal 握手 / 消息日志,只用于排障。
媒体与授权 source、resolveSource source 必填;resolveSource 仅在 E_AUTH_EXPIRED 后由宿主执行,返回新完整源或 null。函数不会跨 postMessage。若不提供,宿主须在 @playable-change 的 action='replace-source' 时展示业务入口并取得新源,否则用户会停在没有重试按钮的授权错误态。
起播行为 autoplay、muted、loop、playsinline、preload、startTime startTime、preload 是创建期语义,不能用挂载后 seek() 完整替代;预览自动播通常同时 autoplay muted。
初始媒体表现 volume、playbackRate、poster、pauseImage volume 取 0–1,playbackRate 取 0.25–4;封面与暂停图可传 URL 或配置对象。
SDK UI controls、controlVisibility、showDefaultLoadingOverlay、showErrorOverlay、interactive controls=false 关掉内置控件;可关闭 overlay 交给宿主品牌 UI,但仍要消费状态事件。
功能扩展 preset、locale、danmaku、pageFullscreen preset 只提供默认值,未显式传布尔 prop 才会生效;字幕/弹幕数据、网页全屏布局与业务 CTA 均由宿主实现。

source 对象可表达 type、live、候选协议和字幕;完整形状以 MediaSource 为准。候选源表示同一内容的媒体封装, 不能拿它表达会员清晰度;权益应由服务端签发对应播放源。

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;其顶层 delivery 带 producer 证据,可用于 QoE。旧 iframe 的事件可缺少该字段。PlayerEvent 用 event 字段区分类型(如 e.event === 'ready'),数据在 payload;它没有 type 字段。

事件组 事件 什么时候用
完整事实流 player-event PlayerEvent.delivery 带 ID、序号与发生时间,适合排序、去重和迟到识别。recovery、sourceswitch 只在完整流中出现。
基础播放 ready、play、pause、ended、time-update、volume-change 渲染控制栏与业务播放意图。ready 给 quality 与可选 subtitles;不逐条上传 time-update。
用户操作结果 seeking、seeked、quality-change、subtitle-change、user-action 更新进度跳转、菜单回显与 SDK 内部控件动作;宿主自定义按钮另写 recordIntent()。
瞬时媒体信号 waiting、playing、stalled 前两者可做局部即时反馈;stalled 是带时长的已测卡顿,适合遥测,不取代聚合 UI 结论。
用户可见结论 playable-change、autoplay-blocked、error UI 只消费 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 仍可播放,不能盖住画面; action='play' 要用户手势,action='replace-source' 要宿主重新取源。iframe 额外的 frame_disconnected 行为见前面的生命周期表。

Template ref:命令、返回值与正确时机

iframe 下行命令都跨 postMessage,因此返回 Promise,包含 pause()、seek() 与 setVolume()。宿主主动调用 ref 命令时,每次调用都要有失败处理路径;可以在业务封装层集中 await + catch,不必在每个按钮里重复写。命令 Promise 成功不等于媒体已播放或恢复成功,仍要等待事件。

目的 方法 使用方式
基础播放与恢复 play()、pause()、seek()、setMuted()、setVolume()、setPlaybackRate()、retry()、performErrorAction() 均 await;performErrorAction() 按当前终态选择播放、重试、换源或手动重建,返回 started / busy / unavailable;恢复成功以后续 recovery / playable-change 为准。
全屏 enterFullscreen()、exitFullscreen()、setPageFullscreen(active) 均 await;网页全屏还需要稳定的 pageFullscreen 宿主布局适配器。
功能菜单 `setQuality(level 'auto')、setSubtitle(id
弹幕 pushDanmaku()、setDanmakuEnabled()、clearDanmaku() 只管渲染;审核、连接、发送、限流和留存属于宿主 / 服务端。
查询与销毁 getCurrentTime()、getDuration()、getPlaybackContext()、getPlayerHandle()、destroy() 前四个是宿主缓存读数;getPlaybackContext() 未连上或未收到上下文时为 null,位置精度约为最近一次事件。普通 Vue 卸载自动清理,显式 destroy 用于手工收口。

命令 rejection 与 @error 独立:一个不保证导致另一个。失联后的旧句柄不能作为恢复方案;可点默认按钮或调用 performErrorAction() 手动重建。

业务封装层可以统一处理命令失败,再按业务需要提示用户或记录诊断。不要只写 void player.value?.play():void 不会处理 Promise rejection。下面的处理器只用于宿主主动发起的命令; SDK 因 props 变化而发起的命令由组件内部处理。

<script setup lang="ts">
import { ref } from 'vue'
import { VideoPlayerFrame, type VideoPlayerFrameExpose } from '@video-lab/vue-frame'

const player = ref<VideoPlayerFrameExpose | null>(null)
const commandError = ref('')
const commandsReady = ref(false)
const canRetry = ref(false)

function onPlayableChange(state: {
  reason: string
  playable: boolean
  recoverable: boolean
  action?: string
}) {
  if (state.reason === 'frame_disconnected') commandsReady.value = false
  canRetry.value = !state.playable && !state.recoverable && state.action === 'retry'
}

async function runCommand(command: (handle: VideoPlayerFrameExpose) => Promise<unknown>) {
  const handle = player.value
  if (!handle || !commandsReady.value) {
    commandError.value = '播放器尚未就绪'
    return
  }

  commandError.value = ''
  try {
    await command(handle)
  } catch {
    // 宿主可在这里接入脱敏诊断;不要把命令失败重复计为播放期 error。
    commandError.value = '操作未完成,请稍后重试'
  }
}

const play = () => runCommand((handle) => handle.play())
const retry = () => runCommand((handle) => handle.retry())
</script>

<template>
  <VideoPlayerFrame
    ref="player"
    source="https://media.example.com/lesson.m3u8"
    origin="https://player.example.internal"
    @ready="commandsReady = true"
    @playable-change="onPlayableChange"
  />
  <button type="button" :disabled="!commandsReady" @click="play">播放</button>
  <button v-if="canRetry" type="button" :disabled="!commandsReady" @click="retry">重试</button>
  <p v-if="commandError" role="alert">{{ commandError }}</p>
</template>

当前实现中,组件 ref 已挂载但内部连接尚未建立时,play() 等多数方法会返回已成功的 Promise 并且 不执行命令;setPageFullscreen() 则会拒绝。不要把 Promise resolve 当成“已经连接”或“已经出画面”, 应等 @ready 再开放播放控制,并继续以 @playable-change 判断播放状态。

生命周期、SSR 与边界

  • iframe 仅在 mounted 后创建;SSR 使用 #ssr-placeholder,无需额外 <ClientOnly>。
  • 组件卸载会销毁 iframe 与桥接;页面/路由离开时还应 telemetry.finish({ reason: 'destroyed' }),产出 QoE summary。
  • 提供 #loading 会自动替换 iframe 内默认 Loading;只有把 Loading DOM 完全放在组件外时才设置 :show-default-loading-overlay="false"。错误 CTA 仍应消费 @playable-change、@error 与 iframe 失联状态。
  • 不直接依赖 @video-lab/player-core、@video-lab/player-ui 或 xgplayer。getPlayerHandle() 是高级逃生舱,不是常规业务 API。
  • 不支持 DASH 或 PlayReady。若只需要静态 URL,请选 iframe + helper,不要私发未公开的 postMessage 命令。

验收清单与继续阅读

  • origin 来自受控部署配置,真实验证过 CSP、artifact URL 与 iframeVersion。
  • SSR 使用 #ssr-placeholder;默认 Loading 或自定义 #loading 覆盖首次/运行时;终态使用默认错误按钮或 performErrorAction()。
  • 403 返回完整新源或业务 CTA,不会无限重试旧 URL。
  • 宿主主动调用的每个 ref 命令都有失败处理路径,可在业务封装层集中 await + catch;失联后使用手动重建动作,不循环调用旧 RPC。
  • QoE 走 player-event、单会话 TelemetrySession 与有界传输队列。
  • 真实验收弱网、自动播放受阻、无字幕 / 无档位、初始握手失败、iframe 后台恢复 / 崩溃和路由卸载。

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

Poster 上叠宿主侧自定义 Loading

Frame 的 #loading 在宿主 DOM 中渲染,可以覆盖 iframe 创建、脚本加载和握手空档:

#poster 与 #pause 也在宿主 DOM 中渲染,由 SDK 管首帧、换源、明确暂停及播放后的显隐。内置控件或公开 pause() 对应的实际暂停会显示暂停画面,被动暂停不会显示。提供插槽后,同类内置图片不会传入 iframe;#pause 内的按钮调用组件 ref 的 play()。不传 poster / pauseImage 即不使用;null 仅用于清除 Provider 默认图片。

<VideoPlayerFrame
  :source="selectedRoom.source"
  :poster="{ url: selectedRoom.videoPoster, fit: 'cover', loading: 'eager' }"
>
  <template #loading="{ phase, reason }">
    <BusinessLoading transparent :phase="phase" :reason="reason" />
  </template>
</VideoPlayerFrame>

initial 从宿主首次渲染保持到 firstframe,runtime 表示首帧后的可恢复等待。提供 slot 后会 自动关闭 iframe 内默认 Loading,无需再传 :show-default-loading-overlay="false"。slot 是否存在 属于构造期策略;要切换实现时重新挂载组件。

带业务流程的启动占位由宿主维护,且只能出现一次

普通 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

Video Lab Player Vue 3 inline 组件:直连 player-core 的高性能接入方式

2,152
@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