更新记录
1.1.0(2026-09-18)
- 【Android 控制栏对齐】完全复刻 d-player-video 内置栏:中央 120px 圆形大播放按钮(ImageView + 半透明底 + 白色描边)、播放/暂停/音量/静音/全屏均改用矢量 Drawable 图标(优先加载 dplayer-iconfont.ttf,失败回退 Canvas Path 绘制),控制行 padding 改为
(42,10,22,12)、按钮宽 44、间距 20,与内置栏视觉一致
- 【iOS 崩溃修复】修复加载后
swift_dynamicCastFailure 崩溃:danmaku-list 元素经 iOS 桥接后不是 UTSJSONObject,as! 强转中止应用;改用 instanceof 判断 + JSON.parse(JSON.stringify()) 兜底安全转换
- 【Web 同名冲突修复】修复
this.showControls is not a function:Web 端 showControls() 方法与 Boolean prop show-controls 冲突,与 Harmony 同步改名 revealControls()(6 处调用)
- 【iOS/Android 弹幕开关加固】
toggle-danmaku 回调内直接内联启停/清屏逻辑(startDanmakuLoop/clearActive/dispatchDueDanmaku 或 stopDanmakuLoop/clearActive),不再依赖 watch 时序,规避安卓弹幕残留但冻结问题
- 【发送弹幕】demo 页新增发送弹幕卡片(输入 + 发送 + 滚动/顶部/底部切换),向
danmaku-list push 新项即刻入场(time = 当前播放位置)
- 【Harmony 触摸显示】弹幕层全屏
@click.stop="" 吞掉点击,改为 @click.stop="onSurfaceTap",修复鸿蒙端点击不显示控制栏
- 【Harmony 命名冲突】
showControls() 方法与 prop 撞名(true is not callable),改名 revealControls()(7 处调用)
- 【文档】新增 README(安装、用法、Props/Events 协议、平台差异表)
平台兼容性
uni-app(5.0)
| Vue2 |
Vue3 |
Chrome |
Safari |
app-vue |
app-nvue |
Android |
iOS |
鸿蒙 |
| √ |
√ |
√ |
√ |
√ |
√ |
√ |
√ |
√ |
| 微信小程序 |
支付宝小程序 |
抖音小程序 |
百度小程序 |
快手小程序 |
京东小程序 |
鸿蒙元服务 |
QQ小程序 |
飞书小程序 |
小红书小程序 |
快应用-华为 |
快应用-联盟 |
| - |
- |
- |
- |
- |
- |
- |
- |
- |
- |
- |
- |
uni-app x(5.0)
| Chrome |
Safari |
Android |
iOS |
鸿蒙 |
微信小程序 |
| - |
- |
- |
- |
- |
- |
d-video-ui 播放器控制栏
面向 uni-app x 的自定义播放器控制栏组件,与 d-player-video 播放器搭配使用。控制栏与弹幕引擎在 Android / iOS / HarmonyOS / Web 四端行为统一。
功能特性
- 四端统一控制栏:播放/暂停、进度条(拖动预览+跳转)、倍速切换、静音、全屏、弹幕开关,Android/iOS/Web/Harmony 交互一致
- 原生挂载(Android/iOS):通过
player.addCustomView() 将原生控制栏挂到视频之上的 customLayer,覆盖播放器内置控制栏(showControls=false)
- Overlay 覆盖层(Web/Harmony):渲染为播放器 slot 内覆盖层,与内置栏样式对齐
- 弹幕引擎:滚动/顶部/底部三种弹幕类型,多轨道防碰撞,支持自定义颜色、字号、滚动速度
- 弹幕列表 prop 驱动:
danmaku-list 数组变化实时生效,播放中发送的弹幕即刻入场
- 事件协议统一:
toggle-play / seek / cycle-speed / toggle-mute / toggle-fullscreen / toggle-danmaku 四端 kebab-case
- 控制栏显隐规则:播放中 3 秒自动隐藏,暂停/未播放常驻;点击画面显示
安装
- 将
d-video-ui 与 d-player-video 复制到项目的 uni_modules/ 目录(easycom 自动注册,无需 pages.json 配置)
- 在页面中使用
基础用法
<template>
<d-player-video
ref="player"
src="https://example.com/video.mp4"
:show-controls="false"
:muted="muted"
@statechange="onStateChange"
@progress=""
>
<!-- Web / Harmony:d-video-ui 作为覆盖层内嵌到播放器 slot -->
<!-- #ifdef WEB || APP-HARMONY -->
<d-video-ui
ref="ui"
:duration="duration"
:position="position"
:is-playing="isPlaying"
:muted="muted"
:speed="playbackRate"
:danmaku-list="danmakuList"
@toggle-play="Play"
@seek=""
@cycle-speed="onCycleSpeed"
@toggle-mute="Mute"
@toggle-fullscreen="Fullscreen"
@toggle-danmaku="Danmaku"
/>
<!-- #endif -->
</d-player-video>
<!-- Android / iOS:d-video-ui 是原生组件(slot 不可用),作为兄弟节点绑定 -->
<!-- #ifdef APP-ANDROID || APP-IOS -->
<d-video-ui
ref="ui"
:duration="duration"
:position="position"
:is-playing="isPlaying"
:muted="muted"
:speed="playbackRate"
:danmaku-list="danmakuList"
@toggle-play="Play"
@seek=""
@cycle-speed="onCycleSpeed"
@toggle-mute="Mute"
@toggle-fullscreen="Fullscreen"
@toggle-danmaku="Danmaku"
/>
<!-- #endif -->
</template>
Android/iOS:把原生控制栏挂到播放器
原生端 d-video-ui 是原生组件,只渲染一个空的占位 View。真正的控制栏通过 getControlBarView() 获取后调用播放器的 addCustomView() 挂载:
// #ifdef APP-ANDROID || APP-IOS
onReady() {
setTimeout(() => {
this.mountNativeControlBar()
}, 300)
},
methods: {
mountNativeControlBar() {
const player = this.$refs.player as DPlayerVideoElement | null
const ui = this.$refs.ui as DVideoUiElement | null
if (player == null || ui == null) return false
const bar = ui.getControlBarView()
if (bar == null) return false
player.addCustomView(bar)
this.controlBarMounted = true
}
}
// #endif
pages.json 需声明两个原生组件:
{
"easycom": {
"custom": {
"d-player-video": {
"app-android": "d-player-video",
"app-ios": "d-player-video",
"app-harmony": "d-player-video"
},
"d-video-ui": {
"app-android": "d-video-ui",
"app-ios": "d-video-ui",
"app-harmony": "d-video-ui"
}
}
}
}
Props
| 名称 |
类型 |
默认值 |
说明 |
duration |
Number |
0 |
视频总时长(ms) |
position |
Number |
0 |
当前播放位置(ms),驱动进度条与弹幕调度 |
is-playing |
Boolean |
false |
是否播放中 |
muted |
Boolean |
false |
是否静音 |
speed |
Number |
1.0 |
播放倍速,用于显示与循环切换 |
live |
Boolean |
false |
是否直播(直播不显示进度条) |
show-controls |
Boolean |
true |
控制栏是否显示 |
is-fullscreen |
Boolean |
false |
是否全屏,用于切换全屏图标 |
danmaku-list |
Array |
[] |
弹幕列表,元素见下 |
danmaku-list 元素
| 字段 |
类型 |
必填 |
说明 |
id |
Number |
是 |
弹幕唯一标识 |
time |
Number |
是 |
弹幕出现的播放时刻(ms) |
text |
String |
是 |
弹幕内容 |
type |
String |
否 |
scroll 滚动(默认)/ top 顶部 / bottom 底部 |
color |
String |
否 |
颜色,CSS 格式 #rrggbb,默认 #ffffff |
fontSize |
Number |
否 |
字号(px),默认 14 |
speed |
Number |
否 |
滚动速度(px/s),仅 scroll 有效,默认 90 |
Events
事件载荷统一为 Map<string, any>(原生端)或普通对象(Web)。
| 事件 |
载荷 |
说明 |
@toggle-play |
{} |
点击播放/暂停 |
@seek |
{ ms: number } |
拖动进度条定位 |
@cycle-speed |
{} |
切换倍速 |
@toggle-mute |
{} |
切换静音 |
@toggle-fullscreen |
{} |
切换全屏 |
@toggle-danmaku |
{} |
切换弹幕开关 |
发送弹幕
向 danmaku-list push 新元素即可(id 递增、time 用当前播放位置),播放中由 position 变化驱动弹幕即刻入场;暂停时发送的弹幕会等到恢复播放后才入场。参考 demo/pages/custom-ui/index.uvue。
平台差异
| 平台 |
控制栏实现 |
触摸显示控制栏 |
图标 |
| Android |
原生 LinearLayout + TextView/SeekBar,drawable 图标 |
SurfaceView 触摸回调 |
iconfont(dplayer-iconfont.ttf),回退 Canvas Path 矢量绘制 |
| iOS |
原生 UIView + UILabel/UISlider/UIButton |
UITapGestureRecognizer(cancelsTouchesInView=false) |
图标文本 + 系统 SF Symbols 近似 |
| Harmony |
ArkUI 覆盖层 |
弹幕层 @click.stop 透传 + 控制层点击 |
文本字形 |
| Web |
DOM 覆盖层 |
点击/触摸事件 |
文本字形 |
Changelog
见 changelog.md。