更新记录
1.2.42(2026-06-06)
- iOS 媒体卡按键最终收口:
initPlayerByIos()原生初始化入口直接强制autoRegisterRemoteCommand = true,不再根据options.autoRegisterRemoteCommand推导全局开关,避免 HBuilderX 5.07 生成层把可选 boolean 再次落成false后跳过MPRemoteCommandCentertarget 注册。 - iOS 媒体卡诊断增强:新增常开
traceIosMediaCard(),并补充媒体卡 wrapper 默认注册已强制开启、媒体卡原生初始化强制注册、媒体卡 target 注册开始/完成等日志;真机初始化后应看到autoRegister=true和 target token 状态。 - iOS 云打包兼容:
clearRemoteCommandTargets()通过 Swift helper 使用let allTargets: Any? = nil清理旧 target;iOSOptionsImpl构造器不再使用默认= null,内部兜底调用显式传入带上下文的null,避免云端 Swift 生成裸nil。 - iOS 媒体卡 token 保活:强引用保存
MPRemoteCommand.addTarget(handler:)返回的remotePlayTarget / remotePauseTarget / remoteStopTarget / remoteTogglePlayPauseTarget / remoteNextTarget / remotePrevTarget / remoteSeekTargettoken,避免 handler 被释放后状态栏播放、暂停、上一首、下一首点击无效。 - iOS 媒体卡按键二次修复:
MPRemoteCommandCenter.addTarget(handler:)返回的是 iOS 原生 token,不一定是UTSCallback,注册前不再依赖instanceof UTSCallback才移除旧 target。 - iOS 远程命令注册增强:每次初始化/重新注册媒体卡命令前,通过 Swift helper 对
play / pause / stop / playPause / next / prev / seek对应的系统命令统一执行removeTarget(nil),清理差量编译或重复初始化残留的旧 handler,避免旧 handler 先返回 success 后导致状态栏媒体卡点击无效。 - iOS 媒体卡执行链路保持原生优先:播放、暂停、停止、上一首、下一首和 playPause 仍由 iOS 平台层先直接消费命令,再上报
remoteCommand且标记handled=true,页面只作为日志和业务观察方。 - 发布日志与诊断标记同步:示例页和插件内置完整示例构建标记更新为
2026-06-06-bg-audio-1.2.42-native-force-remote-register,iOS 原生 trace 标记更新为lizhao-bg-audio/1.2.42-ios-native-force-remote-register@2026-06-06。 package.json、uni_modules.json和 README 当前版本统一升级为1.2.42;发布 iOS 包时请重新云打包或重新制作/安装包含本轮 iOS UTS 生成物的自定义基座。
1.2.41(2026-06-06)
- iOS 发布确认版:针对初始化成功后页面按钮不再触发
success / fail / complete、点击skipToIndexOne闪退的问题,固定发布到1.2.41,便于客户区分 1.2.40 诊断包与本次可发布包。 - iOS typed bridge 回调链路收口:
play / pause / resume / stop / seek / setTrack / setPlaylist / appendTrack / skipToIndex / getPlayerState / getCapabilities等入口继续保留强类型桥接字段优先读取,确保页面按文档传入回调时能真实触发。 - iOS
skipToIndex稳定性确认:目标曲加载完成后先返回success / complete,再延迟一拍自动播放,避免同一原生命令栈内嵌套播放导致闪退。 - 发布日志与诊断标记同步:示例页和插件内置完整示例构建标记更新为
2026-06-06-bg-audio-1.2.41-release,iOS 原生 trace 标记更新为lizhao-bg-audio/1.2.41-ios-typed-callback-release@2026-06-06。 package.json、uni_modules.json和 README 当前版本统一升级为1.2.41;发布 iOS 包时请重新云打包或重新制作/安装包含本轮 iOS UTS 生成物的自定义基座。
1.2.39(2026-06-04)
- 合并 Android 歌单加载最终修复:重新制作 Android 自定义基座后,用户已确认点击【初始化】->【加载歌单】流程正常,不再出现
9012002 tracks 为必填项。 - Android
SetPlaylistBridgeOptions.tracks保持Array<any>,并保留tracksPayloadJson普通 JSON 快照兜底,避免 HBuilderX 5.07 ByJs 对对象数组字段预包装或读取不稳定时丢失歌单。 - Android
setPlaylist({ tracks, tracksPayloadJson, success, fail })恢复强类型回调桥接,稳定生成 success/fail/complete callback wrapper;页面侧现在可收到setPlaylist success,返回当前下标与队列长度。 - Android
setPlaylist()增加首曲装载成功回调兜底:内部setTrack已完成但未回传success时,短延迟确认队列已设置并主动返回currentIndex / queueLength。 - Android 错误详情补充
hasTracksField与tracksPayloadJsonLength,用于区分页面未传参、JS 资源未更新、原生基座未更新或桥接字段丢失。 - iOS 同步支持
tracksPayloadJson,并保持逐项归一化入队,避免公共桥接类型放宽后影响 iOS 编译和运行稳定性。 - 示例页“加载歌单”输出
setPlaylist 参数确认日志,包含曲目数量、曲目 id 与 JSON 快照长度;构建标记统一为2026-06-04-bg-audio-1.2.39-android-release。 - 插件
example/backgroundAudio.vue新增完整演示页,客户下载插件后可直接复制到pages/backgroundAudio/backgroundAudio.vue使用;README 同步补充页面注册与自定义基座说明。 - 更新 Android 队列守卫、回调桥接守卫与版本守卫,锁定对象数组兜底、强类型回调桥接和
1.2.39发布版本,防止后续回退。 package.json与uni_modules.json版本号统一为1.2.39;发布 Android 包时请使用包含本轮 Android 原生 UTS 生成物的新自定义基座。
平台兼容性
uni-app(4.84)
| Vue2 | Vue3 | Chrome | Safari | app-vue | app-nvue | Android | iOS | 鸿蒙 |
|---|---|---|---|---|---|---|---|---|
| √ | √ | √ | √ | √ | √ | √ | √ | √ |
| 微信小程序 | 支付宝小程序 | 抖音小程序 | 百度小程序 | 快手小程序 | 京东小程序 | 鸿蒙元服务 | QQ小程序 | 飞书小程序 | 小红书小程序 | 快应用-华为 | 快应用-联盟 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| √ | √ | - | - | - | - | - | - | - | - | - | - |
uni-app x(4.84)
| Chrome | Safari | Android | iOS | 鸿蒙 | 微信小程序 |
|---|---|---|---|---|---|
| √ | √ | √ | √ | √ | √ |
lizhao-bg-audio
lizhao-bg-audio 是一个纯 UTS 背景音频播放插件,统一封装初始化、播放控制、播放队列、系统媒体卡、锁屏/通知栏控制、播放进度、播放完成回调和错误码。
适合音乐、电台、有声书、课程音频、长音频播放等场景。
当前版本:1.2.42
先看最小接入
- 从插件根目录导入。
- 在页面生命周期或用户点击方法中调用
initPlayer()初始化。 - 初始化成功后调用
setTrack()设置音频,再调用play()播放。
<template>
<view>
<button @click="initAndPlay">初始化并播放</button>
</view>
</template>
<script>
import * as BgAudio from "@/uni_modules/lizhao-bg-audio";
const = (event) => {
console.log("播放完成回调", event.payload);
};
export default {
onLoad() {
// 事件监听建议先注册,避免初始化或起播后立刻派发的事件被页面错过。
BgAudio.on("ended", );
},
onUnload() {
BgAudio.off("ended", );
},
methods: {
initAndPlay() {
const track = {
id: "demo-1",
url: "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3",
title: "SoundHelix Song 1",
singer: "SoundHelix"
};
BgAudio.initPlayer({
success: () => {
BgAudio.setTrack({
track,
success: () => {
BgAudio.play({
startPosition: 0,
fail: (err) => {
console.log("播放失败", err.errCode, err.errMsg);
}
});
},
fail: (err) => {
console.log("设置音频失败", err.errCode, err.errMsg);
}
});
},
fail: (err) => {
console.log("初始化失败", err.errCode, err.errMsg);
}
});
}
}
};
</script>
只能 import 插件根目录:
@/uni_modules/lizhao-bg-audio。不要 import 到utssdk/index.uts。 App 端建议使用success: () => {}/fail: (err) => {}这种字段箭头函数写法,并把调用放在页面方法或生命周期内;不要在<script>顶层直接执行initPlayer()。 iOS 端同样支持各 API options 内的success / fail / complete回调,也支持在BgAudio.on()事件回调里继续写日志、查询状态或触发业务逻辑;更新插件 iOS UTS 原生逻辑后,需要重新打 iOS 自定义基座后再验证这些回调。
插件内置示例页
插件包内已提供可直接复制使用的示例页面:
| 文件 | 说明 |
|---|---|
uni_modules/lizhao-bg-audio/example/index.vue |
最小接入示例,适合快速看初始化、播放、暂停和下一首。 |
uni_modules/lizhao-bg-audio/example/backgroundAudio.vue |
完整演示页,包含三键媒体卡、加载歌单、播放控制、倍速、循环、能力探测、智能下一曲预加载和日志输出。 |
客户下载插件后,如需使用完整演示页,可以把 uni_modules/lizhao-bg-audio/example/backgroundAudio.vue 复制到项目的 pages/backgroundAudio/backgroundAudio.vue,并在 pages.json 中加入:
{
"path": "pages/backgroundAudio/backgroundAudio",
"style": {
"navigationBarTitleText": "背景音频演示"
}
}
示例页已按插件根目录导入:import * as BgAudio from '@/uni_modules/lizhao-bg-audio';。如果要验证 Android 通知栏媒体卡、后台播放、iOS 锁屏控制或 Harmony 后台音频,请使用包含插件原生配置的新自定义基座或正式包。
支持平台
| 平台 | 是否支持 | 说明 |
|---|---|---|
| uni-app | 是 | Vue2 / Vue3 可用 |
| uni-app x | 是 | App / Web / 小程序按平台能力适配 |
| Android | 是 | 原生 MediaPlayer + MediaSession + AudioFocus + 前台服务 + 通知栏媒体控制 |
| iOS | 是 | 原生 AVPlayer + AVAudioSession + MPRemoteCommandCenter + MPNowPlayingInfoCenter,最低 iOS 12.0 |
| Harmony | 是 | HBuilderX 5.07 已验证 AVPlayer + AVSession,支持播放控制、队列、媒体卡、跳转、倍速和后台音频连续任务 |
| Web | 是 | HTMLAudioElement,支持浏览器 MediaSession 时同步媒体信息 |
| 微信小程序 | 是 | 优先后台音频管理器,不可用时降级 |
| 支付宝小程序 | 是 | 优先后台音频管理器,不可用时降级 |
常用能力
| 能力 | API |
|---|---|
| 初始化播放器 | initPlayer(options) |
| 设置单曲 | setTrack(options) |
| 设置歌单 | setPlaylist(options) |
| 播放 / 暂停 / 继续 / 停止 | play(options) / pause(options) / resume(options) / stop(options) |
| 跳转进度 | seek(options) |
| 倍速 / 音量 / 循环 | setPlaybackRate(options) / setVolume(options) / setLoopMode(options) |
| 上一首 / 下一首 / 指定下标 | skipToPrev(options) / skipToNext(options) / skipToIndex(options) |
| 状态 / 队列 / 能力探测 | getPlayerState(options) / getQueueState(options) / getCapabilities(options) |
| 事件监听 | on(eventName, callback) / off(eventName, callback) |
| 智能下一曲预加载 | initPlayer({ preloadNext: true }) + on("preloadNext") + appendTrack(options) |
| iOS 诊断 | getDebugTrace() / clearDebugTrace() / getCrashReports() / clearCrashReports() |
推荐调用顺序
on 绑定事件
-> initPlayer
-> setTrack 或 setPlaylist
-> play
-> pause / resume / seek / skipToNext
-> stop
-> off 解绑事件
完整示例
下面示例同时适用于 uni-app 和 uni-app x。页面可以直接复制使用,再替换成自己的音频地址。
<template>
<view class="page">
<view class="title">背景音频示例</view>
<view class="state">{{ stateText }}</view>
<view class="track">{{ trackText }}</view>
<view class="time">{{ timeText }}</view>
<view class="buttons">
<button type="primary" @click="initAndLoad">初始化并加载歌单</button>
<button @click="play">播放</button>
<button @click="pause">暂停</button>
<button @click="resume">继续</button>
<button @click="stop">停止</button>
<button @click="seek30">跳转 30 秒</button>
<button @click="prev">上一首</button>
<button @click="next">下一首</button>
<button @click="rate15">1.5 倍速</button>
<button @click="volume80">音量 80%</button>
<button @click="loopAll">列表循环</button>
<button @click="readState">读取状态</button>
<button @click="readQueue">读取队列</button>
<button @click="readCapabilities">能力探测</button>
</view>
<scroll-view class="logs" scroll-y>
<text>{{ logsText }}</text>
</scroll-view>
</view>
</template>
<script>
import * as BgAudio from "@/uni_modules/lizhao-bg-audio";
export default {
data() {
return {
inited: false,
stateText: "未初始化",
trackText: "未加载曲目",
currentTime: 0,
duration: 0,
logs: [],
progressHandler: null,
stateHandler: null,
endedHandler: null,
errorHandler: null,
remoteHandler: null
};
},
computed: {
logsText() {
return this.logs.join("\n");
},
timeText() {
return `${this.formatTime(this.currentTime)} / ${this.formatTime(this.duration)}`;
}
},
onUnload() {
this.unbindEvents();
},
methods: {
initAndLoad() {
BgAudio.initPlayer({
// 调试时开启插件内部日志,正式包建议保持默认关闭。
debug: false,
// iOS:允许系统音频中断结束后自动恢复。
autoRecover: true,
// iOS:注册锁屏、控制中心、耳机线控等远程控制。
autoRegisterRemoteCommand: true,
// Android:通知栏媒体按钮,默认推荐三键。
notificationActions: ["prev", "playPause", "next"],
success: (res) => {
this.inited = true;
this.stateText = "初始化成功";
this.log("初始化成功 " + JSON.stringify(res));
this.bindEvents();
this.setPlaylist();
},
fail: (err) => {
this.handleFail("初始化", err);
}
});
},
setPlaylist() {
BgAudio.setPlaylist({
tracks: [
{
id: "demo-1",
url: "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3",
title: "SoundHelix Song 1",
singer: "SoundHelix",
album: "Demo",
cover: ""
},
{
id: "demo-2",
url: "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3",
title: "SoundHelix Song 2",
singer: "SoundHelix",
album: "Demo",
cover: ""
}
],
startIndex: 0,
success: (res) => {
this.log("歌单加载成功 " + JSON.stringify(res));
this.readState();
},
fail: (err) => {
this.handleFail("加载歌单", err);
}
});
},
play() {
if (!this.ensureReady()) return;
BgAudio.play({
startPosition: 0,
success: () => {
this.stateText = "播放中";
this.log("播放成功");
},
fail: (err) => {
this.handleFail("播放", err);
}
});
},
pause() {
if (!this.ensureReady()) return;
BgAudio.pause({
success: () => {
this.stateText = "已暂停";
this.log("暂停成功");
},
fail: (err) => {
this.handleFail("暂停", err);
}
});
},
resume() {
if (!this.ensureReady()) return;
BgAudio.resume({
success: () => {
this.stateText = "继续播放";
this.log("继续播放成功");
},
fail: (err) => {
this.handleFail("继续播放", err);
}
});
},
stop() {
if (!this.ensureReady()) return;
BgAudio.stop({
success: () => {
this.currentTime = 0;
this.stateText = "已停止";
this.log("停止成功");
},
fail: (err) => {
this.handleFail("停止", err);
}
});
},
seek30() {
if (!this.ensureReady()) return;
BgAudio.seek({
position: 30,
success: () => {
this.currentTime = 30;
this.log("已跳转到 30 秒");
},
fail: (err) => {
this.handleFail("跳转进度", err);
}
});
},
prev() {
if (!this.ensureReady()) return;
BgAudio.skipToPrev({
success: (res) => {
this.log("上一首成功 " + JSON.stringify(res));
this.readState();
},
fail: (err) => {
this.handleFail("上一首", err);
}
});
},
next() {
if (!this.ensureReady()) return;
BgAudio.skipToNext({
success: (res) => {
this.log("下一首成功 " + JSON.stringify(res));
this.readState();
},
fail: (err) => {
this.handleFail("下一首", err);
}
});
},
rate15() {
if (!this.ensureReady()) return;
BgAudio.setPlaybackRate({
playbackRate: 1.5,
success: () => {
this.log("倍速已设置为 1.5");
},
fail: (err) => {
this.handleFail("设置倍速", err);
}
});
},
volume80() {
if (!this.ensureReady()) return;
BgAudio.setVolume({
volume: 0.8,
success: () => {
this.log("音量已设置为 80%");
},
fail: (err) => {
this.handleFail("设置音量", err);
}
});
},
loopAll() {
if (!this.ensureReady()) return;
BgAudio.setLoopMode({
loopMode: "all",
success: () => {
this.log("已设置为列表循环");
},
fail: (err) => {
this.handleFail("设置循环", err);
}
});
},
readState() {
if (!this.ensureReady()) return;
BgAudio.getPlayerState({
success: (state) => {
this.applyState(state);
this.log("状态 " + JSON.stringify(state));
},
fail: (err) => {
this.handleFail("读取状态", err);
}
});
},
readQueue() {
if (!this.ensureReady()) return;
BgAudio.getQueueState({
success: (state) => {
this.log("队列 " + JSON.stringify(state));
},
fail: (err) => {
this.handleFail("读取队列", err);
}
});
},
readCapabilities() {
BgAudio.getCapabilities({
success: (res) => {
this.log("能力 " + JSON.stringify(res));
},
fail: (err) => {
this.handleFail("能力探测", err);
}
});
},
bindEvents() {
this.unbindEvents();
this.progressHandler = (event) => {
const payload = event.payload || {};
this.currentTime = Number(payload.currentTime || 0);
this.duration = Number(payload.duration || this.duration || 0);
};
this.stateHandler = (event) => {
const payload = event.payload || {};
this.log("状态回调 " + JSON.stringify(payload));
};
this.endedHandler = (event) => {
this.stateText = "播放完成";
this.log("播放完成回调 " + JSON.stringify(event.payload || {}));
};
this.errorHandler = (event) => {
this.log("错误回调 " + JSON.stringify(event.payload || {}));
};
this.remoteHandler = (event) => {
this.log("媒体卡控制回调 " + JSON.stringify(event.payload || {}));
};
BgAudio.on("progress", this.progressHandler);
BgAudio.on("stateChange", this.stateHandler);
BgAudio.on("ended", this.endedHandler);
BgAudio.on("error", this.errorHandler);
BgAudio.on("remoteCommand", this.remoteHandler);
},
unbindEvents() {
if (this.progressHandler != null) BgAudio.off("progress", this.progressHandler);
if (this.stateHandler != null) BgAudio.off("stateChange", this.stateHandler);
if (this.endedHandler != null) BgAudio.off("ended", this.endedHandler);
if (this.errorHandler != null) BgAudio.off("error", this.errorHandler);
if (this.remoteHandler != null) BgAudio.off("remoteCommand", this.remoteHandler);
this.progressHandler = null;
this.stateHandler = null;
this.endedHandler = null;
this.errorHandler = null;
this.remoteHandler = null;
},
applyState(state) {
if (state == null) return;
this.currentTime = Number(state.currentTime || 0);
this.duration = Number(state.duration || 0);
this.stateText = "当前状态:" + (state.status || "unknown");
if (state.currentTrack != null) {
this.trackText = `${state.currentTrack.title || ""} ${state.currentTrack.singer || ""}`;
}
},
ensureReady() {
if (this.inited) return true;
this.log("请先初始化播放器");
return false;
},
handleFail(action, err) {
const code = err && err.errCode ? err.errCode : "";
const msg = err && err.errMsg ? err.errMsg : "";
this.log(`${action}失败 ${code} ${msg}`);
},
log(text) {
this.logs.unshift(new Date().toLocaleTimeString() + " " + text);
if (this.logs.length > 40) this.logs.pop();
},
formatTime(second) {
const value = Math.max(0, Math.floor(Number(second || 0)));
const minute = Math.floor(value / 60);
const remain = value % 60;
return minute + ":" + (remain < 10 ? "0" + remain : remain);
}
}
};
</script>
<style>
.page {
padding: 28rpx;
}
.title {
font-size: 36rpx;
font-weight: 600;
margin-bottom: 16rpx;
}
.state,
.track,
.time {
margin-bottom: 12rpx;
color: #333;
}
.buttons button {
margin-top: 16rpx;
}
.logs {
height: 320rpx;
margin-top: 24rpx;
padding: 20rpx;
background: #f7f7f7;
border-radius: 12rpx;
font-size: 24rpx;
white-space: pre-wrap;
}
</style>
单曲播放示例
import * as BgAudio from "@/uni_modules/lizhao-bg-audio";
const track = {
id: "single-1",
url: "https://download.samplelib.com/mp3/sample-3s.mp3",
title: "Sample 3s"
};
BgAudio.initPlayer({
success: () => {
BgAudio.setTrack({
track,
success: () => {
BgAudio.play({
startPosition: 0,
fail: (err) => {
console.log("播放失败", err.errCode, err.errMsg);
}
});
},
fail: (err) => {
console.log("设置音频失败", err.errCode, err.errMsg);
}
});
},
fail: (err) => {
console.log("初始化失败", err.errCode, err.errMsg);
}
});
歌单播放示例
import * as BgAudio from "@/uni_modules/lizhao-bg-audio";
const tracks = [
{
id: "track-1",
url: "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3",
title: "第一首"
},
{
id: "track-2",
url: "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3",
title: "第二首"
}
];
function appendTracks(index) {
if (index >= tracks.length) {
return;
}
BgAudio.appendTrack({
track: tracks[index],
success: () => {
appendTracks(index + 1);
},
fail: (err) => {
console.log("追加队列失败", err.errCode, err.errMsg);
}
});
}
BgAudio.initPlayer({
success: () => {
BgAudio.setTrack({
track: tracks[0],
success: () => {
appendTracks(1);
BgAudio.play({
startPosition: 0,
fail: (err) => {
console.log("播放失败", err.errCode, err.errMsg);
}
});
},
fail: (err) => {
console.log("设置首曲失败", err.errCode, err.errMsg);
}
});
},
fail: (err) => {
console.log("初始化失败", err.errCode, err.errMsg);
}
});
Android / Harmony / Web / 小程序等非 iOS 平台推荐优先使用
setPlaylist({ tracks, startIndex })一次性加载完整歌单,避免依赖多次appendTrack回调链导致尾曲未入队。仅当旧 iOS 自定义基座存在数组桥接兼容问题时,再使用上面的setTrack + appendTrack逐首追加写法。
播放完成与媒体卡事件示例
import * as BgAudio from "@/uni_modules/lizhao-bg-audio";
const = (event) => {
console.log("播放完成", event.payload);
};
const onRemote = (event) => {
console.log("系统媒体控制", event.payload);
};
BgAudio.on("ended", );
BgAudio.on("remoteCommand", onRemote);
// 页面销毁时解绑,避免重复进入页面后回调触发多次。
BgAudio.off("ended", );
BgAudio.off("remoteCommand", onRemote);
API 说明
initPlayer(options)
初始化播放器。App 端会创建原生音频会话、媒体卡或远程控制能力。
| 参数 | 类型 | 必填 | 说明 | 默认值 | 可选参数 |
|---|---|---|---|---|---|
| options | object | 是 | 初始化参数对象 | 无 | debug / preloadNext / preloadNextTime / nextTrackTimeout / autoRecover / autoRegisterRemoteCommand / notificationActions / success / fail / complete |
| options.debug | boolean | 否 | 是否开启插件内部控制台日志;开启后 Android / iOS / Harmony 根入口会打印诊断日志 | false |
true / false |
| options.preloadNext | boolean | 否 | 是否开启智能下一曲预加载;开启后队列末尾临近结束会触发 preloadNext 事件 |
false |
true / false |
| options.preloadNextTime | number | 否 | 距离当前曲结束多少秒触发 preloadNext |
15 |
大于 0 的数值 |
| options.nextTrackTimeout | number | 否 | 当前曲播完后等待业务追加下一曲的超时时间,单位毫秒;0 表示不自动超时 |
0 |
大于等于 0 的数值 |
| options.autoRecover | boolean | 否 | iOS 音频中断结束后是否自动恢复播放 | true |
true / false |
| options.autoRegisterRemoteCommand | boolean | 否 | iOS 是否注册锁屏、控制中心、耳机线控命令 | true |
true / false |
| options.notificationActions | string[] | 否 | Android 通知栏媒体按钮顺序 | prev / playPause / next |
prev / rewind / playPause / forward / next |
| options.success | function | 否 | 初始化成功回调 | 无 | 无 |
| options.fail | function | 否 | 初始化失败回调 | 无 | 无 |
| options.complete | function | 否 | 初始化完成回调,成功失败都会触发 | 无 | 无 |
setTrack(options)
设置当前单曲和媒体元数据。
| 参数 | 类型 | 必填 | 说明 | 默认值 | 可选参数 |
|---|---|---|---|---|---|
| options | object | 是 | 设置曲目参数对象 | 无 | track / tracks / tracksPayloadJson / success / fail / complete |
| options.track | object | 是 | 曲目信息 | 无 | id / url / title / singer / album / cover / durationHint / headers / extras |
| options.tracks | array | 否 | 兼容字段:调用方把完整队列随 setTrack 一起传入时,插件会同步为播放队列;推荐新代码优先使用 setPlaylist |
无 | BackgroundAudioTrack[] |
| options.tracksPayloadJson | string | 否 | tracks 的 JSON 快照兜底字段,用于桥接层对象数组读取不稳定时恢复队列 |
无 | 无 |
| options.success | function | 否 | 设置成功回调 | 无 | 无 |
| options.fail | function | 否 | 设置失败回调 | 无 | 无 |
| options.complete | function | 否 | 设置完成回调 | 无 | 无 |
setPlaylist(options)
设置播放队列。
| 参数 | 类型 | 必填 | 说明 | 默认值 | 可选参数 |
|---|---|---|---|---|---|
| options | object | 是 | 设置队列参数对象 | 无 | tracks / startIndex / success / fail / complete |
| options.tracks | array | 是 | 曲目数组 | 无 | BackgroundAudioTrack[] |
| options.startIndex | number | 否 | 起始播放下标 | 0 |
大于等于 0 的整数 |
| options.success | function | 否 | 设置成功回调 | 无 | 无 |
| options.fail | function | 否 | 设置失败回调 | 无 | 无 |
| options.complete | function | 否 | 设置完成回调 | 无 | 无 |
play(options)
开始播放。
| 参数 | 类型 | 必填 | 说明 | 默认值 | 可选参数 |
|---|---|---|---|---|---|
| options | object | 是 | 播放参数对象 | 无 | startPosition / success / fail / complete |
| options.startPosition | number | 否 | 从指定秒数开始播放 | 当前进度 | 大于等于 0 的数值 |
| options.success | function | 否 | 播放成功回调 | 无 | 无 |
| options.fail | function | 否 | 播放失败回调 | 无 | 无 |
| options.complete | function | 否 | 播放完成回调,成功失败都会触发 | 无 | 无 |
pause(options) / resume(options) / stop(options)
暂停、继续、停止播放。
| 参数 | 类型 | 必填 | 说明 | 默认值 | 可选参数 |
|---|---|---|---|---|---|
| options | object | 是 | 控制参数对象 | 无 | success / fail / complete |
| options.success | function | 否 | 成功回调 | 无 | 无 |
| options.fail | function | 否 | 失败回调 | 无 | 无 |
| options.complete | function | 否 | 完成回调 | 无 | 无 |
seek(options)
跳转到指定播放进度。
| 参数 | 类型 | 必填 | 说明 | 默认值 | 可选参数 |
|---|---|---|---|---|---|
| options | object | 是 | 跳转参数对象 | 无 | position / success / fail / complete |
| options.position | number | 是 | 目标秒数 | 无 | 大于等于 0 的数值 |
| options.success | function | 否 | 跳转成功回调 | 无 | 无 |
| options.fail | function | 否 | 跳转失败回调 | 无 | 无 |
| options.complete | function | 否 | 跳转完成回调 | 无 | 无 |
setPlaybackRate(options)
设置播放倍速。Harmony 真机不再写入 BackgroundAudioManager.playbackRate,改由 AVPlayer.setSpeed() 执行;系统只支持固定档位时会按最近档位归一化。
| 参数 | 类型 | 必填 | 说明 | 默认值 | 可选参数 |
|---|---|---|---|---|---|
| options | object | 是 | 倍速参数对象 | 无 | playbackRate / success / fail / complete |
| options.playbackRate | number | 是 | 播放倍速 | 无 | 大于 0 的数值 |
| options.success | function | 否 | 成功回调 | 无 | 无 |
| options.fail | function | 否 | 失败回调 | 无 | 无 |
| options.complete | function | 否 | 完成回调 | 无 | 无 |
setVolume(options)
设置播放音量。部分平台由系统音量控制,可能返回不支持。
| 参数 | 类型 | 必填 | 说明 | 默认值 | 可选参数 |
|---|---|---|---|---|---|
| options | object | 是 | 音量参数对象 | 无 | volume / success / fail / complete |
| options.volume | number | 是 | 音量值 | 无 | 0 ~ 1 |
| options.success | function | 否 | 成功回调 | 无 | 无 |
| options.fail | function | 否 | 失败回调 | 无 | 无 |
| options.complete | function | 否 | 完成回调 | 无 | 无 |
setLoopMode(options)
设置循环模式。
| 参数 | 类型 | 必填 | 说明 | 默认值 | 可选参数 |
|---|---|---|---|---|---|
| options | object | 是 | 循环参数对象 | 无 | loopMode / success / fail / complete |
| options.loopMode | string | 是 | 循环模式 | 无 | off / single / all |
| options.success | function | 否 | 成功回调 | 无 | 无 |
| options.fail | function | 否 | 失败回调 | 无 | 无 |
| options.complete | function | 否 | 完成回调 | 无 | 无 |
appendTrack(options)
向当前队列追加曲目。
开启智能下一曲预加载并且播放器处于 waitingNext 状态时,业务接口拿到下一曲后调用 appendTrack(),插件会自动切到刚追加的下一曲并继续播放,成功回调会额外返回 autoPlay: true。
| 参数 | 类型 | 必填 | 说明 | 默认值 | 可选参数 |
|---|---|---|---|---|---|
| options | object | 是 | 追加参数对象 | 无 | track / success / fail / complete |
| options.track | object | 是 | 要追加的曲目信息 | 无 | BackgroundAudioTrack |
| options.success | function | 否 | 成功回调 | 无 | 无 |
| options.fail | function | 否 | 失败回调 | 无 | 无 |
| options.complete | function | 否 | 完成回调 | 无 | 无 |
removeTrack(options)
从队列移除指定曲目。
| 参数 | 类型 | 必填 | 说明 | 默认值 | 可选参数 |
|---|---|---|---|---|---|
| options | object | 是 | 移除参数对象 | 无 | trackId / success / fail / complete |
| options.trackId | string | 是 | 要移除的曲目 ID | 无 | 无 |
| options.success | function | 否 | 成功回调 | 无 | 无 |
| options.fail | function | 否 | 失败回调 | 无 | 无 |
| options.complete | function | 否 | 完成回调 | 无 | 无 |
skipToNext(options) / skipToPrev(options) / skipToIndex(options)
切换上一首、下一首或指定队列下标。
skipToIndex的success / complete表示目标曲目已装载并同步队列下标;插件会随后自动尝试播放,避免 iOS 因播放二级回调未返回而丢失本次切歌回调。
| 参数 | 类型 | 必填 | 说明 | 默认值 | 可选参数 |
|---|---|---|---|---|---|
| options | object | 是 | 切歌参数对象 | 无 | index / success / fail / complete |
| options.index | number | 否 | 目标队列下标,仅 skipToIndex 必填 |
无 | 大于等于 0 的整数 |
| options.success | function | 否 | 成功回调 | 无 | 无 |
| options.fail | function | 否 | 失败回调 | 无 | 无 |
| options.complete | function | 否 | 完成回调 | 无 | 无 |
getPlayerState(options) / getQueueState(options) / getCapabilities(options)
读取播放器状态、队列状态和平台能力。
| 参数 | 类型 | 必填 | 说明 | 默认值 | 可选参数 |
|---|---|---|---|---|---|
| options | object | 是 | 查询参数对象 | 无 | success / fail / complete |
| options.success | function | 否 | 成功回调 | 无 | 无 |
| options.fail | function | 否 | 失败回调 | 无 | 无 |
| options.complete | function | 否 | 完成回调 | 无 | 无 |
on(eventName, callback) / off(eventName, callback)
订阅和取消订阅播放器事件。
| 参数 | 类型 | 必填 | 说明 | 默认值 | 可选参数 |
|---|---|---|---|---|---|
| eventName | string | 是 | 事件名称 | 无 | stateChange / progress / buffer / error / ended / preloadNext / waitingNext / nextTrackTimeout / remoteCommand / interruption |
| callback | function | 是 | 事件回调 | 无 | 无 |
事件说明:
| 事件 | 触发时机 |
|---|---|
stateChange |
播放状态变化 |
progress |
播放进度更新,频率较高 |
buffer |
缓冲状态变化 |
error |
播放器错误 |
ended |
当前曲目播放完成 |
preloadNext |
当前曲接近结束且队列暂无下一曲,提示业务提前请求下一曲 |
waitingNext |
当前曲已播完且业务下一曲尚未追加,播放器进入等待下一曲状态 |
nextTrackTimeout |
等待业务追加下一曲超过 nextTrackTimeout |
remoteCommand |
系统媒体卡、锁屏、耳机线控命令 |
interruption |
iOS 音频中断等系统事件 |
智能下一曲预加载
适合“下一曲需要先请求业务接口”的场景,例如有声书、课程音频、付费内容鉴权或动态推荐。开启后,插件会在当前曲快结束且队列里还没有下一曲时提前触发 preloadNext;如果当前曲播完接口还没返回,会进入 waitingNext 状态等待。业务拿到下一曲后调用 appendTrack(),插件会自动续播。
import * as BgAudio from "@/uni_modules/lizhao-bg-audio";
BgAudio.initPlayer({
preloadNext: true,
preloadNextTime: 20,
nextTrackTimeout: 15000,
success() {
console.log("背景音频初始化成功");
}
});
BgAudio.on("preloadNext", async (event) => {
const currentTrack = event.payload.currentTrack;
// 这里换成你的接口请求,例如根据 currentTrack.extras 或 currentTrack.id 获取下一集。
const nextTrack = await requestNextTrack(currentTrack);
if (nextTrack != null) {
BgAudio.appendTrack({
track: nextTrack,
success(res) {
console.log("下一曲已追加", res);
}
});
}
});
BgAudio.on("waitingNext", () => {
console.log("当前曲已播完,正在等待下一曲接口返回");
});
BgAudio.on("nextTrackTimeout", (event) => {
console.log("等待下一曲超时", event.payload);
});
注意事项:
preloadNext只在loopMode = "off"且当前播放到队列末尾时触发;队列里已经有下一曲时,插件会直接续播已有下一曲。- 如果业务先调用
setPlaylist()或连续appendTrack()把下一曲提前放进队列,当前曲临近结束时不会触发preloadNext,这是正常行为;只有“当前曲就是队列最后一首、插件确实缺少下一曲”时才需要通知业务请求接口。 - 测试
preloadNext时请确保队列只有当前这一首,例如先调用setPlaylist({ tracks: [currentTrack], startIndex: 0 })清掉旧队列;如果之前加载过完整歌单,旧的下一曲仍在队列中,会导致看不到preloadNext回调。 - Harmony 真机调试时建议参考示例页“加载单曲预加载测试”:示例页会用唯一
id和唯一url调用setTrack(),确保当前曲就是队列最后一首。若设备或基座中setPlaylist()的数组桥接不稳定,也可以采用这种方式重置为单曲队列。 - “跳到预加载触发点”只是把进度移动到阈值附近,真正触发还需要已经开启
preloadNext、当前曲有有效duration、loopMode = "off",并且队列里没有下一曲;满足条件后事件可能在 seek 完成或后续播放进度回调中立即触发。 preloadNext对同一首曲目只触发一次,避免进度频繁上报导致接口重复请求。nextTrackTimeout默认0,表示一直等待;业务希望超时展示重试按钮时可设置为10000或15000。- 如果业务主动切歌、重新设置歌单、停止播放或移除等待中的曲目,插件会清理本次等待状态。
getDebugTrace() / clearDebugTrace() / getCrashReports() / clearCrashReports()
iOS 诊断 API,用于读取和清理持久化调试轨迹。正常业务一般不需要调用。
| 参数 | 类型 | 必填 | 说明 | 默认值 | 可选参数 |
|---|---|---|---|---|---|
| 无 | 无 | 否 | 无参数 | 无 | 无 |
类型说明
BackgroundAudioTrack
| 字段 | 类型 | 说明 |
|---|---|---|
| id | string | 曲目唯一 ID |
| url | string | 音频地址,支持远程地址和平台支持的本地地址 |
| title | string | 曲目标题 |
| singer | string | 歌手名称,可选 |
| album | string | 专辑名称,可选 |
| cover | string | 封面图地址,可选 |
| durationHint | number | 时长兜底值,单位秒,可选 |
| headers | object | 请求头扩展,按平台支持情况透传 |
| extras | object | 业务扩展字段,插件不解释 |
BackgroundAudioPlayerState
| 字段 | 类型 | 说明 |
|---|---|---|
| initialized | boolean | 是否已初始化 |
| status | string | idle / ready / playing / paused / stopped / ended / waitingNext / buffering |
| currentTrack | object/null | 当前曲目 |
| currentTime | number | 当前播放秒数 |
| duration | number | 总时长秒数 |
| buffered | number | 已缓冲秒数 |
| playbackRate | number | 当前倍速 |
| volume | number | 当前音量 |
| loopMode | string | off / single / all |
| queueIndex | number | 当前队列下标 |
| queueLength | number | 队列长度 |
| backgroundActive | boolean | 是否处于后台活跃播放态 |
BackgroundAudioCapabilities
| 字段 | 类型 | 说明 |
|---|---|---|
| supported | boolean | 当前平台是否支持 |
| sessionLevel | string | native / manager / fallback / none |
| backgroundAudio | boolean | 是否支持后台音频 |
| mediaSession | boolean | 是否支持媒体会话元数据 |
| remoteCommand | boolean | 是否支持系统媒体控制命令 |
| queue | boolean | 是否支持队列 |
| playbackRate | boolean | 是否支持倍速 |
| volume | boolean | 是否支持音量 |
| platform | string | 平台标识 |
| adapter | string | 适配器标识 |
| requiresCustomBase | boolean | 是否建议自定义基座 |
错误码
| 错误码 | 含义 | 说明 |
|---|---|---|
| 9012001 | 当前平台不支持 | 当前平台没有可用播放能力 |
| 9012002 | 参数不合法 | 缺少必填参数或参数范围错误 |
| 9012003 | 运行时未初始化 | 调用播放 API 前未先调用 initPlayer() |
| 9012004 | 音源不可用 | 曲目缺失、URL 为空或音源不可访问 |
| 9012005 | 播放失败 | 原生播放器启动或播放失败 |
| 9012006 | 跳转位置非法 | seek 秒数不合法 |
| 9012007 | 能力不支持 | 当前平台或当前环境不支持该能力,例如倍速、音量或远程控制 |
| 9012008 | 缺少后台音频能力 | 原生音频会话或后台能力不可用 |
权限与配置
| 平台 | 是否支持 | 说明 |
|---|---|---|
| Android | 是 | 需要网络权限;使用前台服务、媒体通知、Android 13+ 通知权限时建议自定义基座 |
| iOS | 是 | 最低 iOS 12.0;建议配置 UIBackgroundModes: audio,Windows 标准基座真机运行无法使用 iOS UTS 插件 |
| Harmony | 是 | 使用 AVPlayer + AVSession 启用播放、跳转、倍速、系统媒体卡和后台音频连续任务;需要声明 backgroundModes: ["audioPlayback"] 与 ohos.permission.KEEP_BACKGROUND_RUNNING,缺失后台管理器、AVPlayer 或媒体会话时返回错误 |
| Web | 是 | 受浏览器自动播放和后台策略限制 |
| 微信小程序 | 是 | 建议配置 requiredBackgroundModes: ["audio"] |
| 支付宝小程序 | 是 | 按支付宝小程序后台音频规则配置 |
Harmony 后台音频连续任务配置
Harmony 息屏后仍要保持播放和系统媒体卡可用时,必须在鸿蒙工程 harmony-configs/entry/src/main/module.json5 的 EntryAbility 中配置 backgroundModes: ["audioPlayback"],并在 requestPermissions 中声明 ohos.permission.INTERNET 和 ohos.permission.KEEP_BACKGROUND_RUNNING。
示例(只展示关键字段):
{
"module": {
"abilities": [
{
"name": "EntryAbility",
"backgroundModes": [
"audioPlayback"
]
}
],
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
},
{
"name": "ohos.permission.KEEP_BACKGROUND_RUNNING"
}
]
}
}
INTERNET 用于网络音频访问;KEEP_BACKGROUND_RUNNING 与 audioPlayback 用于配合插件内部后台音频连续任务申请,让息屏播放和媒体卡控制在系统后台策略下继续可用。修改该配置后必须重新打鸿蒙自定义基座或运行包。
Harmony 短音/瞬态音频焦点策略
Harmony 上架检测会检查短音、瞬态音播放场景是否使用 share / duck / pause 等不会永久打断后台音乐的焦点策略。插件的 Harmony 原生链路会在 AVPlayer 首次 prepare() / play() 前激活 AudioSessionManager 的 CONCURRENCY_MIX_WITH_OTHERS 并发策略,同时显式设置 audioInterruptMode = SHARE_MODE 和 audioRendererInfo.usage = STREAM_USAGE_MUSIC,让后台音乐按系统策略并发播放或在短暂暂停后自动恢复,避免被识别为 stop 独占焦点策略。
是否需要自定义基座
| 场景 | 是否需要 | 说明 |
|---|---|---|
| Android 原生媒体通知、前台服务、通知权限 | 建议 | 自定义基座或云打包能确保 Manifest 配置完整生效 |
| iOS 后台音频、锁屏媒体卡、控制中心 | 建议 | 最低 iOS 12.0;需要后台音频能力配置,标准基座能力有限 |
| Harmony 后台音频连续任务 | 是 | 需要 Harmony 工程在 EntryAbility 声明 backgroundModes: ["audioPlayback"],并声明 ohos.permission.KEEP_BACKGROUND_RUNNING;更新后必须重新打自定义基座或运行包 |
| Web / 小程序基础播放 | 否 | 主要受平台策略限制 |
| 新增三方 SDK、原生资源、权限声明 | 是 | 需要重新打自定义基座 |
目录结构
uni_modules/
└─ lizhao-bg-audio/
├─ package.json
├─ readme.md
├─ changelog.md
├─ example/
│ ├─ index.vue
│ ├─ backgroundAudio.vue
│ └─ readme.md
└─ utssdk/
├─ interface.uts
├─ unierror.uts
├─ index.uts
├─ app-android/index.uts
├─ app-ios/index.uts
├─ app-harmony/index.uts
├─ web/index.uts
├─ mp-weixin/index.uts
└─ mp-alipay/index.uts
注意事项
- 先
initPlayer(),再调用播放、队列、状态 API。 progress事件频率较高,页面日志建议采样展示。on()和off()需要使用同一个回调函数引用。- iOS 中文 URL 会做安全编码归一化,但仍建议业务侧使用可直接访问的 HTTPS 音频地址。
setVolume()、setPlaybackRate()在部分平台可能受系统策略限制,建议先用getCapabilities()判断;Harmony 通过AVPlayer.setSpeed()支持常见固定倍速档位。- Harmony 后台保活依赖
module.json5中的backgroundModes: ["audioPlayback"]和ohos.permission.KEEP_BACKGROUND_RUNNING;配置变化后必须重新打鸿蒙自定义基座或运行包。
联系方式
信-微:l-z-1-8-7-1512-5421(-去掉,不这样写会被和谐)

收藏人数:
购买源码授权版(
试用
赞赏(1)
下载 6483
赞赏 5
下载 12177017
赞赏 1918
赞赏
京公网安备:11010802035340号