更新记录

1.2.42(2026-06-06)

  • iOS 媒体卡按键最终收口:initPlayerByIos() 原生初始化入口直接强制 autoRegisterRemoteCommand = true,不再根据 options.autoRegisterRemoteCommand 推导全局开关,避免 HBuilderX 5.07 生成层把可选 boolean 再次落成 false 后跳过 MPRemoteCommandCenter target 注册。
  • iOS 媒体卡诊断增强:新增常开 traceIosMediaCard(),并补充 媒体卡 wrapper 默认注册已强制开启媒体卡原生初始化强制注册媒体卡 target 注册开始/完成 等日志;真机初始化后应看到 autoRegister=true 和 target token 状态。
  • iOS 云打包兼容:clearRemoteCommandTargets() 通过 Swift helper 使用 let allTargets: Any? = nil 清理旧 target;iOS OptionsImpl 构造器不再使用默认 = null,内部兜底调用显式传入带上下文的 null,避免云端 Swift 生成裸 nil
  • iOS 媒体卡 token 保活:强引用保存 MPRemoteCommand.addTarget(handler:) 返回的 remotePlayTarget / remotePauseTarget / remoteStopTarget / remoteTogglePlayPauseTarget / remoteNextTarget / remotePrevTarget / remoteSeekTarget token,避免 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.jsonuni_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.jsonuni_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 错误详情补充 hasTracksFieldtracksPayloadJsonLength,用于区分页面未传参、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.jsonuni_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

先看最小接入

  1. 从插件根目录导入。
  2. 在页面生命周期或用户点击方法中调用 initPlayer() 初始化。
  3. 初始化成功后调用 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)

切换上一首、下一首或指定队列下标。

skipToIndexsuccess / 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、当前曲有有效 durationloopMode = "off",并且队列里没有下一曲;满足条件后事件可能在 seek 完成或后续播放进度回调中立即触发。
  • preloadNext 对同一首曲目只触发一次,避免进度频繁上报导致接口重复请求。
  • nextTrackTimeout 默认 0,表示一直等待;业务希望超时展示重试按钮时可设置为 1000015000
  • 如果业务主动切歌、重新设置歌单、停止播放或移除等待中的曲目,插件会清理本次等待状态。

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.json5EntryAbility 中配置 backgroundModes: ["audioPlayback"],并在 requestPermissions 中声明 ohos.permission.INTERNETohos.permission.KEEP_BACKGROUND_RUNNING

示例(只展示关键字段):

{
  "module": {
    "abilities": [
      {
        "name": "EntryAbility",
        "backgroundModes": [
          "audioPlayback"
        ]
      }
    ],
    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET"
      },
      {
        "name": "ohos.permission.KEEP_BACKGROUND_RUNNING"
      }
    ]
  }
}

INTERNET 用于网络音频访问;KEEP_BACKGROUND_RUNNINGaudioPlayback 用于配合插件内部后台音频连续任务申请,让息屏播放和媒体卡控制在系统后台策略下继续可用。修改该配置后必须重新打鸿蒙自定义基座或运行包。

Harmony 短音/瞬态音频焦点策略

Harmony 上架检测会检查短音、瞬态音播放场景是否使用 share / duck / pause 等不会永久打断后台音乐的焦点策略。插件的 Harmony 原生链路会在 AVPlayer 首次 prepare() / play() 前激活 AudioSessionManagerCONCURRENCY_MIX_WITH_OTHERS 并发策略,同时显式设置 audioInterruptMode = SHARE_MODEaudioRendererInfo.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

注意事项

  1. initPlayer(),再调用播放、队列、状态 API。
  2. progress 事件频率较高,页面日志建议采样展示。
  3. on()off() 需要使用同一个回调函数引用。
  4. iOS 中文 URL 会做安全编码归一化,但仍建议业务侧使用可直接访问的 HTTPS 音频地址。
  5. setVolume()setPlaybackRate() 在部分平台可能受系统策略限制,建议先用 getCapabilities() 判断;Harmony 通过 AVPlayer.setSpeed() 支持常见固定倍速档位。
  6. Harmony 后台保活依赖 module.json5 中的 backgroundModes: ["audioPlayback"]ohos.permission.KEEP_BACKGROUND_RUNNING;配置变化后必须重新打鸿蒙自定义基座或运行包。

联系方式

信-微:l-z-1-8-7-1512-5421(-去掉,不这样写会被和谐)

隐私、权限声明

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

后台音频、媒体会话、通知权限、Harmony audioPlayback 后台模式与 KEEP_BACKGROUND_RUNNING 连续任务权限按平台配置

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

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