@video-lab/vue-frame
| Install | |
|---|---|
npm install @video-lab/vue-frame |
|
| Latest Version: | 4.1.5 |
| Vue: | 3 |
| License: | MIT |
| Last Updated: | Sep 30, 2026 |
| Links: | Repository · npm |
@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,不内嵌第二份运行时。
宿主接入前要准备什么
- 已授权且 CORS 可访问的媒体
source;SDK 不接收内容 ID、业务 headers 或 token。 - iframe artifact 的受控部署根地址。显式传
origin,或在构建环境配置VITE_VIDEO_LAB_EMBED_ORIGIN;两者都没有会在初始化失败。 - 一份部署兼容策略:需要固定 artifact 时传
iframeVersion(格式为v<semver>),并验证 host 与 iframe 的协议主版本一致。 - 宿主可按品牌自定义 Loading、终态 CTA 和遥测传输队列;默认 Loading、错误层及可执行按钮已内置。
- 若签名 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 后台恢复 / 崩溃和路由卸载。
深入场景、错误码、部署、换源和恢复口径见:
- 五种模式与起步
- 业务场景与功能配方
- 离线 API、错误与恢复语义
- 网页全屏:见包内 API 参考的“网页全屏”一节
- 遥测:安装
@video-lab/telemetry后读该包 README
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
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 |