更新记录
1.0.1(2026-06-30)
新增ios android 获取设备存储空间大小的功能
1.0.0(2026-06-30)
新增ios android m3u8视频下载,http 服务方便下载后播放,支持key 在http服务器里边解密进行播放
平台兼容性
uni-app(5.14)
| Vue2 | Vue3 | Chrome | Safari | app-vue | app-nvue | Android | iOS | 鸿蒙 |
|---|---|---|---|---|---|---|---|---|
| √ | √ | - | - | - | - | - | - | - |
| 微信小程序 | 支付宝小程序 | 抖音小程序 | 百度小程序 | 快手小程序 | 京东小程序 | 鸿蒙元服务 | QQ小程序 | 飞书小程序 | 小红书小程序 | 快应用-华为 | 快应用-联盟 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| - | - | - | - | - | - | - | - | - | - | - | - |
uni-app x(5.14)
| Chrome | Safari | Android | iOS | 鸿蒙 | 微信小程序 |
|---|---|---|---|---|---|
| - | - | 5.0 | 13 | - | - |
M3U8 视频下载与本地播放(puke-m3u8-downloader)
UTS 插件,App 端(Android / iOS)下载 M3U8 视频(key + ts 分片),并通过本地 HTTP 服务离线播放,支持加密 key 在服务端解密、防盗播。
适用于在线教育、网课、点播等需要在 App 内「下载到本地、无网络也能看」的场景。双端原生实现,性能与稳定性均经过线上项目验证。
✨ 特性
- 双端原生:Android(NanoHTTPD)+ iOS(GCDWebServer)两套原生实现,体验一致。
- 完整下载链路:解析 M3U8 → 下载 key + ts 分片 → 重写 playlist → 本地播放,一条龙。
- 本地 HTTP 服务离线播放:下载完成后启动本地 HTTP 服务,把本地 m3u8 喂给系统播放器即可播放。
- 加密 key 服务端解密(防盗播):对 32 字节的加密 key,在 HTTP 服务返回时取奇数索引位还原成 16 字节 AES key,磁盘上的原始 key 文件不外泄,防止用户拷贝 ts 直接播放。
- 断点续传:已下载的 ts 分片自动跳过;App 被杀掉重启后,用原 m3u8 地址再次
startDownload即可续传,不会重复下载。 - 完整传输控制:开始 / 批量开始 / 暂停 / 继续 / 取消 / 删除。
- 全局进度监听:
onProgress回调返回状态、百分比、已下载 / 总字节数、错误信息,内置节流,避免高频刷 JS 桥导致卡顿。 - 并发控制:最多 2 个视频并行下载,单视频内顺序下载分片,对移动端网络友好。
- 协议兼容:兼容 master playlist(自动选最高码率变体)、手动跟随 HTTP→HTTPS 重定向。
- 安全降级:非 App 平台通过
js_sdk包装层安全降级,isSupport()返回false,调用不崩溃,方便同一套代码多端编译。
📱 支持平台
本插件为 uni-app-x 专用 UTS 插件(旧版 uni-app / 5+App 不支持)。
| 平台 | 是否支持 | 说明 |
|---|---|---|
| uni-app-x · App-Android | ✅ | minVersion 21 |
| uni-app-x · App-iOS | ✅ | minVersion 13 |
| H5 / 各家小程序 / 快应用 | ❌ | 受限于本地文件与本地 HTTP 服务能力,仅 App 端可用 |
引擎要求:HBuilderX ^3.6.8、uni-app-x ^5.14。
小程序 / H5 端调用时会安全降级,不会报错崩溃,可在业务层用
isSupport()判断后再展示「下载」入口。
🚀 快速开始
插件是 UTS 插件,不要用 uni.requireNativePlugin(那是 5+App 纯原生插件的加载方式,会报「当前运行的基座不包含原生插件」)。直接 import js_sdk 即可,HBuilderX 编译 App 时会按平台解析 utssdk 实现,随标准基座 / 正式包一起编译,无需制作自定义基座。
import m3u8Downloader from '@/uni_modules/puke-m3u8-downloader/js_sdk'
最小可用三步:
- App 启动时启动本地 HTTP 服务(推荐在
App.vue的onLaunch调用,幂等); - 注册一次全局进度监听
onProgress(贯穿 App 生命周期); - 调用
startDownload开始下载。
📖 完整使用示例
<script setup>
// #ifdef APP-PLUS
import m3u8Downloader from '@/uni_modules/puke-m3u8-downloader/js_sdk'
// #endif
// 进度回调(全局只注册一次,建议放在 App.vue)
const onDownloadProgress = (res) => {
// res: { courseVideoId, status, progress, downloadedBytes, totalBytes, error? }
console.log(res.courseVideoId, res.status, res.progress + '%')
}
onLoad(() => {
// #ifdef APP-PLUS
// 1. 启动本地 HTTP 服务(默认端口 19685,可省略 port)
if (m3u8Downloader.isSupport()) {
m3u8Downloader.startLocalServer({ port: 19685 })
}
// 2. 注册进度监听
m3u8Downloader.onProgress(onDownloadProgress)
// #endif
})
// 开始下载单个视频
const startDownload = () => {
// #ifdef APP-PLUS
m3u8Downloader.startDownload({
courseVideoId: 'video_001', // 视频唯一 ID,作为本地缓存目录名
m3u8Url: 'https://example.com/index.m3u8', // M3U8 播放地址
title: '第一课 · 课程导学' // 可选,仅用于日志/状态展示
})
// #endif
}
// 批量下载
const startBatchDownload = () => {
// #ifdef APP-PLUS
m3u8Downloader.startBatchDownload({
videos: [
{ courseVideoId: 'video_001', m3u8Url: 'https://example.com/1/index.m3u8', title: '第一课' },
{ courseVideoId: 'video_002', m3u8Url: 'https://example.com/2/index.m3u8', title: '第二课' }
]
})
// #endif
}
// 下载完成后,取本地播放地址喂给 <video> 播放
const playLocal = (courseVideoId) => {
// #ifdef APP-PLUS
m3u8Downloader.getLocalPlayUrl({
courseVideoId,
success: (res) => {
// res.url 形如:http://127.0.0.1:19685/video_001/playlist.m3u8
// 直接把 res.url 赋值给 video 的 src 即可离线播放
console.log('本地播放地址:', res.url)
},
fail: (err) => {
console.error(err.errMsg) // 例:本地视频不存在
}
})
// #endif
}
onUnload(() => {
// #ifdef APP-PLUS
m3u8Downloader.offProgress(onDownloadProgress) // 移除指定回调;传 null 移除全部
// 如不再需要本地播放,可停掉服务(一般 App 退出由系统回收,无需手动停)
// m3u8Downloader.stopLocalServer()
// #endif
})
</script>
📘 API 文档
所有方法在非 App 端均为安全降级(空操作或回调默认值),不会抛错。
isSupport()
当前平台是否支持下载 / 本地播放。App 端返回 true,其他平台返回 false。
startLocalServer(options)
启动本地 HTTP 服务(用于本地 M3U8 播放)。
options.port?:服务端口,默认19685。已启动则幂等返回。
必须先启动本地服务,下载与播放才可用。推荐在
App.vue的onLaunch调用。
stopLocalServer()
停止本地 HTTP 服务。
startDownload(options)
开始下载单个视频。
options.courseVideoId:视频唯一 ID,作为本地缓存目录名(String)。options.m3u8Url:完整的 M3U8 播放地址(String)。options.title?:视频标题,仅用于日志 / 状态展示。
startBatchDownload(options)
开始批量下载,内部逐个调用 startDownload,受全局并发控制(最多 2 个并行)。
options.videos:StartDownloadOptions[]列表。
pauseDownload(options) / resumeDownload(options)
options.courseVideoId:暂停 / 继续指定视频下载。
cancelDownload(options)
options.courseVideoId:取消下载(不删本地文件,已下载分片保留)。
deleteDownload(options)
options.courseVideoId:删除该视频的本地缓存目录与任务记录。
getDownloadStatus(options)
查询下载状态。
options.courseVideoIdoptions.success(res):res = { status, progress, downloadedBytes?, totalBytes? }options.fail(err)/options.complete(res)
getLocalPlayUrl(options)
获取本地播放地址(下载完成后调用)。
options.courseVideoIdoptions.success(res):res = { url },形如http://127.0.0.1:19685/<courseVideoId>/playlist.m3u8options.fail(err):本地视频不存在等错误,err.errMsgoptions.complete(res)
getStorageInfo(options)
获取离线缓存所在卷的可用 / 总空间,便于下载前做容量判断。
options.success(res):res = { availableBytes, totalBytes }options.fail(err)/options.complete(res)
getCachedSize(options)
获取单个课程视频缓存目录的实际占用大小。
options.courseVideoIdoptions.success(res):res = { courseVideoId, bytes }options.fail(err)/options.complete(res)
onProgress(callback) / offProgress(callback)
注册 / 移除全局下载进度监听。
offProgress传null移除全部监听。
status 状态枚举
| 值 | 含义 |
|---|---|
none |
无任务 / 已取消 |
pending |
已加入队列,等待下载 |
downloading |
下载中 |
paused |
已暂停 |
completed |
下载完成 |
failed |
下载失败 |
DownloadProgress 回调字段
| 字段 | 类型 | 说明 |
|---|---|---|
courseVideoId |
string | 视频唯一 ID |
status |
string | 见上表状态枚举 |
progress |
number | 进度 0–100 |
downloadedBytes |
number | 已下载字节数 |
totalBytes |
number | 总字节数(注意:为避免阻塞,未做全分片预扫,下载过程中该值可能为 0,仅用于进度展示) |
error? |
string | 失败时的错误信息 |
📁 本地文件目录结构
下载的文件统一存放在应用沙盒内(Android 为外部存储,iOS 为 Documents),根目录名为 school_videos:
<应用存储>/school_videos/<courseVideoId>/
├── playlist.m3u8 # 重写后的本地 playlist,ts 行已替换成本地文件名,key 的 URI 替换为 "key"
├── segment_000.ts # ts 分片,序号固定 3 位补零(padIndex)
├── segment_001.ts
├── ...
└── key # 加密 key 原始文件(32 或 16 字节,磁盘上保持原样不外泄)
本地播放 URL 形如:http://127.0.0.1:19685/<courseVideoId>/playlist.m3u8
本地 HTTP 服务在返回 key 时会按规则解密:若磁盘 key 为 32 字节,取奇数索引位 raw[1,3,...,31] 还原成 16 字节 AES key 返回给播放器;16 字节则原样返回。磁盘文件始终不变,防盗播。
⚠️ 注意事项 / FAQ
- 仅支持 App 端:小程序 / H5 不支持本地文件与本地 HTTP 服务,请用
isSupport()判断后再展示下载入口。 - 必须先
startLocalServer:下载与本地播放都依赖该服务,推荐在App.vue的onLaunch启动(幂等,可重复调用)。 onProgress全局只注册一次:建议放在 App 生命周期内,避免页面切换时重复注册导致回调多次触发。- App 被杀后任务表丢失:原生任务表是内存态,App 被系统杀死后丢失。重启后用原
m3u8Url再次startDownload即可续传(已下载的 ts 分片会自动跳过)。 - 自定义端口:
startLocalServer({ port })与getLocalPlayUrl内部默认使用同一端口(19685),自定义端口请保持一致,否则取到的播放地址会无法访问。 - 字段类型:
courseVideoId/m3u8Url/title必须是字符串。js_sdk已做一层String()防护,但直接调用 UTS 时请确保类型正确,避免as!强转导致的 native crash。 - key 解密规则需与服务端对齐:默认按 32 字节取奇数索引位得 16 字节 AES key。若你的加密方案不同,需修改两端
readKeyBytesForPlayer/ 等价实现。
🔗 开发参考
📝 更新日志
详见 changelog.md。
- 1.0.0(2026-06-30):新增 iOS / Android M3U8 视频下载,内置 HTTP 服务方便下载后播放,支持 key 在 HTTP 服务里解密进行播放。

收藏人数:
购买普通授权版(
试用
赞赏(0)
下载 1
赞赏 0
下载 12506890
赞赏 1942
赞赏
京公网安备:11010802035340号