更新记录

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 六档弹出菜单,面板可滚动并自动定位当前档。
  • 断点续播:seekTime prop + handleSeek() 方法。
  • 主题色:themeColor prop 一处配置进度条已播段 / 滑块 / 倍速面板滚动条颜色。
  • 暂停时画面正中显示大播放键;拖动进度条时画面正中显示目标时间预览(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 六档,可用 rates prop 动态换成任意档位列表;Android 端面板可滚动并自动定位当前档
  • 断点续播:seekTime prop,开播后自动跳一次
  • 主题色:themeColor 一处配置进度条已播段 / 滑块 / 倍速面板滚动条颜色
  • 缓冲指示:卡顿 / 首帧加载时画面正中显示转圈 + 实时加载速度(1.2 MB/s), 恢复播放立即自动隐藏
  • 全屏顶部条:title prop 传入视频标题,全屏时与返回键一起显示在顶部, 跟随控制栏一起显隐(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 锁回竖屏兜底。

安装

  1. 从插件市场导入(或把 uni_modules/dumuqiao-video 复制到你的项目)即可, easycom 自动注册,无需在 pages.json 配置。
  2. App 端:uts 原生插件必须制作「自定义基座」后才能运行(HBuilderX 菜单 「运行 → 运行到手机或模拟器 → 制作自定义调试基座」),正式发版走云打包。
  3. 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 事件回传新状态; 页面也可以改 danmakuEnabled prop 从外部控制。

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 方法:运行时改主题色直接改 themeColor prop, 两端都有 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 端运行后播放器是黑屏 / 找不到组件? 按顺序查三件事:

  1. uts 原生插件必须用自定义基座或云打包运行(标准基座不含插件代码);
  2. 组件必须放在 nvue 页面;
  3. 必须用 :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 限制、全屏转场几何、弹幕时序等) 以源码注释为准;若你不需要改源码,上面的「常见问题」已覆盖使用层的全部坑。

隐私、权限声明

1. 本插件需要申请的系统权限列表:

无

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件不采集任何数据

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

无

许可协议

MIT协议

暂无用户评论。