更新记录

1.0.0(2026-09-10)

首个正式版本,提供可上架售卖的核心播放能力。

新增

  • 跨端架构:采用 UTS uni-app 兼容模式组件(utssdk/app-android/index.vueutssdk/app-ios/index.vue),面向传统 uni-app(vue2/vue3)的 nvue 页面,同时兼容 uni-app x 的 VDOM 模式。
  • 播放内核
    • Android:Media3 / ExoPlayer(Apache-2.0),支持 mp4、HLS(m3u8)、DASH、SmoothStreaming。
    • iOS:系统 AVPlayer / AVPlayerLayer(AVFoundation),支持 mp4、HLS(m3u8)。
  • 媒体源:在线点播(mp4)、在线/直播流(m3u8)、本地沙盒视频文件。
  • 播放控制方法play() / pause() / stop() / seekTo(ms) / setRate(rate) / getCurrentTime() / getDuration() / setMute(bool) / enterFullScreen() / exitFullScreen()
  • Propssrc / title / poster / autoplay / loop / muted / isLive / playRate / showControl / objectFit / controlAutoHide / showTitle / showPoster / showFullscreenBtn
  • 事件onReady / onPlay / onPause / onEnded / onBuffering / onError(code,message) / onProgress(current,duration) / onFullscreenChange / onRateChange
  • 内置 UI 控制面板:进度条(可 seek)、播放/暂停、全屏、倍速(0.5~2.0)、静音、标题栏、封面图、控件自动隐藏、可单独隐藏任意控件。
  • 画面缩放objectFit 支持 contain / cover / fill。
  • 音频焦点:Android 通过 AudioManager 音频焦点 + 前后台生命周期,iOS 通过 AVAudioSession + 中断/前后台通知,实现切后台、来电自动暂停,切回前台自动恢复。
  • 健壮性:页面销毁时释放播放器、移除监听器/定时器/通知,防止内存泄漏;播放异常统一走 onError 回调,不崩溃。
  • 共享契约utssdk/interface.uts 统一双端事件名、错误码、负载字段,保证两端行为一致。
  • 示例与文档:提供 demo/ 下 nvue 示例(入口导航、mp4 点播、m3u8 直播)、完整 readme.mdlicense.md、独立测试文档 TEST.md

修复与优化(真机测试期)

  • 页面经 $refs 调用的全部组件方法改为派发原生主线程执行,修复按钮失灵、全屏卡死、返回后页面导航异常。
  • play() 自愈:IDLE 态(stop() 或播放出错后)自动重新 prepare(),ENDED 态(播完)自动回起点重播;iOS 端 AVPlayerItem 处于 failed 时自动重建媒体项。修复“播放按钮间歇性失灵”。
  • Android 本地无协议头绝对路径自动补 file://;demo 新增「选本地视频」(uni.chooseVideo + plus.io 路径转换)。
  • 直播 demo 更换为 Apple 官方 HLS / Mux / Akamai 三源(国内更可达)。
  • demo 点播页不再放置「停止」「取进度」按钮(对应 API stop() / getCurrentTime() / getDuration() 保留)。

已知限制

  • 仅 App 端(Android / iOS)可用;小程序、H5、快应用、鸿蒙不支持
  • 兼容模式组件须在 nvue 页面中使用;不支持组合式 API(仅选项式 API)。
  • H.265/HEVC 硬解依赖设备硬件,老旧机型可能无声或无画面。
  • 普通授权仅支持云打包并绑定 AppID;离线打包需源码授权;试用仅限自定义基座。

后续阶段规划(本版本不含)

  • 阶段二:选集切换、边播边缓存(最大缓存设置 + clearCache)、全屏横竖屏完善。
  • 阶段三:弹幕、截屏保存相册、画中画悬浮小窗。
  • 阶段四:DLNA 局域网投屏。

平台兼容性

uni-app(5.24)

Vue2 Vue2插件版本 Vue3 Vue3插件版本 Chrome Safari app-vue app-nvue app-nvue插件版本 Android Android插件版本 iOS iOS插件版本 鸿蒙
1.0.0 1.0.0 × × × 1.0.0 5.0 1.0.0 14 1.0.0 ×
微信小程序 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 小红书小程序 快应用-华为 快应用-联盟
× × × × × × × × × × × ×

全能视频播放器 omni-media-player

纯 UTS 兼容模式视频播放器组件 · Android 基于 Media3-ExoPlayer(Apache-2.0) · iOS 基于系统 AVPlayer · 无 GPL 风险 · 可安全用于商业闭源 App

omni-media-player 是一款面向 uni-app 的原生视频播放器组件,使用 UTS(uni type script)编写,编译为纯原生代码(Android→Kotlin,iOS→Swift)。它内置了完整的播放控制面板,支持点播、直播、本地视频、倍速、全屏、音频焦点等能力,开箱即用。


✨ 核心优势

优势 说明
无 GPL 风险 Android 采用 Media3-ExoPlayer(Apache-2.0),iOS 采用系统 AVPlayer,均可闭源商用,区别于使用 ijkplayer(GPL)的方案
🚀 纯 UTS 原生 编译为原生代码,性能接近纯原生播放器,非 webview 模拟
🎛️ 内置控制面板 进度条、播放/暂停、全屏、倍速、静音、标题、封面、自动隐藏,全部可配置、可单独隐藏
📡 点播 + 直播 支持 mp4 点播、m3u8(HLS)点播/直播、本地沙盒视频
🔊 音频焦点 切后台、来电自动暂停,切回自动恢复(双端一致)
🧩 easycom 零配置 导入后在 nvue 页面直接写 <omni-media-player> 标签即可
🛡️ 健壮防泄漏 页面销毁自动释放资源;异常统一走 onError,不崩溃

⚠️ 平台兼容性(务必先读)

本插件为 UTS 兼容模式组件,仅在 App 端可用:

平台 支持 说明
App-Android 基于 Media3-ExoPlayer,minSdkVersion 21(Android 5.0)+
App-iOS 基于 AVPlayer,deploymentTarget iOS 14.0+
App-鸿蒙(harmony) 暂不支持
H5 不支持
小程序(微信/支付宝等) 不支持
快应用 不支持

Vue 版本:兼容传统 uni-app 的 vue2 与 vue3

页面类型(关键约束)

  • 本组件必须在 nvue 页面中使用(.nvue 文件)。nvue 是原生渲染页面,兼容模式 UTS 组件依赖原生 View 承载。
  • 不支持在 vue 页面(webview 渲染)中使用
  • 组件仅支持选项式 APIexport default {}),不支持组合式 API<script setup> / setup())。
  • 同时兼容 uni-app x 的 VDOM 模式(如在 uni-app x 中使用,请参考 DCloud UTS 组件文档的 uni-app x 调用方式)。

🔐 授权与打包说明

授权类型 是否可查看源码 打包方式 适用场景
普通授权 否(DCloud 平台加密) 仅云打包,绑定购买者 AppID 大多数用户,价格低
源码授权 云打包 / 离线打包均可 需二次开发、需离线打包
  • 试用:仅支持运行到自定义基座进行试用,不能用于打正式包。
  • 普通授权购买后,插件源码由 DCloud 市场平台机制自动加密,绑定你的 AppID,无需自行处理加密。
  • 若需离线打包(本地集成 Android Studio / Xcode 工程),请购买源码授权

🚀 快速开始

📖 详细操作手册(demo 逐按钮说明、本地视频播放、全屏机制、FAQ)见 docs/OPERATION.md

1. 导入插件

在 HBuilderX 中,将本插件(omni-media-player)导入到你的项目 uni_modules/ 目录下。导入后目录结构:

你的项目/
├─ pages/
│  └─ index.nvue
├─ uni_modules/
│  └─ omni-media-player/     ← 插件本体
├─ pages.json
└─ manifest.json

uni_modules 组件支持 easycom 自动扫描,导入后无需手动 import 组件,直接在 nvue 页面使用标签即可。

2. 在 nvue 页面中使用

新建一个 .nvue 页面(注意不是 .vue),直接使用 <omni-media-player> 标签:

<template>
    <view class="page">
        <!-- 播放器组件:必须放在 nvue 页面中 -->
        <omni-media-player
            ref="player"
            class="player"
            :src="videoSrc"
            :title="视频标题"
            :autoplay="true"
            :loop="false"
            :muted="false"
            :playRate="1.0"
            :showControl="true"
            objectFit="contain"
            @ready="onReady"
            @play="onPlay"
            @pause="onPause"
            @ended=""
            @buffering="onBuffering"
            @error="onError"
            @progress=""
            @fullscreenchange=""
            @ratechange=""
        ></omni-media-player>

        <view class="btns">
            <text class="btn" @click="doPlay">播放</text>
            <text class="btn" @click="doPause">暂停</text>
            <text class="btn" @click="doSeek">跳到30秒</text>
        </view>
    </view>
</template>

<script>
    export default {
        data() {
            return {
                videoSrc: 'https://example.com/test.mp4'
            }
        },
        methods: {
            // 通过 this.$refs.player 调用组件方法
            doPlay() {
                this.$refs.player.play();
            },
            doPause() {
                this.$refs.player.pause();
            },
            doSeek() {
                this.$refs.player.seekTo(30000); // 单位:毫秒
            },
            // 事件回调(注:负载字段的读取存在跨端差异,详见下文「事件负载的读取方式」,推荐用 pv(e,key) 防御式读取)
            onReady(e) {
                console.log('准备完成,时长(ms):', e.duration);
            },
            onPlay() { console.log('开始播放'); },
            onPause() { console.log('暂停'); },
            () { console.log('播放结束'); },
            onBuffering(e) { console.log('缓冲中:', e.buffering); },
            onError(e) { console.log('播放错误:', e.code, e.message); },
            (e) { console.log('进度(ms):', e.current, '/', e.duration); },
            (e) { console.log('全屏:', e.isFullscreen); },
            (e) { console.log('倍速:', e.rate); }
        }
    }
</script>

<style>
    .page { flex: 1; background-color: #000000; }
    /* nvue 中组件需显式设置宽高 */
    .player { width: 750rpx; height: 422rpx; }
    .btns { flex-direction: row; padding: 20rpx; }
    .btn { color: #ffffff; background-color: #2979ff; padding: 12rpx 24rpx; margin-right: 16rpx; border-radius: 8rpx; }
</style>

nvue 布局提示:nvue 使用 weex/flex 布局,组件必须显式设置 width / height,否则可能不显示。rpx 在 nvue 中同样可用。


📋 Props 属性

属性名 类型 默认值 说明
src String "" 视频地址。支持:http(s):// 在线 mp4/m3u8、file:// 或沙盒路径的本地视频
title String "" 标题栏文字
poster String "" 封面图地址(http(s):// 或本地路径),播放前显示
autoplay Boolean false 是否自动播放
loop Boolean false 是否循环播放
muted Boolean false 是否静音
isLive Boolean false 是否直播。直播场景建议设为 true(作为直播语义标识;直播下进度条/seek 的精细呈现将于后续阶段完善)
playRate Number 1.0 播放倍速,范围 0.5 ~ 2.0
showControl Boolean true 是否显示内置控制条
objectFit String "contain" 画面缩放模式:contain(等比完整,可能黑边) / cover(等比铺满裁剪) / fill(拉伸铺满,可能变形)
controlAutoHide Number 3000 控制条自动隐藏时间(毫秒)
showTitle Boolean true 是否显示标题栏
showPoster Boolean true 是否显示封面
showFullscreenBtn Boolean true 是否显示全屏按钮

所有 Props 均支持运行时动态修改(组件内部 watch 实时生效)。例如动态改变 src 即可切换播放源。


🛠️ 方法(通过 this.$refs.xxx 调用)

方法 参数 返回 说明
play() 播放 / 继续播放
pause() 暂停
stop() 停止(回到起点并暂停,重新显示封面)
seekTo(positionMs) positionMs: Number(毫秒) 跳转到指定播放位置
setRate(rate) rate: Number(0.5~2.0) 设置播放倍速
getCurrentTime(callback) callback: (ms: Number) => void 无(结果经 callback 回传) 获取当前播放位置(毫秒)
getDuration(callback) callback: (ms: Number) => void 无(结果经 callback 回传) 获取总时长(毫秒,直播或未知回传 0)
setMute(muted) muted: Boolean 设置静音
enterFullScreen() 进入全屏
exitFullScreen() 退出全屏

命名说明:倍速/静音的方法名为 setRate / setMute(而非 setPlayRate / setMuted),因为 prop 已占用 playRate / muted;UTS 兼容模式组件会为每个 prop 自动生成同名 setter,方法若与之同名会产生 JVM 签名冲突。声明式改倍速/静音也可直接改 prop(:playRate / :muted),二者等效。

调用示例:

⚠️ nvue 重要约束:nvue(weex 引擎)不支持组件方法的同步返回值。因此 getCurrentTime / getDuration 采用 callback 回调形式返回结果,而非 return。此外,兼容模式组件不支持在组件标签上绑定 touch/click/tap 等事件(组件内部的按钮点击由原生层处理,不受影响)。

// 播放
this.$refs.player.play();
// 跳转到第 60 秒
this.$refs.player.seekTo(60000);
// 设置 1.5 倍速
this.$refs.player.setRate(1.5);
// 获取当前进度(nvue 不支持同步返回值,必须用 callback 形式)
this.$refs.player.getCurrentTime((cur) => {
    console.log('当前位置(ms):', cur);
});
this.$refs.player.getDuration((dur) => {
    console.log('总时长(ms):', dur);
});
// 静音
this.$refs.player.setMute(true);
// 全屏
this.$refs.player.enterFullScreen();

📡 事件

事件负载统一为对象(内部以 Map<string, any> 传递)。

事件名 回调参数 说明
@ready { duration } 播放器准备完成,可获取时长(毫秒)
@play 开始播放 / 继续播放
@pause 暂停
@ended 播放结束(非循环时触发)
@buffering { buffering } 缓冲状态变化,bufferingtrue/false
@error { code, message } 播放错误,见下方错误码
@progress { current, duration } 播放进度(单位毫秒),约每 0.5 秒触发一次
@fullscreenchange { isFullscreen } 全屏状态变化
@ratechange { rate } 倍速变化

错误码(error 事件的 code

code 含义 常见原因
1001 网络错误 断网、超时、拉流失败
1002 无效地址 src 为空、非法 URL、文件不存在
1003 解码/编码不支持 设备硬件不支持该编码(如部分机型不支持 H.265)
1999 未知错误 其他未归类异常

事件名均为全小写(如 fullscreenchangeratechange),以兼容早期 HBuilderX 对事件名的限制。

❗ 事件负载的读取方式(跨端差异,务必阅读)

事件负载在原生层统一以 Map<string, any> 发送。但在传统 uni-app(vue2/vue3)的 nvue 页面中,负载到达 JS 层的位置在不同平台/版本上可能不一致:

  • 部分情况下字段挂在事件对象本身:e.codee.current
  • 部分情况下(尤其 iOS)字段挂在 e.detail 下:e.detail.codee.detail.current

为保证双端一致,推荐用一个防御式小函数读取负载(demo 中已采用):

methods: {
    // 优先 e.detail[key],回退 e[key]
    pv(e, key) {
        if (e == null) return undefined;
        const d = e.detail;
        if (d != null && d[key] !== undefined) return d[key];
        if (e[key] !== undefined) return e[key];
        return undefined;
    },
    onError(e) {
        const code = this.pv(e, 'code');
        const message = this.pv(e, 'message');
        console.log('错误:', code, message);
    },
    (e) {
        const current = this.pv(e, 'current');
        const duration = this.pv(e, 'duration');
        console.log('进度:', current, '/', duration);
    }
}

负载 Map 的 value 仅支持 string / number / Map 等基础类型(iOS 限制),本插件所有负载字段均为 string/number/boolean,符合规范。若你使用 uni-app x,请参考 uni-app x 的事件接收方式(iOS 用 UniEvent,Android 用 Map)。


📺 支持的媒体格式

类型 Android(Media3) iOS(AVPlayer)
MP4 (H.264)
HLS / m3u8(点播+直播)
DASH ⚠️ 视系统而定
SmoothStreaming
本地沙盒视频
H.265 / HEVC ⚠️ 依赖硬件 ⚠️ 依赖硬件

实际可播放的编码/分辨率取决于设备硬件与系统解码能力,本插件不承诺支持全部格式。建议优先使用 H.264 + AAC 的 MP4 或标准 HLS 流以获得最佳兼容性。


❗ 使用限制与注意事项

  1. 仅 App 端:小程序、H5、快应用、鸿蒙均不支持。
  2. 仅 nvue 页面:必须在 .nvue 页面中使用,vue 页面(webview)无效。
  3. 仅选项式 API:不支持 <script setup> / 组合式 API。
  4. nvue 布局:组件必须显式设置宽高(flex 布局),否则可能不显示。
  5. 全屏说明:全屏通过将播放器视图临时挂载到窗口/DecorView 并切换横竖屏实现;退出全屏会恢复原布局。
  6. 音频焦点:组件会自动申请音频焦点,切后台/来电时暂停,回到前台恢复;这是预期行为。
  7. 打包限制:普通授权仅云打包并绑定 AppID;离线打包需源码授权;试用仅自定义基座。
  8. HBuilderX 版本:建议使用较新版本 HBuilderX(含较新 UTS 编译器),以确保 UTS 语法与 Media3 依赖正常编译。

❓ 常见问题(FAQ)

Q:为什么组件不显示 / 是黑屏? A:① 确认页面是 .nvue 而非 .vue;② 确认给组件设置了 width/height;③ 确认 src 地址可访问;④ 真机运行(模拟器可能无法解码)。

Q:为什么提示需要自定义基座? A:UTS 插件包含原生代码,标准基座不含本插件,必须制作自定义基座或云打包后才能运行。

Q:进度回调频率是多少? A:约每 0.5 秒一次(@progress)。如需更精细可自行调整。

Q:能播放 H.265 吗? A:取决于设备硬件解码能力。老旧机型可能无声或无画面,建议提供 H.264 源作为兜底。

Q:支持 DRM 加密视频吗? A:第一阶段暂不支持 DRM。


🗺️ 功能规划

  • 阶段一(当前版本):核心播放 + UI 控制面板 + 音频焦点 + 全屏。
  • 阶段二:选集切换、边播边缓存(最大缓存设置 + clearCache)、全屏横竖屏完善。
  • 阶段三:弹幕、截屏保存相册、画中画悬浮小窗。
  • 阶段四:DLNA 局域网投屏。

后续阶段将单独交付并同步更新本 readme 与 TEST.md。购买用户可获得对应阶段的更新。


📜 许可与免责

  • 本插件 Android 端使用 Media3-ExoPlayer(Apache License 2.0),iOS 端使用 Apple 系统 AVFoundation/AVPlayer,均可闭源商用,无 GPL 传染风险。详见 license.md
  • 本插件代码为独立实现,未抄袭任何第三方商业插件源码。
  • 本插件不采集、不上传任何用户隐私数据,播放与缓存等逻辑全部在设备本地执行。
  • 媒体内容的版权与合法性由使用者自行负责,本插件仅提供播放能力。
  • 作者不对使用者因使用本插件产生的任何业务损失承担责任。

📮 联系与支持

  • 使用问题、Bug 反馈、功能建议:请通过 DCloud 插件市场本插件页面的「联系作者」或评论区反馈。
  • 反馈时请附上:HBuilderX 版本、手机型号与系统版本、复现步骤、错误日志(@error 回调的 code/message)。

祝使用愉快 🎬

隐私、权限声明

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

网络访问(拉取在线视频/直播流)、音频焦点与音频设置(播放与来电/后台自动暂停恢复)、存储读写(播放本地沙盒视频、封面缓存);后续画中画功能需悬浮窗权限、DLNA 投屏需局域网权限

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

本插件不采集、不上传任何用户隐私数据,播放与缓存等全部逻辑在用户设备本地执行

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

暂无用户评论。