更新记录
1.2.1(2026-09-18)
- 【鸿蒙 seek 防抖】
seekTo指令立即把 UI 进度定位到目标位置,并设 1.2s 保护窗口(seekGuardMs/seekTargetMs)忽略原生回传的旧位置,消除拖动后进度条回跳抖动 - 【鸿蒙 overlay slot】模板新增
<slot>自定义覆盖层,支持第三方 overlay UI 挂载,与 Web 端对齐 - 【安卓挂载层置顶】
bringCustomLayerToFront增加重复挂载检测:已是子视图时先removeView再addView,确保自定义层始终位于最前 - 【iOS 编译依赖】
DPlayerEngine.swift补充import DCloudUTSFoundation,修复编译期模块解析失败 - 【插件类型】package.json
dcloudext.type由uts-vue-component更正为component-uts - 【鸿蒙进度条样式】拖动滑块 thumb 位置微调(
top: -4px→-3px) - 增加 自定义视频上层 内容 如 控制栏,弹幕,字幕等
1.1.0(2026-09-13)
- 【Web 支持】新增 Web 平台实现(
utssdk/web/index.vue):采用 uni-h5 内置<video>组件,解决 Web 端此前渲染为空(easycom 回退到空占位组件)的问题 - 【Web 接口对齐】props、emits、
command#key指令协议与原生平台对齐(play/pause/stop/seekTo/setVolume/setPlaybackRate),事件载荷统一为Map<string, any> - 【Web 控制能力】支持播放/暂停/停止/跳转/静音/倍速(含 0.5x/1.25x/2x,uni-h5 对非预设倍率回退 1x)、资源切换、全屏(浏览器原生控器)
- 【Web 进度同步】
timeupdate驱动 progress/percent,引入 seek 保护窗口(1.2s ±500ms)避免拖动后进度回跳 - 【Web 直播】直播流加载失败自动重连(最多 3 次,触发 livereconnect);
1.0.9(2026-09-13)
- 【iOS 崩溃修复】修复真机启动闪退问题:
resourcesprop 编译为[Any?]无法 KVC 桥接,改用PropType<Array<any>>生成Array<Any>,修复viewDidLoad反射崩溃 - 【iOS 加载动画】修复 loading 图标位于左上角的问题,
layoutControls()中按intrinsicContentSize居中显示 - 【iOS 字体图标】修复打开新播放页面后字体图标不显示的问题,
loadIconFont()增加对已注册字体(fontNames(forFamilyName:))的判断 - 【鸿蒙多页面】修复第一个页面播放控制失效的问题:新增
onHide/onShow页面生命周期处理,页面隐藏自动暂停、返回自动恢复 - 【鸿蒙多会话】
getPlayerForSession不再创建幽灵会话,会话缺失时回退createPlayer()并输出错误日志 - 【鸿蒙状态同步】修复返回页面后状态显示暂停但视频仍在播放的问题,
onNativeState仅在 mpv 确认paused=true时才切换为暂停态 - 【鸿蒙进度条】自研触控进度条替换原生 Slider:解决全屏模式进度条宽度不自动铺满(改用 100% 自适应布局)以及拖动无反应(
getBoundingClientRect()精确定位 +isSeeking防轮询回跳 + 触摸热区加大 + 事件冒泡拦截)问题 - 【安卓暂停修复】修复暂停按钮无效问题,
pause()仅停止主线程进度刷新,不再清空 mpv 线程的查询队列 - 【安卓全屏播放】修复暂停状态下切换全屏后自动播放的问题,进入全屏前同步 mr wantPlay 状态
- 【安卓多页面】修复第二个页面不播放的问题,
loadMedia()将wantPlay同步提前到 Surface 就绪判断之前
平台兼容性
uni-app x(5.0)
| Chrome | Safari | Android | iOS | 鸿蒙 | 微信小程序 |
|---|---|---|---|---|---|
| √ | √ | 7.0 | 16 | √ | - |
其他
| 多语言 | 暗黑模式 | 宽屏模式 | 蒸汽模式 |
|---|---|---|---|
| × | × | × | √ |
DPlayer 视频播放器
基于 libmpv 的跨平台视频播放器 uni_modules 插件,支持 H.265 解码和直播拉流。
功能特性
- H.265/HEVC 支持:Android/iOS 硬件解码 + FFmpeg 软件解码
- 直播拉流:HLS、HTTP-FLV、RTMP、RTSP 协议
- 多平台:uni-app X (UTS)、uni-app NVue、Web、微信小程序
- 弹幕系统:滚动弹幕、顶部弹幕、底部弹幕
- 清晰度切换:通过
resourcesprop 传入多码率列表,点击循环切换 - 自动重连:直播断流自动重连
- B站风格 UI:底部控制栏采用 B 站两层布局设计
- 鸿蒙适配:支持 HarmonyOS NEXT,ArkUI 原生渲染
安装
- 下载本插件到项目的
uni_modules/目录 - 在
pages.json中配置页面 - 在页面中使用
<d-player-video>组件
iOS 依赖配置
iOS 平台通过 CocoaPods 自动依赖 Libmpv 框架,无需手动下载。首次编译时会自动从 Gitee 下载 libmpv-1.0.0.xcframework.zip。
基础用法
<template>
<d-player-video
ref="player"
src="https://example.com/video.mp4"
:autoplay="true"
:live="false"
:resources="qualityList"
@statechange="onStateChange"
@progress=""
@prepared="onPrepared"
@error="onError"
/>
</template>
<script lang="uts">
export default {
data() {
return {
qualityList: [
{ name: '超清', url: 'https://example.com/1080p.mp4' },
{ name: '高清', url: 'https://example.com/720p.mp4' }
]
}
},
methods: {
onStateChange(e: any) {
console.log('状态变化:', e.state)
},
(e: any) {
console.log('进度:', e.position, '/', e.duration)
},
onPrepared(e: any) {
console.log('时长:', e.duration)
},
onError(e: any) {
console.error('错误:', e.code, e.message)
}
}
}
</script>
Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| src | String | "" | 视频/直播流地址 |
| autoplay | Boolean | false | 自动播放 |
| loop | Boolean | false | 循环播放 |
| live | Boolean | false | 是否为直播流 |
| muted | Boolean | false | 静音 |
| volume | Number | 1.0 | 音量 (0-1) |
| playbackRate | Number | 1.0 | 播放速度 |
| decodeMode | String | "auto" | 解码模式: auto/hw/sw |
| showControls | Boolean | true | 显示控制栏 |
| showDanmaku | Boolean | false | 显示弹幕 |
| objectFit | String | "contain" | 视频填充模式 |
| command | String | "" | 外部命令(如 "play"/"pause"/"seek:10000") |
| resources | Array | [] | 清晰度列表,每项 { name: string, url: string } |
| headers | Object | {} | HTTP 鉴权头,如 { Authorization: "Bearer xxx", Referer: "https://example.com" } |
API 使用方法
通过 ref 调用组件方法:
播放控制
// 开始播放(如果未加载媒体则自动加载)
this.$refs.player.play()
// 暂停播放
this.$refs.player.pause()
// 停止播放(重置到开头)
this.$refs.player.stop()
// 跳转到指定位置(单位:毫秒)
this.$refs.player.seek(10000) // 跳转到 10 秒
音量控制
// 设置音量(0-1 之间)
this.$refs.player.applyVolume(0.5) // 50% 音量
this.$refs.player.applyVolume(0.0) // 静音
this.$refs.player.applyVolume(1.0) // 最大音量
// 切换静音状态(通过 UI 按钮或外部调用)
this.$refs.player.toggleMute()
播放速度
// 设置播放速度
this.$refs.player.setSpeed(0.5) // 0.5 倍速
this.$refs.player.setSpeed(1.0) // 正常速度
this.$refs.player.setSpeed(1.5) // 1.5 倍速
this.$refs.player.setSpeed(2.0) // 2 倍速
// 循环切换倍速(UI 按钮自动循环:1.0→1.25→1.5→2.0→0.75)
this.$refs.player.cycleSpeed()
获取播放信息
播放状态通过事件回调获取,组件内部状态通过 data 绑定:
// 状态变化通过 statechange 事件
// 状态值: "idle" | "opening" | "ready" | "playing" | "paused" | "completed" | "error"
this.$on('statechange', (e) => {
console.log('状态:', e.state)
})
// 进度通过 progress 事件
this.$on('progress', (e) => {
console.log('位置:', e.position, '时长:', e.duration)
})
弹幕控制
// 通过 UI 按钮切换弹幕显示/隐藏
// 弹幕功能在内置控制栏中操作
清晰度切换
通过 resources prop 传入清晰度列表,用户点击 UI 上的清晰度按钮自动循环切换:
<d-player-video
:resources="qualityList"
@qualitychange="onQualityChange"
/>
const qualityList = [
{ name: '超清', url: 'https://example.com/1080p.mp4' },
{ name: '高清', url: 'https://example.com/720p.mp4' },
{ name: '标清', url: 'https://example.com/480p.mp4' }
]
注:
qualitychange事件目前仅在鸿蒙平台触发,Android/iOS 待实现。
全屏控制
全屏通过内置 UI 按钮触发,会自动切换页面方向(横屏/竖屏)和导航栏样式:
// 全屏状态通过 statechange 事件获取
// isFullscreen 状态在组件内部维护
释放资源
// 停止播放并释放所有资源(组件销毁前调用)
this.$refs.player.releasePlayer()
完整示例
<template>
<view>
<d-player-video
ref="player"
src="https://example.com/video.mp4"
:autoplay="true"
:loop="false"
:live="false"
:resources="qualityList"
@statechange="onStateChange"
@progress=""
@prepared="onPrepared"
@completion="onCompletion"
@error="onError"
/>
<view class="controls">
<button @click="togglePlay">播放/暂停</button>
<button @click="seekForward">快进 10 秒</button>
<button @click="seekBackward">快退 10 秒</button>
<button @click="changeSpeed">切换倍速</button>
</view>
</view>
</template>
<script lang="uts">
export default {
data() {
return {
isPlaying: false,
qualityList: [
{ name: '超清', url: 'https://example.com/1080p.mp4' },
{ name: '高清', url: 'https://example.com/720p.mp4' }
]
}
},
methods: {
togglePlay() {
this.$refs.player.togglePlay()
},
seekForward() {
this.$refs.player.seek(this.$refs.player.getPosition() + 10000)
},
seekBackward() {
this.$refs.player.seek(Math.max(0, this.$refs.player.getPosition() - 10000))
},
changeSpeed() {
this.$refs.player.cycleSpeed()
},
onStateChange(e) {
this.isPlaying = (e.state === "playing")
console.log('状态变化:', e.state)
},
(e) {
console.log('位置:', e.position, '时长:', e.duration)
},
onPrepared(e) {
console.log('媒体加载完成,时长:', e.duration)
},
onCompletion() {
console.log('播放完成')
},
onError(e) {
console.error('播放错误:', e.code, e.message)
}
},
onUnmounted() {
this.$refs.player.releasePlayer()
}
}
</script>
事件
| 事件名 | 参数 | 说明 |
|---|---|---|
| statechange | { state } | 播放状态变化(idle/opening/ready/playing/paused/completed/error) |
| progress | { position, duration } | 播放进度更新 |
| prepared | { duration } | 媒体加载完成 |
| buffering | { percent } | 缓冲状态 |
| error | { code, message } | 播放错误 |
| videosizechanged | { width, height } | 视频尺寸变化 |
| seekcomplete | {} | 跳转完成 |
| completion | {} | 播放完成 |
| livereconnect | { attempt } | 直播重连 |
| qualitychange | { index, name, url } | 清晰度切换(鸿蒙平台) |
直播拉流
<template>
<d-player-video
src="rtmp://live.example.com/stream"
:live="true"
:autoplay="true"
@livereconnect="onReconnect"
/>
</template>
支持的直播协议:
- HLS (m3u8)
- HTTP-FLV
- RTMP
- RTSP
H.265 支持
组件自动检测 H.265 硬件解码能力:
- Android:MediaCodec 硬解 + FFmpeg 软解
- iOS:VideoToolbox 硬解 + FFmpeg 软解
- 鸿蒙:FFmpeg 软解
- Web:ffmpeg.wasm 软解(实验性)
- 小程序:不支持 H.265
平台支持
| 平台 | 状态 | 说明 |
|---|---|---|
| uni-app X (Android) | 完整支持 | libmpv + MediaCodec |
| uni-app X (iOS) | 完整支持 | libmpv + VideoToolbox |
| uni-app X (鸿蒙) | 完整支持 | mpv-ohos ArkUI 原生渲染 |
| uni-app NVue | 完整支持 | 同 uni-app X |
| Web | 完整支持 | mpegts.js + hls.js |
| 微信小程序 | 基础支持 | 原生 video 组件 |
UI 特性
B站风格控制栏
底部控制栏采用 B 站两层布局设计:
- 上层:细红色进度条,支持拖动 seek
- 下层:播放/暂停按钮、时间显示、倍速、音量、全屏
图标样式
- 播放按钮:饱满三角形(B站风格)
- 暂停按钮:圆角双竖杠(B站风格)
- 全屏按钮:四角向外/向内图标
- 音量按钮:喇叭+声波图标
全屏适配
- 全屏模式下控制栏自动放大 1.4 倍
- 保持 B 站风格布局一致
- 鸿蒙平台通过
uni.getWindowInfo().safeArea动态适配安全区域 - 鸿蒙暂停状态下全屏切换通过
frame-step强制渲染修复画面不铺满问题
自定义控制栏 / 挂载点
native-view 组件不支持子组件,所以自定义 UI 不能通过 <slot> 塞进原生容器(Web 端例外,见下)。App 端提供挂载层:一个透明的原生容器,始终位于视频与内置控制栏之上,且随全屏自动迁移,自定义 UI 在全屏下依然可见可控。
平台行为
| 平台 | 挂载层 | 全屏跟随 |
|---|---|---|
| Android | FrameLayout,全屏时由 moveControlsTo 迁移进独立全屏窗口 |
自动 |
| iOS | UIView,随 containerView 整体进出全屏 UIWindow |
自动 |
| 鸿蒙 | 同视图树,全屏用 position: fixed 覆盖 |
第二批支持 |
| Web | <slot> 直接写模板 |
容器整体 requestFullscreen(),自动 |
App 端用法
// 1. 拿到挂载层(NVLoad 之后可用)
const layer = this.$refs.player.playerCustomLayer()
// 2. 把自建原生 View 挂上去
const btn = new View(this.$androidContext!)
btn.setLayoutParams(new FrameLayout.LayoutParams(48, 48))
this.$refs.player.addCustomView(btn)
// 3. 移除
this.$refs.player.removeCustomView(btn)
// 4. 清空全部自定义视图
this.$refs.player.clearCustomViews()
addCustomView 内部会切到主线程执行,可安全地在任意线程调用。
状态读取(拉取式)
自定义原生 UI 可用定时器轮询,不必等事件:
playerPosition() : number // 当前播放位置,毫秒
playerDuration() : number // 总时长,毫秒
playerState() : string // "idle"|"opening"|"ready"|"playing"|"paused"|"completed"|"error"
playerIsPlaying() : boolean
playerIsMuted() : boolean
playerIsFullscreen() : boolean
playerSpeed() : number
统一
player*前缀:Android 侧原生字段(duration、isFullscreen等)会自动生成同名 getter,方法名冲突会编译失败。
状态推送(订阅式)
不想轮询可订阅回调,返回的 id 用于注销:
const pid = this.$refs.player.addProgressListener((position, duration) => {
// 约 250ms 一次,仅在已准备好后触发
this.updateBar(position, duration)
})
const sid = this.$refs.player.addStateListener((state, isPlaying) => {
this.updatePlayIcon(isPlaying)
})
// 注销(务必在 onUnload / 卸载时调用,避免闭包持有 this 造成泄漏)
this.$refs.player.removeProgressListener(pid)
this.$refs.player.removeStateListener(sid)
Web 端用法
Web 实现是普通 Vue 组件,直接支持 <slot>,覆盖层置于视频之上:
<d-player-video ref="player" :src="src">
<view class="my-badge">LIVE</view>
</d-player-video>
覆盖层容器 pointer-events: none 不拦截事件,直接子元素自动恢复可交互,空白区域点击会穿透到下方原生 video 控制条。
设计取舍
- 不走
<slot>:native-view组件不支持子组件(官方文档);UTS 的<slot>自动转容器组件仅兼容模式支持,且不支持具名插槽、不支持 touch/click/tap 事件。本插件是标准模式uts-vue-component。 - 不做全局
createDPlayerContext(id):跨平台原生 view 类型不统一,且本插件是NVLoad()返回原生 view,模板里没有native-view节点,官方iterateElement递归范式无法套用。故以 ref 调组件方法交付;第三方插件跨组件访问留作后续。 - Android 层叠:
SurfaceView未设setZOrderMediaOverlay,默认 surface 在窗口之下,同FrameLayout内后加的 View 天然在上层,与内置控制栏可用原理一致。
注意事项
- H.265 专利风险:H.265 编解码器存在专利许可问题,商业使用需注意授权
- Web 端 H.265:依赖 ffmpeg.wasm,性能可能受影响
- 小程序限制:微信小程序原生 video 组件不支持 H.265
- 直播重连:默认最多重试 3 次,间隔 3 秒
- iOS 依赖:首次编译需联网下载 Libmpv 框架(约 10MB)
- 鸿蒙平台:不支持
calc()/env()CSS 函数,安全区域通过 JS 动态读取;position: absolute/fixed必须使用width/height而非left/right/top/bottom撑开
开发
目录结构
d-player-video/
├── index.uts # 全局入口
├── index.vue # 根组件
├── package.json # uni_modules 配置
├── common/
│ ├── types.uts # 类型定义
│ ├── events.uts # 事件常量
│ ├── state-machine.uts # 状态机
│ ├── player-config.uts # 配置管理
│ ├── danmaku-types.uts # 弹幕类型
│ ├── quality-types.uts # 清晰度类型
│ └── event-utils.uts # 事件工具
├── components/
│ ├── DPlayerControl.vue # 控制栏组件
│ ├── DPlayerProgress.vue # 进度条组件
│ ├── DPlayerDanmaku.vue # 弹幕组件
│ └── DPlayerQuality.vue # 清晰度组件
└── utssdk/
├── app-android/
│ ├── index.vue # Android UTS 组件
│ ├── index.uts # Android 平台函数
│ ├── config.json # Android 配置
│ └── DPlayerNative.kt # Native 桥接
├── app-ios/
│ ├── index.vue # iOS UTS 组件
│ ├── index.uts # iOS 平台函数
│ ├── config.json # iOS 配置
│ ├── Libmpv.podspec # CocoaPods 配置
│ └── DPlayerEngine.swift # 引擎实现
├── app-harmony/
│ ├── index.vue # 鸿蒙 UTS 组件
│ ├── index.uts # 鸿蒙平台函数
│ ├── builder.ets # ArkTS 构建函数 + mpv 命令
│ └── config.json # 鸿蒙配置
├── web/
│ └── index.vue # Web 平台实现(uni-h5 内建 video + slot 覆盖层)
└── mp-weixin/
└── index.vue # 微信小程序实现
更新日志
详见 changelog.md
许可
MIT License

收藏人数:
购买源码授权版(
试用
使用 HBuilder 导入示例项目
赞赏(0)
下载 106
赞赏 0
下载 12700526
赞赏 1956
赞赏
京公网安备:11010802035340号