更新记录
1.0.0(2026-10-18)
首个正式版。三端(Android / iOS / HarmonyOS)全屏体验与进度显示统一收口。
- 全屏进出不流畅(三端共享层):
- 全屏状态改为点击瞬间乐观置位,
success 只校准、fail 回滚。此前要等 success 回调(它在旋转动画结束后才回来)才置位,控制栏 padding、标题栏、全屏图标会比动画晚一拍"跳"一下
- 暂停帧重绘从「每实例两次微 seek」降为「一次」:全局广播由 300ms + 600ms 两次改为请求瞬间一次,接收端加防抖(
SURFACE_REFRESH_DELAY,默认 500ms,只此一个可调常量)
- 移除
onResize 兜底:它是页面生命周期、组件内不生效(uni-app x 亦无 uni.onWindowResize API),跨实例通知统一走 uni.$emit,并在注释中写明原因
- 新增
@fullscreenchange / @fullscreenerror 兜底:系统手势、返回键退出全屏时 page.exitFullscreen 的 success 根本不会触发,改由 page.fullscreenElement(Web 用 document.fullscreenElement)校准;带可靠性开关,属性未实现时不会把刚进全屏的 UI 误判成"未全屏"
- 进度条与播放时间改本地线性插值(
displayPosition):不再按原生轮询频率(安卓 400ms / 鸿蒙 500ms)2~2.5Hz 跳格;插值系数封顶 1,不会超前于最近一次真实采样;seek / 切源等跳变直接吸附
- 清理死代码:
refreshSafeTop()(全注释空函数)、safeTop ref、标题栏残留的空 :style
- 安卓:全屏时页面 TitleBar 由
View.GONE 改为 View.INVISIBLE,修复 GONE 释放布局空间导致页面内容上移铺满、退出全屏后顶部被遮挡的问题(与代码内注释及 issue #33146 对齐)
- 修复中央播放按钮在默认配置下基本不出现:初始化被
&& !autoplay 置否(默认 autoplay 为真,故开局即为 false),且状态到 READY/PLAYING 后被单向置 false 再无恢复路径,图标还写死为播放键。现改为随控制栏显隐、图标跟随播放状态切换(播放中显示暂停键);并把原来 100%×100% 的覆盖层收成图标大小的居中按钮,避免它连"点画面任意位置切换控制栏"一起吃掉;顺带删掉随之失效的 controlsState
0.3.2(2026-09-30)
- 鸿蒙多实例视频状态读取:改用
getProperty 直读替代 player.state 回调,修复多实例场景下只有最后一个实例能更新状态的问题
autoplay=false 未真正暂停:doOpen 后紧跟 ['set', 'pause', 'yes'],mpv 打开文件默认自动播放
- 暂停状态推导:用
wantPlay 标志位替代不可信的 getPropertyBool('pause')(mpv 暂停后仍返回 false)
- 打开队列串行化:等待当前实例
duration > 0 才放行下一路,加 8s 看门狗防死锁
getPlayerForSession 不再复用其他 session 的 player,避免 surface 冲突
emitTimeupdate 每 tick 无条件触发(此前仅在 playing 状态下发送,导致暂停/结束不更新)
- 全屏退出后暂停视频画面压扁:页面旋转导致 XComponent surface 重建,暂停视频不自动重渲染。新增全局事件总线 + 微 seek 强制重配 VO 渲染一帧
- loading 语义修正:暂停和已播完不显示 loading,仅播放中缓冲才显示(
buffering = playing && paused-for-cache)
- 缓冲状态边沿触发:仅在 loading 状态翻转时发事件,避免每 500ms 重复发送 waiting/buffering
- Android 内存泄漏:
destroy() 置空 surfaceCallback / surfaceHolder / surfaceView / nativeContainer / $element / params,切断 View → Activity 引用链
0.3.1(2026-09-29)
Added
- README 新增「注意事项」章节:蒸汽模式不支持 rpx、退出全屏字体变大、watch 不触发、@keyframes 不可用、Math.random() 返回 0、Element.animate() 不支持、iOS 事件对象兼容说明
Fixed
- iOS 自定义 UI 时间/状态不更新:
getEventValue 先尝试 (e as any)[key] 属性访问,失败再 fallback 到 (e as Map<string, any>).get(key)
- 所有 Demo 页面 rpx → px 转换(d-video/vapor/live),2rpx=1px
查看更多
平台兼容性
uni-app x(5.0)
| Chrome |
Safari |
Android |
iOS |
鸿蒙 |
微信小程序 |
| √ |
√ |
8.0 |
16 |
√ |
- |
其他
| 多语言 |
暗黑模式 |
宽屏模式 |
蒸汽模式 |
| × |
× |
× |
√ |
d-video · 蒸汽模式视频播放器
对齐官方 uni-video 架构,UI 直接写 view、native-view 桥接 libmpv 引擎,支持蒸汽(Vapor)模式与直播/倍速/弹幕/全屏。
特性
- 蒸汽模式 — 视图层直出 bytecode/nativecode,无 VDOM diff
- native-view 桥接 — Android 端通过
native-view 桥接 libmpv,Web 端回退内置 <video>
- 跨平台 — Android / iOS / 鸿蒙 / H5 四端覆盖
- 直播支持 — HLS/FLV/RTMP/RTSP 直播流,断流自动重连,直播模式自动隐藏进度条与倍速
- 弹幕引擎 — Vapor 视图层 v-for + setInterval 推进,不经过 VDOM diff
- 图标字体 — CSS
@font-face 全局加载,模板用 HTML 实体  引用
- 全屏 — Android 沉浸式 +
requestedOrientation,iOS/鸿蒙 $setPageStyle 横屏,H5 原生 DOM Fullscreen API
- 自定义 UI —
controls="false" 隐藏默认 UI,通过 <slot> 插槽注入自定义控制栏
- 多实例 — 支持同页多个 d-video 实例,各自独立引擎
快速开始
# 安卓
cli launch app-android --project <path> --playground custom --customType local --cleanCache true
# H5
cli launch web --project <path>
使用
<d-video
ref="playerRef"
:src="videoUrl"
:autoplay="false"
:live="false"
:muted="false"
:danmuBtn="true"
:enableDanmu="true"
:danmuList="danmuList"
:showMuteBtn="true"
@statechange="onStateChange"
@progress="onProgress"
@prepared="onPrepared"
@fullscreenchange=""
/>
自定义 UI(Slot 插槽)
传 controls="false" 隐藏默认 UI,通过 <slot> 注入自定义控制栏:
<d-video ref="playerRef" :src="videoUrl" :controls="false" :showCenterPlayBtn="false" :showLoading="false">
<!-- 自定义播放按钮 -->
<view class="my-overlay" v-if="!playing">
<text class="my-play-btn" @click="playerRef?.play()">▶</text>
</view>
<!-- 自定义控制栏 -->
<view class="my-controls">
<text>{{ formatTime(position) }} / {{ formatTime(duration) }}</text>
<text @click="playerRef?.toggle()">{{ playing ? '⏸' : '▶' }}</text>
<text @click="playerRef?.toggleFullscreen()">⛶</text>
</view>
</d-video>
多实例视频列表
<scroll-view>
<view v-for="(item, idx) in videoList" :key="idx">
<d-video
:ref="(el: any) => onPlayerRef(el, idx)"
:src="item.src"
:controls="false"
:danmuBtn="false"
@statechange="(e: any) => onStateChange(e, idx)"
@progress="(e: any) => onProgress(e, idx)"
></d-video>
<text>{{ item.title }}</text>
</view>
</scroll-view>
直播模式
<d-video
:src="hlsUrl"
:live="true"
:autoplay="true"
@livereconnect="onReconnect"
/>
直播模式下自动隐藏进度条、倍速控制;断流触发 livereconnect 事件。
支持协议
基于 libmpv 引擎,支持以下协议:
| 协议 |
示例 |
| RTMP |
rtmp://192.168.0.182:1936/test |
| RTSP |
rtsp://192.168.0.182:8554/test |
| HLS |
https://192.168.0.182:8888/test/index.m3u8 |
| FLV |
https://example.com/live.flv |
| HTTP/HTTPS |
https://example.com/video.mp4 |
libmpv 支持格式
| 类别 |
格式 |
| 容器 |
MP4, MKV, AVI, MOV, FLV, HLS (m3u8), TS, WebM |
| 视频编码 |
H.264, H.265/HEVC, VP9, AV1, MPEG-2, MPEG-4, Theora |
| 音频编码 |
AAC, MP3, FLAC, Vorbis, Opus, AC3, DTS |
| 字幕 |
SRT, ASS/SSA, VobSub, WebVTT |
组件 API
Props
| 属性 |
类型 |
默认值 |
说明 |
| src |
string |
— |
视频 URL |
| controls |
boolean |
true |
显示默认控制栏 |
| autoplay |
boolean |
false |
自动播放 |
| live |
boolean |
false |
直播模式 |
| muted |
boolean |
false |
静音 |
| loop |
boolean |
false |
循环 |
| playbackRate |
number |
1.0 |
倍速 |
| objectFit |
string |
'contain' |
填充模式 |
| title |
string |
'' |
全屏标题 |
| danmuBtn |
boolean |
false |
显示弹幕开关 |
| enableDanmu |
boolean |
false |
开启弹幕 |
| danmuList |
Danmu[] |
[] |
弹幕列表 |
| showMuteBtn |
boolean |
false |
显示静音按钮 |
| showFullscreenBtn |
boolean |
true |
显示全屏按钮 |
| showPlayBtn |
boolean |
true |
显示播放按钮 |
| showCenterPlayBtn |
boolean |
true |
显示中央播放按钮 |
| showLoading |
boolean |
true |
显示加载指示 |
Methods
| 方法 |
说明 |
| play() |
播放 |
| pause() |
暂停 |
| stop() |
停止 |
| seek(ms) |
跳转(毫秒) |
| toggle() |
播放/暂停切换 |
| mute(muted) |
静音/取消 |
| playbackRate(rate) |
设置倍速 |
| requestFullscreen() |
进入全屏 |
| exitFullscreen() |
退出全屏 |
| toggleFullscreen() |
全屏切换 |
| getCurrentState() |
当前状态 |
| getPosition() |
当前位置(ms) |
| getDuration() |
总时长(ms) |
| switchQuality(index) |
切换清晰度 |
| switchSource(url) |
切换视频源(Vapor 模式需直接调用) |
Events
| 事件 |
说明 |
| statechange |
播放状态变化 |
| progress |
播放进度 |
| prepared |
就绪 |
| end |
播放结束 |
| error |
错误 |
| fullscreenchange |
全屏变化 |
| livereconnect |
直播重连 |
Iconfont 用法
/* App.uvue 全局加载 */
@font-face {
font-family: 'dplayer-iconfont';
src: url('/static/dplayer-iconfont.ttf');
}
<!-- 模板中用 HTML 实体,不要用 JS 转义 -->
<text style="font-family: 'dplayer-iconfont';"></text>
<!-- 动态切换可用 JS 字符串 -->
<text>{{ playing ? '\ue60f' : '\ue612' }}</text>
| 图标 |
码点 |
HTML 实体 |
| 播放 |
U+E612 |
 |
| 暂停 |
U+E60F |
 |
| 全屏进入 |
U+E610 |
 |
| 全屏退出 |
U+E611 |
 |
| 音量 |
U+E615 |
 |
| 静音 |
U+E617 |
 |
注意:字体文件不含 U+E601,返回按钮使用 Unicode 箭头 ← 代替。
Demo 页面
| 页面 |
路径 |
说明 |
| 蒸汽模式 Demo |
pages/vapor/index |
多实例视频列表 + 弹幕 + 全屏 |
| 官方式 Demo |
pages/d-video/index |
对齐 uni-video |
| HLS 直播 Demo |
pages/live/index |
直播流 + 断流重连 |
| 自定义 UI Demo |
pages/custom-ui/index |
slot 插槽自定义控制栏 |
平台支持
| 平台 |
引擎 |
全屏方式 |
| Android |
libmpv (native-view) |
沉浸式 + requestedOrientation |
| iOS |
libmpv (native-view) |
$setPageStyle 横屏 |
| 鸿蒙 |
libmpv (native-view) |
$setPageStyle 横屏 |
| H5 |
内置 <video> |
DOM Fullscreen API |
注意事项
- 蒸汽模式不支持 rpx — Vapor 模式下
rpx 会被当作固定像素值,不随屏幕适配缩放,请统一使用 px(2rpx = 1px)
- 退出全屏字体变大 — 全屏状态下
100vw/100vh 计算会因方向切换导致 UI 异常,建议全屏/非全屏使用不同的控制栏样式,或通过 fullscreenchange 事件监听后主动切换
- Vapor 模式 watch 不触发 —
watch(() => props.src) 在 Vapor 模式下不会触发,需通过 switchSource(url) 方法直接切换视频源
- CSS @keyframes 不可用 — Vapor 模式下 CSS 动画不生效,弹幕等动效请使用
v-for + setInterval 推进
- Math.random() 不生效 — Vapor 模式下返回 0,需使用自定义伪随机(如 LCG)
- Element.animate() 不支持 — Vapor 模式下不可用,需手动操作样式
- iOS 事件对象 — 自定义 UI 中读取事件数据时,需兼容属性访问和 Map.get() 两种方式
- Vapor 不跟踪 ref 深层属性写入 —
states.value[idx].position = v 这类深层赋值不会触发视图更新,必须整体重新赋值(states.value = list)或改用扁平 ref<number> / ref<string>
- Vapor 慎用
Record + 动态下标 — ref<Record<number, X>>({}) 配合 v-for 动态 key 时更新易丢失,建议改用 ref<X[]>([]) 并按索引整体替换元素
getPropertyBool('pause') 不可信 — mpv 的 pause 属性在暂停后可能仍返回 false,必须用 wantPlay 标志位推导暂停状态
autoplay=false 需显式暂停 — mpv 打开文件后默认自动播放,doOpen 中必须紧跟 ['set', 'pause', 'yes'] 才能真正暂停
- 全屏退出后暂停视频画面变形 — 页面旋转导致 XComponent surface 重建,暂停视频不会自动重渲染。需在旋转结束后通过全局事件通知所有实例,微 seek 强制重配 VO 渲染一帧
- Android 内存泄漏:DVideo 持有 View/Activity 引用 —
destroy() 必须置空 surfaceCallback / surfaceHolder / surfaceView / nativeContainer / $element / params,否则已销毁的 Activity 无法回收
许可
MIT