更新记录
1.0.0(2026-10-22) 下载此版本
1.0.0(2026-10-21)
首个发布版本。
- 三端同一组件名、同一套 props / 事件 / 方法:
- App(Android):uts 原生实现,MediaPlayer + TextureView 播放内核,零三方依赖; 全屏 / 退出全屏为原生层 90° 旋转动画(不走系统旋转,Activity 锁竖屏)。
- H5 / 微信小程序:Vue 实现,自绘控制栏 + 悬屏滚动弹幕(小程序用原生 danmu-list)。
- 控制栏三端同一布局:播放/暂停 · 时间 · 进度条 · 弹幕开关 · 倍速 · 全屏。
- 弹幕:滚动弹幕、开关、发送即时反馈(
sendDanmaku,H5/小程序)。 - 倍速:0.5 / 0.75 / 1 / 1.25 / 1.5 / 2 六档弹出菜单,面板可滚动并自动定位当前档。
- 断点续播:
seekTimeprop +handleSeek()方法。 - 主题色:
themeColorprop 一处配置进度条已播段 / 滑块 / 倍速面板滚动条颜色。 - 暂停时画面正中显示大播放键;拖动进度条时画面正中显示目标时间预览(Android)。
平台兼容性
uni-app(5.26)
| Vue2 | Vue3 | Chrome | Safari | app-vue | app-nvue | Android | iOS | 鸿蒙 |
|---|---|---|---|---|---|---|---|---|
| × | √ | √ | √ | √ | √ | √ | × | × |
| 微信小程序 | 支付宝小程序 | 抖音小程序 | 百度小程序 | 快手小程序 | 京东小程序 | 鸿蒙元服务 | QQ小程序 | 飞书小程序 | 小红书小程序 | 快应用-华为 | 快应用-联盟 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| √ | × | × | × | × | × | × | × | × | × | × | × |
dumuqiao-video 视频播放器
三端(App-Android / H5 / 微信小程序)统一的视频播放组件,支持滚动弹幕、倍速、 断点续播与主题色定制。同一个组件名、同一套 props / 事件 / 方法,页面代码无需按端分叉。
- App(Android):uts 原生实现——
TextureView + MediaPlayer播放内核, 零三方依赖(不引 ExoPlayer/Media3,自定义基座无需联网拉 Maven 包); 控制栏、进度条、倍速菜单、弹幕层全部原生自绘;全屏 / 退出全屏是原生层 90° 旋转动画(不走系统旋转,B 站式转场)。全屏的尺寸切换、转场动画、 黑底与沉浸式标记全部在组件内部完成,页面不需要任何全屏布局代码。 - H5:Vue 实现,
<video>+ 自绘控制栏 + CSS 动画弹幕层。 - 微信小程序:Vue 实现,原生
<video>+danmu-list弹幕 +cover-view控制栏。
iOS 暂未适配(
utssdk/app-ios待补),iOS 端会回退渲染 Vue 版组件的空壳, 请在 iOS 上自行降级处理。
特性
- 控制栏三端同一布局:播放/暂停 · 时间 · 进度条 · 弹幕开关 · 倍速 · 全屏
- 滚动弹幕:悬屏弹幕层、开关、发送即时反馈;每条弹幕可带颜色与字号, 自己发送的弹幕渲染为浅色底 + 描边,一眼可从满屏弹幕里认出来
- 倍速:菜单选档,默认 0.5 / 0.75 / 1 / 1.25 / 1.5 / 2 六档,可用
ratesprop 动态换成任意档位列表;Android 端面板可滚动并自动定位当前档 - 断点续播:
seekTimeprop,开播后自动跳一次 - 主题色:
themeColor一处配置进度条已播段 / 滑块 / 倍速面板滚动条颜色 - 缓冲指示:卡顿 / 首帧加载时画面正中显示转圈 + 实时加载速度(
1.2 MB/s), 恢复播放立即自动隐藏 - 全屏顶部条:
titleprop 传入视频标题,全屏时与返回键一起显示在顶部, 跟随控制栏一起显隐(App 端) - Android 端额外体验:暂停时画面正中的大播放键、拖动进度条时居中的目标时间预览、 控制栏 3 秒无操作自动收起、全屏时控制栏两端图标自动内收
- 组件自带控制栏图标(
static/随插件分发),不依赖宿主项目的静态资源
运行环境要求
| 项 | 要求 |
|---|---|
| HBuilderX | ≥ 3.6.18(uts 组件的最低版本) |
| App 端系统 | Android 5.0+(minSdkVersion 21);iOS 暂未适配 |
| App 端基座 | 自定义基座或云打包(标准基座不含 uts 插件代码) |
| App 端页面 | 只能放在 nvue 页面(uts 兼容模式组件的限制) |
| App 端方向 | manifest.json 的 app-plus.screenOrientation 只保留 ["portrait-primary"] |
| H5 / 小程序 | 无特殊要求,.vue 页面直接用 |
锁竖屏的原因:Android 端全屏是原生层手动画 90° 旋转,如果系统同时也在旋转, 两套旋转会打架。组件初始化时也会主动把 Activity 锁回竖屏兜底。
安装
- 从插件市场导入(或把
uni_modules/dumuqiao-video复制到你的项目)即可, easycom 自动注册,无需在pages.json配置。 - App 端:uts 原生插件必须制作「自定义基座」后才能运行(HBuilderX 菜单 「运行 → 运行到手机或模拟器 → 制作自定义调试基座」),正式发版走云打包。
- App 端组件只能用在 nvue 页面,H5 / 小程序的
.vue页面不受影响。 通常做法:详情页写两份——xxx.nvue(App)与xxx.vue(H5/小程序), uni-app 在 App 端自动优先.nvue。
快速开始
<template>
<!-- App 端请把本组件放在 .nvue 页面里,并用 :style 给显式宽高(只是内联尺寸,
全屏尺寸与转场由组件内部接管,页面不用改) -->
<dumuqiao-video
ref="playerRef"
style="width: 750rpx; height: 422rpx"
:src="playUrl"
:poster="coverUrl"
:autoplay="true"
:seekTime="resumeSec"
:danmakuText="danmakuText"
:danmakuEnabled="true"
themeColor="#2f7355"
@play="onPlay"
@pause="onPause"
@timeupdate=""
@fullscreenchange=""
@danmakutoggle="onDanmakuToggle"
@error="onError"
/>
</template>
<script setup lang="ts">
import { ref } from "vue";
const playUrl = ref("https://example.com/video.mp4");
const coverUrl = ref("https://example.com/cover.jpg");
const resumeSec = ref(12); // 上次看到的进度(秒),0 表示从头播
// 弹幕列表是 JSON 字符串,timeSec 单位秒(含小数)。
// color / fontSize / self 都可省略:默认白色 18 号,self 控制「自己的弹幕」高亮
const danmakuText = ref(
JSON.stringify([
{ id: 1, timeSec: 3.5, content: "前排围观" },
{ id: 2, timeSec: 8, content: "这段高能", color: "#ffd23f", fontSize: 22, self: true },
]),
);
const playerRef = ref();
// Android 端事件负载是 JSON 字符串,H5/小程序是对象——先统一剥成对象
const parsePayload = (payload: any): any => {
let detail = payload;
for (let i = 0; i < 5; i += 1) {
if (typeof detail === "string") {
try { detail = JSON.parse(detail); } catch { return null; }
continue;
}
if (detail && typeof detail === "object" && "detail" in detail) {
detail = detail.detail;
continue;
}
break;
}
return detail && typeof detail === "object" ? detail : null;
};
const isFullscreen = ref(false);
const = (payload: any) => {
const detail = parsePayload(payload);
isFullscreen.value = !!detail?.fullScreen;
};
const = (payload: any) => {
const detail = parsePayload(payload) ?? {};
// detail.currentTime / detail.duration(秒)
};
// 发弹幕:先把新弹幕合入列表(三端通用),H5 再补一条即时飘屏
const sendMyDanmaku = (content: string, color = "#ffffff", fontSize = 18) => {
const timeSec = playerRef.value?.getCurrentTime?.() ?? 0;
const list = JSON.parse(danmakuText.value || "[]");
// self: true —— 自己刚发的这条在画面上带浅色底 + 描边
list.push({ id: Date.now(), timeSec, content, color, fontSize, self: true });
danmakuText.value = JSON.stringify(list);
// #ifdef H5
playerRef.value?.sendDanmaku?.({ content, timeSec, color, fontSize });
// #endif
};
</script>
nvue 页面里 prop 绑定必须写 camelCase(
:danmakuText/:seekTime), kebab-case(:danmaku-text)在 nvue 的 uts 组件上不生效,prop 会悄悄丢失。 事件监听不受此限(@fullscreenchange正常)。
全屏:页面不用写任何尺寸逻辑
Android 端进入全屏时,组件会把原生视图从页面布局里摘出来,挂到 Activity 的
decorView 上铺满屏幕,并按视频宽高比自动决定是否旋转 90°;退出时先播完
归位动画,再把它接回原父容器的原索引与原布局参数。因此:
- 页面只需要给组件「内联」尺寸(
:style="width:...;height:..."), 不要为全屏准备第二套宽高,也不要在onResize里改尺寸; - 全屏时页面内容(导航栏、简介等)会被原生全屏层盖住,不需要
v-if隐藏; - 全屏时控制栏的退出按钮、弹幕开关都在原生层,跟着画面一起旋转。
唯一建议页面接的是 fullscreenchange,用于拦截返回键(否则全屏按返回会直接
关掉页面)。
全屏时组件自带顶部条:左侧返回键(点按退出全屏)、右侧 title prop 传入的
视频标题。App 端它与底部控制栏同显同隐(点画面唤出、3 秒无操作一起收起);
H5 端控制栏不收起,顶部条跟随它常驻。页面不需要为它写任何代码。
拦截返回键的写法(页面侧唯一建议接的代码):
// nvue 页面(选项式写法;组合式见上方快速开始)
data() {
return { isFullscreen: false };
},
onBackPress() {
if (this.isFullscreen) {
this.$refs.playerRef?.exitFullScreen();
return true;
}
return false;
},
缓冲指示与加载速度
卡顿(播放中数据跟不上)和首帧加载时,画面正中会显示转圈 + 加载速度
(如 正在缓冲 1.2 MB/s),无需任何配置。
- App-Android:由 MediaPlayer 的
BUFFERING_START / END事件驱动; 速度 = 缓冲百分比增量 × 文件大小 ÷ 时间。文件大小靠组件对src自动发一次HEAD请求取Content-Length——服务器不返回或非 http 地址时只显示 「正在缓冲…」,不显示速度。 - H5:
waiting事件亮起,play/timeupdate熄灭(uni-h5 的内置 video 组件只转发waiting / play / pause / ended / timeupdate / error / loadedmetadata / progress / fullscreenchange,不转发playing / canplay, 所以「恢复播放」只能靠播放中每 250ms 必到的timeupdate兜底—— 画面一动 loading 必隐藏,不会卡在「正在缓冲…」)。 速度 = 已缓冲时长增量 × 平均码率,文件大小同样来自HEAD请求 (跨域且未开 CORS 时拿不到,退化为只显示「正在缓冲…」)。 - 微信小程序:原生
<video>自带加载态,组件不额外绘制。
弹幕数据
三端统一的数据形状(utssdk/interface.uts 的 DumuqiaoVideoDanmaku):
{
id: number; // 唯一即可,用于去重与「新弹幕即时飘屏」判定
timeSec: number; // 相对视频开头的秒数,含小数
content: string; // 纯文本
color?: string; // 文字颜色 #rrggbb,默认 "#ffffff"
fontSize?: number; // 字号(sp / px 同口径),默认 18,建议 12~32
self?: boolean; // 是否自己发送:true 时加浅色底 + 描边,默认 false
}
样式是逐条的:同一屏里可以同时有黄色大字和白色小字。颜色/字号只在
App 与 H5 端生效;微信小程序的原生 danmu-list 只认 text / color / time,
字号与「自己的弹幕」描边在小程序上会被忽略(颜色照常生效)。
- 传列表用
danmakuText(JSON.stringify后的字符串):三端通用。 nvue 桥上数组 prop 不可靠(Android 侧拿到的长度恒为 0),所以约定字符串。 - 发送弹幕:先把新弹幕合入
danmakuText——Android 端原生层会自动补放 最近几秒的弹幕;H5 端组件检测到「新出现且紧贴当前播放位置」的弹幕也会 立即飘一条。sendDanmaku()只是 H5 上的手动即时反馈,不调也能看到。 自己发的条目记得带self: true(服务端由userId判断),组件会给它 加浅色半透明底 + 白色细描边,颜色仍按color渲染。 - 弹幕开关由用户点控制栏切换,通过
danmakutoggle事件回传新状态; 页面也可以改danmakuEnabledprop 从外部控制。
Props
| 属性 | 类型 | 默认值 | 平台 | 说明 |
|---|---|---|---|---|
| src | String | - | 三端 | 视频地址(必填) |
| poster | String | "" | H5/小程序 | 封面图(Android 端忽略,首帧前的封面请用页面层的 <image> 盖一层) |
| autoplay | Boolean | false | 三端 | 自动播放 |
| loop | Boolean | false | 三端 | 循环播放 |
| muted | Boolean | false | 三端 | 静音 |
| objectFit | String | "contain" | H5/小程序 | contain / fill / cover;Android 端固定 contain 语义,传了也忽略 |
| seekTime | Number | 0 | 三端 | 续播点(秒)。开播后自动跳一次;Android 端后续改这个 prop 会再跳 |
| danmakuText | String | "" | 三端 | 弹幕列表的 JSON 字符串 [{id,timeSec,content,color?,fontSize?,self?}]。三端通用,推荐 |
| danmakuList | Array | [] | H5/小程序 | 弹幕列表的数组形态;danmakuText 非空时优先于它 |
| danmakuEnabled | Boolean | true | 三端 | 弹幕层开关 |
| danmakuStyle | String | "" | 三端 | 弹幕显示样式覆盖 'color,fontSize'(如 '#ffd23f,22'):非空时画面上所有弹幕立即按它渲染(样式面板「即时生效」走的就是它);空串回到逐条样式 |
| rates | Array / String | [](内置六档) | 三端 | 倍速档位列表。H5/小程序可传数组 [0.5,1,2] 或逗号字符串 '0.5,1,2';Android 端只接字符串(数组 prop 跨 nvue 桥不可靠)。单个值过滤到 (0, 4],全部非法时回退内置六档 |
| themeColor | String | H5: "#2f7355" Android: ""(用原生内置默认色) |
三端 | 主题色:进度条已播段与滑块;Android 端还作用于倍速面板滚动条 |
| title | String | "" | 三端 | 视频标题。只在全屏时显示在顶部条(返回键右侧);内联态不显示(页面自己有标题栏) |
| videoId | String/Number | "dumuqiao-video" | 三端 | 底层 video 节点 id,同页多个播放器时区分用 |
Events
| 事件 | 平台 | 说明 |
|---|---|---|
| play / pause / ended | 三端 | 播放状态变化 |
| error | 三端 | 播放失败。Android 端 payload 是 {"detail":{"message":"..."}} 字符串 |
| timeupdate | 三端 | 播放进度。H5/小程序为原生 event(event.detail.currentTime / duration,秒);Android 端 event.detail 是 JSON 字符串(含 currentTime/duration/positionMs/playing),需 JSON.parse |
| fullscreenchange | 三端 | 全屏状态。H5/小程序为对象 {fullScreen, direction};Android 端为 JSON 字符串 {"fullScreen":true,"direction":"horizontal"} |
| danmakutoggle | 三端 | 弹幕开关被切换。H5/小程序为对象 {enabled};Android 端为 JSON 字符串 {"enabled":true} |
| exitstart / exitend | 仅 Android | 退出全屏动画开始 / 彻底结束。转场与黑底都由组件内部处理,页面一般不需要接;仅当页面要在动画期间联动自己的视觉(如提前恢复背景色)时才用 |
Android 端事件负载统一是 JSON 字符串:nvue 桥上对象负载到不了 JS 层, 这是 uts 的已知限制。直接用「快速开始」里的
parsePayload统一剥成对象, 三端事件就能走同一套处理代码。
Methods(ref 调用)
| 方法 | 平台 | 说明 |
|---|---|---|
| play() / pause() / stop() | 三端 | 播放控制 |
| seek(sec) | 三端 | 跳转到指定秒 |
| getCurrentTime() | 三端 | 同步取当前播放秒数(发弹幕 / 上报进度用,不等 timeupdate) |
| requestFullScreen(direction) | 三端 | 进全屏。direction 传 "horizontal" / "vertical";Android 端按视频宽高比自动判定,参数仅保持签名;H5 端浏览器要求由用户手势触发,否则会静默失败 |
| exitFullScreen() | 三端 | 退全屏 |
| setPlaybackRate(rate) | 三端 | 设置倍速(0.5~2) |
| handleSeek() | H5/小程序 | 跳到 seekTime 续播点并播放。Android 端无此方法(seekTime prop 变化时原生层已自动 seek)。handelSeek 是历史拼写,保留兼容 |
| sendDanmaku({content, timeSec, color?, fontSize?}) | 仅 H5 | 即时飘一条弹幕(按自己发送的高亮样式渲染)。小程序端是空操作(弹幕走原生 danmu-list);Android 端无此方法——把新弹幕合入 danmakuText 即可 |
| togglePlay() / toggleDanmaku() | H5/小程序 | 状态取反。Android 端无此方法(播放/弹幕开关用 play()/pause() 与 danmakuEnabled prop) |
没有
setThemeColor方法:运行时改主题色直接改themeColorprop, 两端都有 watch 会实时生效。
平台差异速查
| 能力 | App-Android | H5 | 微信小程序 |
|---|---|---|---|
| 播放内核 | MediaPlayer(uts 原生) | <video> |
原生 <video> |
| 弹幕渲染 | 原生弹幕层(33ms 帧循环) | CSS 动画层 | 原生 danmu-list |
| 全屏 | 原生 90° 旋转动画 + 全屏覆盖层(组件内部接管,页面零布局代码) | 容器 requestFullscreen | video requestFullScreen |
| 缓冲指示 | 转圈 + 实时加载速度 | 转圈 + 实时加载速度 | 原生 video 自带加载态 |
| 全屏顶部条(返回+标题) | ✓(随控制栏显隐) | ✓(常驻,控制栏不收起) | ✗ |
| 弹幕数据 prop | 仅 danmakuText | danmakuText / danmakuList | danmakuText / danmakuList |
| 弹幕颜色 | ✓(逐条) | ✓(逐条) | ✓(逐条,走原生 danmu-list 的 color) |
| 弹幕字号 / 自己的弹幕描边 | ✓ | ✓ | ✗(原生 danmu-list 不支持,忽略这两个字段) |
| sendDanmaku | ✗(合入 danmakuText 即可) | ✓ | ✗(空操作) |
| handleSeek | ✗(seekTime 自动生效) | ✓ | ✓ |
| 页面类型要求 | 仅 nvue 页面 | 无 | 无 |
常见问题
Q:App 端运行后播放器是黑屏 / 找不到组件? 按顺序查三件事:
- uts 原生插件必须用自定义基座或云打包运行(标准基座不含插件代码);
- 组件必须放在 nvue 页面;
- 必须用
:style给显式宽高——原生组件不参与 flex 排版, 不给宽高就是 0×0,既不播也不报错。
Q:自定义基座怎么做? HBuilderX 菜单「运行 → 运行到手机或模拟器 → 制作自定义调试基座」,等打包完成后再 「运行到 Android App 基座」并勾选「使用自定义基座运行」。改了 uts 源码后需要重新制作。 本插件零三方依赖,打基座不需要配置 Maven 仓库或联网拉原生包。
Q:nvue 里传了 prop 但组件拿到的是默认值?
prop 绑定写成了 kebab-case。nvue 的 uts 组件只认 camelCase(:danmakuText),
改成 camelCase 即可。
Q:Android 端 timeupdate / fullscreenchange 的 detail 字段全是 undefined?
Android 端事件负载是 JSON 字符串,先 JSON.parse 再取值。
推荐用「快速开始」里的 parsePayload 统一处理,三端共用一套代码。
Q:弹幕传了 danmakuList / 数组,Android 端一条都不飘?
nvue 桥上数组 prop 到原生侧长度恒为 0(uts 限制)。列表统一走 danmakuText
(JSON 字符串),三端都能用。
Q:全屏时按返回键,页面直接关掉了 / 退出后尺寸不对?
页面没有拦截返回键。接 fullscreenchange 记下全屏态,onBackPress 里先调
exitFullScreen() 并 return true(见上文「全屏:页面不用写任何尺寸逻辑」)。
尺寸不需要页面处理——组件退出全屏时会把视图接回原来的父容器与原布局参数。
Q:H5 端点全屏没反应?
浏览器的 requestFullscreen 必须由用户手势触发。从控制栏按钮点进来是满足的;
如果你在定时器 / 接口回调里调 requestFullScreen(),会被浏览器拒绝(组件已静默处理)。
Q:主题色怎么在运行时改?
改 themeColor prop(两端都有 watch)。没有也不需要 setThemeColor 方法。
Q:iOS 支持吗?
暂未适配。utssdk/app-ios 后续会用 AVPlayer 补同一套契约;目前 iOS 请自行降级
(例如用条件编译换成官方 video 组件)。
已知限制
- iOS 未适配(见上)。
- Android 端
objectFit固定 contain:cover/fill 需要裁剪 Surface,传了会被忽略。 - Android 端
poster忽略:首帧前的封面在页面层用<image>盖在组件上即可, 起播后由页面隐藏。 - Android 端全屏旋转方向固定 ±90°:不按传感器判断手机朝向;用户反向横握时 画面可能上下颠倒(后续版本按传感器补方向)。
- Android 端事件负载均为 JSON 字符串(uts 桥限制),页面侧按上文解析。
- 微信小程序的弹幕字号 / 自己的弹幕描边不生效:原生
danmu-list只支持text / color / time三个字段,颜色照常生效;需要完整样式请用 App 或 H5。
目录结构
uni_modules/dumuqiao-video/
├── package.json # 插件清单
├── readme.md
├── changelog.md
├── components/
│ └── dumuqiao-video/ # H5 / 微信小程序实现(easycom 自动注册)
│ ├── dumuqiao-video.vue
│ └── danmaku-scheduler.ts # H5 弹幕调度(按播放进度上屏)
├── static/
│ └── dumuqiao-video/ # 控制栏图标(随插件分发,不依赖宿主 static)
│ ├── play.png / pause.png / fullscreen.png
└── utssdk/
├── interface.uts # 三端共享的数据 / 回调类型
└── app-android/ # Android 原生实现
├── index.vue # 组件入口(NVLoad 返回原生 View)
├── index.uts # MediaPlayer 封装、控制栏、倍速面板、全屏转场
├── DanmakuView.uts # 弹幕层(FrameLayout + TextView)
└── config.json # 仅 minSdkVersion 21,无三方依赖
实现原理与排障笔记(进阶)
组件内部的排障记录(uts 桥限制、nvue 限制、全屏转场几何、弹幕时序等) 以源码注释为准;若你不需要改源码,上面的「常见问题」已覆盖使用层的全部坑。

收藏人数:
下载插件并导入HBuilderX
下载示例项目ZIP
赞赏(0)
下载 0
赞赏 0
下载 12703700
赞赏 1957
赞赏
京公网安备:11010802035340号