Appearance
BizMediaPlayer 媒体播放器
原生 <audio> / <video> 统一封装的媒体播放器(零第三方依赖),type 区分音频 / 视频:
- video:点击画面播放 / 暂停(含中央播放按钮)、自绘控件区(进度拖拽含缓冲进度、倍速、静音、全屏 Fullscreen API)、封面
poster、断点续播initial-time; - audio standard:播放 / 暂停、进度拖拽(含缓冲进度)、时长显示、倍速、循环、音量控制;
- audio compact:一行迷你控件(IM 语音消息形态,
mode="compact",仅 audio 合法——video 传 compact 控制台警告并回落 standard); - 通用:倍速档位(0.5x~2x,可自定义
rates)、loop循环、muted静音、volume音量、断点续播(timeupdate事件由业务存取后经initial-time传入)、加载失败「失败提示 + 重试」兜底 UI。
适用于培训视频、监控回放、宣传位视频、客服语音留言回放、录音证据回放、IM 语音消息(与 BizChat 互补)——上传归 BizUploadVideo / BizUploadFile,播放归本组件。
文档示例音视频由 WebAudio / canvas + MediaRecorder 本地合成(免外网资源),实际使用传业务媒体地址即可。
视频基础用法
点击画面或中央按钮播放/暂停;底部控件条支持进度拖拽(浅色为缓冲进度)、倍速、静音与全屏。
标准视频播放:封面、点击画面播放、进度拖拽(含缓冲)、倍速、静音、全屏。
<script setup lang="ts">
/** 场景:培训视频基础播放——点击画面播放/暂停、进度拖拽(含缓冲)、倍速、静音、全屏 */
import { onMounted, onUnmounted, ref } from 'vue'
import { BizMediaPlayer } from 'vue3-ep-biz-lib'
import { makePosterUrl, makeVideoUrl } from './make-video'
const src = ref('')
const poster = ref('')
onMounted(async () => {
;[src.value, poster.value] = await Promise.all([makeVideoUrl(4), Promise.resolve(makePosterUrl())])
})
onUnmounted(() => {
if (src.value) URL.revokeObjectURL(src.value)
})
</script>
<template>
<div style="max-width: 560px">
<BizMediaPlayer v-if="src" type="video" :src="src" :poster="poster" />
<p style="margin-top: 10px; font-size: 13px; color: var(--el-text-color-secondary)">
点击画面或中央按钮播放/暂停;底部控件条支持进度拖拽(浅色为缓冲进度)、倍速、静音与全屏。
</p>
</div>
</template>
显示代码
视频封面与断点续播
上次进度 0.0s(播放后经 timeupdate 实时写入 sessionStorage,当前 0.0s)——刷新页面后 initial-time 从上次位置续播。
poster 封面;initial-time 指定起始位置,timeupdate 实时记录进度(sessionStorage),重开从上次位置续播。<script setup lang="ts">
/** 场景:视频封面 + 断点续播——timeupdate 记录进度,重开从上次位置继续 */
import { onMounted, onUnmounted, ref } from 'vue'
import { ElButton, ElMessage } from 'element-plus'
import { BizMediaPlayer } from 'vue3-ep-biz-lib'
import { makePosterUrl, makeVideoUrl } from './make-video'
const STORE_KEY = 'biz-media-player-demo-progress'
const src = ref('')
const poster = ref('')
const session = ref(0)
const resumeAt = ref(0)
const progress = ref(0)
onMounted(async () => {
const saved = Number(window.sessionStorage.getItem(STORE_KEY) ?? 0)
resumeAt.value = Number.isFinite(saved) ? saved : 0
;[src.value, poster.value] = await Promise.all([makeVideoUrl(5), Promise.resolve(makePosterUrl())])
})
onUnmounted(() => {
if (src.value) URL.revokeObjectURL(src.value)
})
const onTimeupdate = (t: number): void => {
progress.value = t
window.sessionStorage.setItem(STORE_KEY, String(t))
}
const restart = async (): Promise<void> => {
window.sessionStorage.setItem(STORE_KEY, '0')
resumeAt.value = 0
session.value += 1
src.value = await makeVideoUrl(5)
ElMessage.info('已重置进度并重新生成演示视频')
}
</script>
<template>
<div style="max-width: 560px">
<BizMediaPlayer
v-if="src"
:key="session"
type="video"
:src="src"
:poster="poster"
:initial-time="resumeAt"
@timeupdate="onTimeupdate"
/>
<p style="margin-top: 10px; font-size: 13px; color: var(--el-text-color-secondary)">
上次进度 {{ resumeAt.toFixed(1) }}s(播放后经 timeupdate 实时写入 sessionStorage,当前
{{ progress.toFixed(1) }}s)——刷新页面后 initial-time 从上次位置续播。
</p>
<ElButton size="small" @click="restart">重置进度重新生成</ElButton>
</div>
</template>
显示代码
培训课程
课程章节列表 + 播放器联动(key 重建实例),
ended 联动提示。<script setup lang="ts">
/** 场景:培训课程——章节列表 + 播放器联动(key 重建实例,ended 联动提示) */
import { onMounted, onUnmounted, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { BizMediaPlayer } from 'vue3-ep-biz-lib'
import { makePosterUrl, makeVideoUrl } from './make-video'
interface Course {
id: number
name: string
duration: string
src: string
}
const courses = ref<Course[]>([])
const current = ref(1)
const poster = ref('')
onMounted(async () => {
poster.value = makePosterUrl()
const [a, b] = await Promise.all([makeVideoUrl(3), makeVideoUrl(5)])
courses.value = [
{ id: 1, name: '第 1 课:平台概览', duration: '3s 演示片', src: a ?? '' },
{ id: 2, name: '第 2 课:核心功能', duration: '5s 演示片', src: b ?? '' },
]
})
onUnmounted(() => {
courses.value.forEach((c) => URL.revokeObjectURL(c.src))
})
</script>
<template>
<div style="max-width: 560px">
<div
v-for="course in courses"
:key="course.id"
:style="{
padding: '8px 12px',
marginBottom: '6px',
borderRadius: '6px',
cursor: 'pointer',
fontSize: '13px',
color: current === course.id ? 'var(--el-color-primary)' : 'var(--el-text-color-regular)',
background:
current === course.id ? 'var(--el-color-primary-light-9)' : 'var(--el-fill-color-lighter)',
}"
@click="current = course.id"
>
{{ course.name }}
<span style="float: right; font-size: 12px; color: var(--el-text-color-secondary)">
{{ course.duration }}
</span>
</div>
<BizMediaPlayer
v-if="courses.length"
:key="current"
type="video"
:src="courses.find((c) => c.id === current)?.src"
:poster="poster"
@ended="ElMessage.success('本课播放完毕,可切换下一课')"
/>
</div>
</template>
显示代码
静音循环(宣传位)
muted + loop + autoplay:首页宣传位 / 大屏背景视频形态(浏览器策略下静音才允许自动播放)。
muted + loop + autoplay 宣传位背景视频形态(浏览器策略下静音才允许自动播)。<script setup lang="ts">
/** 场景:宣传位背景视频——muted + loop + autoplay 静音循环自动播放 */
import { onMounted, onUnmounted, ref } from 'vue'
import { BizMediaPlayer } from 'vue3-ep-biz-lib'
import { makeVideoUrl } from './make-video'
const src = ref('')
onMounted(async () => {
src.value = await makeVideoUrl(4)
})
onUnmounted(() => {
if (src.value) URL.revokeObjectURL(src.value)
})
</script>
<template>
<div style="max-width: 560px">
<BizMediaPlayer
v-if="src"
type="video"
:src="src"
muted
loop
autoplay
:height="240"
:show-rate="false"
:show-mute="false"
/>
<p style="margin-top: 10px; font-size: 13px; color: var(--el-text-color-secondary)">
muted + loop + autoplay:首页宣传位 / 大屏背景视频形态(浏览器策略下静音才允许自动播放)。
</p>
</div>
</template>
显示代码
监控回放网格
固定高度小窗 + 精简控件(关闭倍速 / 静音 / 全屏),多路网格布局。
<script setup lang="ts">
/** 场景:监控回放网格——多路摄像头小窗,固定高度 + 精简控件(关闭倍速/全屏/静音) */
import { onMounted, onUnmounted, ref } from 'vue'
import { BizMediaPlayer } from 'vue3-ep-biz-lib'
import { makeVideoUrl } from './make-video'
interface Channel {
id: number
name: string
src: string
}
const channels = ref<Channel[]>([])
onMounted(async () => {
const srcs = await Promise.all([makeVideoUrl(3), makeVideoUrl(3), makeVideoUrl(3), makeVideoUrl(3)])
const names = ['东门 01', '西门 02', '南门 03', '北门 04']
channels.value = srcs.map((src, i) => ({ id: i + 1, name: names[i] ?? `通道 ${i + 1}`, src }))
})
onUnmounted(() => {
channels.value.forEach((c) => URL.revokeObjectURL(c.src))
})
</script>
<template>
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; max-width: 680px">
<div v-for="ch in channels" :key="ch.id">
<div style="font-size: 13px; margin-bottom: 6px; color: var(--el-text-color-regular)">
{{ ch.name }} · 回放 09:30-09:33
</div>
<BizMediaPlayer
type="video"
:src="ch.src"
:height="150"
:show-rate="false"
:show-mute="false"
:show-fullscreen="false"
/>
</div>
</div>
</template>
显示代码
音频标准播放
type="audio" 标准模式:播放/暂停、进度拖拽(含缓冲)、时长、倍速、循环、音量一应俱全。
type="audio" 标准模式:播放 / 暂停、进度拖拽(含缓冲)、时长、倍速、循环、音量。<script setup lang="ts">
/** 场景:客服来电录音回放——音频标准模式(进度拖拽/时长/倍速/循环/音量) */
import { onMounted, onUnmounted, ref } from 'vue'
import { BizMediaPlayer } from 'vue3-ep-biz-lib'
import { makeToneUrl } from './make-tone'
const src = ref('')
onMounted(async () => {
src.value = await makeToneUrl(6, 440)
})
onUnmounted(() => {
if (src.value) URL.revokeObjectURL(src.value)
})
</script>
<template>
<div style="max-width: 460px">
<BizMediaPlayer v-if="src" type="audio" :src="src" title="客服来电录音-20260930.mp3" />
<p style="margin-top: 10px; font-size: 13px; color: var(--el-text-color-secondary)">
type="audio" 标准模式:播放/暂停、进度拖拽(含缓冲)、时长、倍速、循环、音量一应俱全。
</p>
</div>
</template>
显示代码
IM 语音消息(compact)
播完自动连播下一条(ended 事件驱动)
mode="compact" 行内迷你控件,IM 会话左右气泡形态;ended 自动连播下一条。<script setup lang="ts">
/** 场景:IM 会话语音消息——compact 迷你控件(气泡形态),ended 自动连播下一条 */
import { onMounted, onUnmounted, ref } from 'vue'
import { ElMessage, ElSwitch } from 'element-plus'
import { BizMediaPlayer } from 'vue3-ep-biz-lib'
import { makeToneUrl } from './make-tone'
interface VoiceMessage {
id: number
side: 'left' | 'right'
label: string
seconds: string
src: string
}
const messages = ref<VoiceMessage[]>([])
const current = ref(0)
const autoNext = ref(true)
onMounted(async () => {
const srcs = await Promise.all([makeToneUrl(8, 392), makeToneUrl(3, 523), makeToneUrl(5, 349)])
messages.value = [
{ id: 1, side: 'left', label: '客户', seconds: '8"', src: srcs[0] ?? '' },
{ id: 2, side: 'right', label: '我', seconds: '3"', src: srcs[1] ?? '' },
{ id: 3, side: 'left', label: '客户', seconds: '5"', src: srcs[2] ?? '' },
]
})
onUnmounted(() => {
messages.value.forEach((m) => URL.revokeObjectURL(m.src))
})
const onEnded = (): void => {
if (!autoNext.value) return
if (current.value < messages.value.length - 1) current.value += 1
else ElMessage.success('语音已全部播放完毕')
}
</script>
<template>
<div style="max-width: 460px">
<div
style="
display: flex;
flex-direction: column;
gap: 12px;
padding: 14px;
border-radius: 8px;
background: var(--el-fill-color-lighter);
"
>
<template v-for="(m, i) in messages" :key="m.id">
<div
:style="{
display: 'flex',
alignItems: 'center',
gap: '10px',
justifyContent: m.side === 'left' ? 'flex-start' : 'flex-end',
flexDirection: m.side === 'left' ? 'row' : 'row-reverse',
}"
>
<span style="font-size: 12px; color: var(--el-text-color-secondary)">{{ m.label }}</span>
<span
v-if="current !== i"
:style="{
display: 'inline-flex',
alignItems: 'center',
gap: '6px',
padding: '8px 12px',
borderRadius: '16px',
fontSize: '13px',
cursor: 'pointer',
background:
m.side === 'left' ? 'var(--el-bg-color)' : 'var(--el-color-primary-light-9)',
color: 'var(--el-text-color-regular)',
}"
@click="current = i"
>
{{ m.seconds }} 语音消息(点击播放)
</span>
<BizMediaPlayer
v-else
type="audio"
mode="compact"
:src="m.src"
@ended="onEnded"
/>
<span style="font-size: 12px; color: var(--el-text-color-secondary)">{{ m.seconds }}</span>
</div>
</template>
</div>
<div style="margin-top: 10px; display: flex; align-items: center; gap: 8px">
<ElSwitch v-model="autoNext" size="small" />
<span style="font-size: 13px; color: var(--el-text-color-secondary)">
播完自动连播下一条(ended 事件驱动)
</span>
</div>
</div>
</template>
显示代码
倍速与循环控制
当前倍速 1x loop 循环播放 + 倍速菜单降至 0.5x 慢速精听;rates 自定义档位(0.5 / 0.8 / 1 / 1.2 / 1.5)
loop 循环 + 自定义 rates 倍速档位组合(慢速精听场景),rate-change 联动展示。<script setup lang="ts">
/** 场景:外语听写训练——循环精听 + 倍速控制(自定义倍速档位 + rate-change 联动) */
import { onMounted, onUnmounted, ref } from 'vue'
import { ElTag } from 'element-plus'
import { BizMediaPlayer } from 'vue3-ep-biz-lib'
import { makeToneUrl } from './make-tone'
const src = ref('')
const rate = ref(1)
onMounted(async () => {
src.value = await makeToneUrl(7, 330)
})
onUnmounted(() => {
if (src.value) URL.revokeObjectURL(src.value)
})
</script>
<template>
<div style="max-width: 460px">
<BizMediaPlayer
v-if="src"
type="audio"
:src="src"
title="听力材料 Lesson-12(循环精听)"
loop
:rates="[0.5, 0.8, 1, 1.2, 1.5]"
@rate-change="rate = $event"
/>
<div style="margin-top: 10px; display: flex; align-items: center; gap: 8px">
<ElTag size="small" type="info">当前倍速 {{ rate }}x</ElTag>
<span style="font-size: 13px; color: var(--el-text-color-secondary)">
loop 循环播放 + 倍速菜单降至 0.5x 慢速精听;rates 自定义档位(0.5 / 0.8 / 1 / 1.2 / 1.5)
</span>
</div>
</div>
</template>
显示代码
事件日志
00:00 / 00:00
操作播放器查看事件(timeupdate 已抽样展示,实际逐帧触发,适合断点续播存进度)…
全部事件外抛面板(
play / pause / ended / error / loaded / timeupdate / rate-change)。<script setup lang="ts">
/** 场景:事件日志——全部事件外抛面板(play/pause/ended/error/loaded/timeupdate/rate-change) */
import { onMounted, onUnmounted, ref } from 'vue'
import { ElButton, ElTag } from 'element-plus'
import { BizMediaPlayer } from 'vue3-ep-biz-lib'
import type { BizMediaPlayerInstance } from 'vue3-ep-biz-lib'
import { makeVideoUrl } from './make-video'
const src = ref('')
const logs = ref<string[]>([])
const playerRef = ref<BizMediaPlayerInstance>()
const log = (text: string): void => {
logs.value = [`${new Date().toLocaleTimeString()} ${text}`, ...logs.value].slice(0, 8)
}
onMounted(async () => {
src.value = await makeVideoUrl(4)
})
onUnmounted(() => {
if (src.value) URL.revokeObjectURL(src.value)
})
</script>
<template>
<div style="max-width: 560px">
<BizMediaPlayer
ref="playerRef"
type="video"
:src="src"
:show-rate="true"
@play="log('play:开始播放')"
@pause="log('pause:已暂停')"
@ended="log('ended:播放结束')"
@loaded="(d: number) => log(`loaded:元数据就绪,时长 ${d.toFixed(1)}s`)"
@timeupdate="(t: number) => t % 1 < 0.05 && log(`timeupdate:${t.toFixed(1)}s`)"
@rate-change="(r: number) => log(`rate-change:倍速切换为 ${r}x`)"
@error="log('error:加载失败')"
/>
<div style="margin-top: 10px; display: flex; gap: 8px">
<ElButton size="small" @click="playerRef?.toggle()">播放 / 暂停</ElButton>
<ElButton size="small" @click="playerRef?.setRate(2)">2x</ElButton>
<ElButton size="small" @click="playerRef?.seek(2)">跳到 2s</ElButton>
<ElButton size="small" @click="logs = []">清空日志</ElButton>
</div>
<div style="margin-top: 10px; display: flex; flex-direction: column; gap: 4px">
<ElTag v-for="item in logs" :key="item" size="small" type="info">{{ item }}</ElTag>
<span v-if="logs.length === 0" style="font-size: 13px; color: var(--el-text-color-secondary)">
操作播放器查看事件(timeupdate 已抽样展示,实际逐帧触发,适合断点续播存进度)…
</span>
</div>
</div>
</template>
显示代码
错误兜底与重试
00:00 / 00:00
失败 0 次
演示地址为主线路(故意失效):失败时画面内出现「失败提示 + 重试」兜底 UI 并外抛 error 事件;业务侧可换源(改 src)后自动恢复加载。
加载失败时画面内出现「失败提示 + 重试」兜底 UI 并外抛
error;业务可换源(改 src)恢复。<script setup lang="ts">
/** 场景:错误兜底与重试——加载失败提示 + 组件内重试 + 业务换备用线路恢复 */
import { onMounted, onUnmounted, ref } from 'vue'
import { ElButton, ElMessage, ElTag } from 'element-plus'
import { BizMediaPlayer } from 'vue3-ep-biz-lib'
import type { BizMediaPlayerInstance } from 'vue3-ep-biz-lib'
import { makePosterUrl, makeVideoUrl } from './make-video'
const BROKEN_SRC = 'https://cdn.invalid/biz-demo/broken-video.mp4'
const poster = ref('')
/** 主线路(故意失效)→ 备用线路(本地生成) */
const src = ref(BROKEN_SRC)
const failCount = ref(0)
const playerRef = ref<BizMediaPlayerInstance>()
let backupSrc = ''
onMounted(() => {
poster.value = makePosterUrl()
})
onUnmounted(() => {
if (backupSrc) URL.revokeObjectURL(backupSrc)
})
const onError = (): void => {
failCount.value += 1
ElMessage.error(`媒体加载失败(第 ${failCount.value} 次),可点击画面上的「重试」`)
}
const switchBackup = async (): Promise<void> => {
if (!backupSrc) backupSrc = await makeVideoUrl(4)
src.value = backupSrc
ElMessage.success('已切换备用线路,正在重新加载')
}
</script>
<template>
<div style="max-width: 560px">
<BizMediaPlayer
ref="playerRef"
type="video"
:src="src"
:poster="poster"
@error="onError"
/>
<div style="margin-top: 10px; display: flex; align-items: center; gap: 8px">
<ElButton size="small" @click="playerRef?.retry()">组件内重试(重载当前地址)</ElButton>
<ElButton size="small" type="primary" plain @click="switchBackup">切换备用线路</ElButton>
<ElTag size="small" type="danger">失败 {{ failCount }} 次</ElTag>
</div>
<p style="margin-top: 10px; font-size: 13px; color: var(--el-text-color-secondary)">
演示地址为主线路(故意失效):失败时画面内出现「失败提示 + 重试」兜底 UI 并外抛 error
事件;业务侧可换源(改 src)后自动恢复加载。
</p>
</div>
</template>
显示代码
受控编程播放
证据音频 evidence_0930.wav 已存证00:00 / 00:00
时长(mediaRef.duration):0s进度:0.0s倍速:1x循环:关静音:关
Expose 驱动(
play / pause / toggle / seek / setRate / toggleLoop / toggleMute / mediaRef)+ title / extra 插槽扩展。<script setup lang="ts">
/** 场景:证据审查台——受控编程播放(Expose 驱动)+ 插槽扩展(title 存证标识 / extra 操作区) */
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { ElButton, ElMessage, ElTag } from 'element-plus'
import { Download, Flag } from '@element-plus/icons-vue'
import { BizMediaPlayer } from 'vue3-ep-biz-lib'
import type { BizMediaPlayerInstance } from 'vue3-ep-biz-lib'
import { makeToneUrl } from './make-tone'
const src = ref('')
const currentTime = ref(0)
const rate = ref(1)
const looping = ref(false)
const muted = ref(false)
const playerRef = ref<BizMediaPlayerInstance>()
onMounted(async () => {
src.value = await makeToneUrl(8, 294, 'triangle')
})
onUnmounted(() => {
if (src.value) URL.revokeObjectURL(src.value)
})
const durationText = computed(() => {
const d = playerRef.value?.mediaRef?.duration ?? 0
return Number.isFinite(d) ? `${d.toFixed(0)}s` : '--'
})
const onRateChange = (r: number): void => {
rate.value = r
}
const forward = (): void => {
const d = playerRef.value?.mediaRef?.duration ?? 8
const total = Number.isFinite(d) && d > 0 ? d : 8
const target = Math.min(currentTime.value + 5, total)
playerRef.value?.seek(target)
ElMessage.info(`已编程跳转到 ${target.toFixed(0)}s`)
}
const onToggleLoop = (): void => {
playerRef.value?.toggleLoop()
looping.value = playerRef.value?.mediaRef?.loop ?? false
}
const onToggleMute = (): void => {
playerRef.value?.toggleMute()
muted.value = playerRef.value?.mediaRef?.muted ?? false
}
</script>
<template>
<div style="max-width: 520px">
<BizMediaPlayer
ref="playerRef"
type="audio"
:src="src"
:show-loop="false"
:show-rate="false"
@timeupdate="currentTime = $event"
@rate-change="onRateChange"
>
<template #title>
<span
style="display: inline-flex; align-items: center; gap: 6px; font-size: 14px; font-weight: 500"
>
证据音频 evidence_0930.wav
<span style="font-size: 12px; font-weight: 400; color: var(--el-color-danger)">已存证</span>
</span>
</template>
<template #extra>
<ElButton size="small" :icon="Download" @click="ElMessage.info('下载音频(blob 已就绪)')">
下载
</ElButton>
<ElButton size="small" :icon="Flag" @click="ElMessage.warning('已提交举报')">举报</ElButton>
</template>
</BizMediaPlayer>
<div style="margin-top: 10px; display: flex; flex-wrap: wrap; gap: 8px; align-items: center">
<ElButton size="small" type="primary" @click="playerRef?.toggle()">播放 / 暂停</ElButton>
<ElButton size="small" @click="forward">快进 5s(seek)</ElButton>
<ElButton size="small" @click="playerRef?.setRate(0.5)">0.5x</ElButton>
<ElButton size="small" @click="playerRef?.setRate(2)">2x</ElButton>
<ElButton size="small" @click="onToggleLoop">循环</ElButton>
<ElButton size="small" @click="onToggleMute">静音</ElButton>
</div>
<div style="margin-top: 10px; display: flex; gap: 8px; flex-wrap: wrap">
<ElTag size="small" type="info">时长(mediaRef.duration):{{ durationText }}</ElTag>
<ElTag size="small" type="info">进度:{{ currentTime.toFixed(1) }}s</ElTag>
<ElTag size="small" type="info">倍速:{{ rate }}x</ElTag>
<ElTag size="small" :type="looping ? 'success' : 'info'">循环:{{ looping ? '开' : '关' }}</ElTag>
<ElTag size="small" :type="muted ? 'warning' : 'info'">静音:{{ muted ? '开' : '关' }}</ElTag>
</div>
</div>
</template>
显示代码
Props
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| type | 'audio' | 'video' | — | 媒体类型(必传) |
| src | string | '' | 媒体地址 |
| poster | string | '' | 封面图(仅 video 生效) |
| title | string | '音频' | 标题(audio 标准模式标题行) |
| mode | 'standard' | 'compact' | 'standard' | 展示形态;compact 仅 audio 合法,video 传 compact 警告并回落 standard |
| height | number | string | 16:9 自适应 | video 容器高度(px / %) |
| autoplay | boolean | false | 自动播放(受浏览器策略限制,建议 video 配合 muted) |
| muted | boolean | false | 静音 |
| loop | boolean | false | 循环播放 |
| preload | 'none' | 'metadata' | 'auto' | 'metadata' | 预加载策略 |
| volume | number | 1 | 默认音量 0~1 |
| initial-time | number | 0 | 起始播放位置(秒,断点续播由业务经 timeupdate 存取后传入) |
| rates | number[] | [0.5, 0.75, 1, 1.25, 1.5, 2] | 倍速档位列表 |
| show-rate | boolean | true | 显示倍速切换 |
| show-loop | boolean | true | 显示循环按钮(audio 标准模式) |
| show-volume | boolean | true | 显示音量控制(audio 标准模式) |
| show-mute | boolean | true | 显示静音按钮(video 控件区) |
| show-fullscreen | boolean | true | 显示全屏按钮(video 控件区) |
Events
| 事件名 | 参数 | 说明 |
|---|---|---|
| play | () | 开始播放 |
| pause | () | 暂停 |
| ended | () | 播放结束(连播场景切下一条) |
| error | (event: Event) | 加载 / 解码失败(兜底 UI 同步出现) |
| loaded | (duration: number) | 元数据就绪(拿到时长) |
| timeupdate | (currentTime: number) | 播放进度(断点续播由业务存取) |
| rate-change | (rate: number) | 倍速切换 |
Slots
| 插槽名 | 说明 |
|---|---|
| title | 标题区(audio 标准模式) |
| extra | 控件区右侧扩展(下载等操作按钮,audio 标准模式) |
Expose
| 名称 | 类型 | 说明 |
|---|---|---|
| play / pause / toggle | () => void | 播放控制 |
| seek | (seconds: number) => void | 跳转到指定秒 |
| setRate | (rate: number) => void | 设置倍速(外抛 rate-change) |
| toggleLoop | () => void | 循环开关切换 |
| toggleMute | () => void | 静音开关切换 |
| toggleFullscreen | () => void | 全屏切换(Fullscreen API,video) |
| retry | () => void | 重试加载(重新挂载媒体元素) |
| mediaRef | Ref<HTMLMediaElement | null> | 原生媒体元素引用(audio / video),可接 WebAudio、hls.js / flv.js 自行扩展 |
TIP
autoplay 受浏览器自动播放策略限制(需用户先交互,静音形态最稳);hls / flv 直播流场景经 mediaRef 接 hls.js / flv.js 自行扩展(库不强制依赖);需要频谱可视化等高级能力时经 mediaRef 接 WebAudio。