更新记录
1.0.2(2026-08-19) 下载此版本
修复已知问题
1.0.1(2026-08-19) 下载此版本
更新文档
1.0.0(2026-08-19) 下载此版本
自用自己开发的插件给大家免费共享 有问题评论区留言吧 我已经在魅族和小米测试过正常打包正常能使用
查看更多平台兼容性
uni-app x(4.66)
| Chrome | Safari | Android | iOS | 鸿蒙 | 微信小程序 |
|---|---|---|---|---|---|
| × | × | 5.0 | × | × | × |
losfer-audio-player
安卓端原生通知栏音乐播放器(前台服务保活),用于替代 uni.getBackgroundAudioManager() 在安卓端没有系统播放器的问题。
- 原生系统媒体中心:Android 8.0+ 使用系统
MediaSession+MediaStyle,锁屏/下拉通知栏显示原生媒体卡片,支持上一曲/播放暂停/下一曲,与系统音乐播放器体验一致 - 前台服务保活:App 退后台、锁屏后音频持续播放,通知栏常驻
- 全局单例:全局只允许一个播放器实例,重复创建时自动注销上一个播放器,杜绝多路音频同时播放
- 纯系统 API:不依赖任何三方 SDK,支持云打包与自定义基座
平台兼容性
| 平台 | 支持情况 |
|---|---|
| App-Android | ✓ |
| App-iOS | ✗(请继续使用 uni.getBackgroundAudioManager()) |
| 微信小程序 / H5 / 其他 | ✗(请继续使用 uni.getBackgroundAudioManager() 或 uni.createInnerAudioContext()) |
安装与配置
1. 引入插件
// #ifdef APP-ANDROID
import { LosferAudioPlayer } from "@/uni_modules/losfer-audio-player";
// #endif
2. 权限配置
插件已内置 AndroidManifest.xml,自动申请以下权限,无需手动配置:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
Android 13+(API 33)通知栏权限:
POST_NOTIFICATIONS属于运行时权限,需要调用系统权限申请,否则通知不显示(播放不受影响):// 使用 uni-app x 内置 API 申请(App 端) // #ifdef APP-ANDROID UTSAndroid.requestSystemPermission(["android.permission.POST_NOTIFICATIONS"], (res) => { console.log("通知权限结果:", res) }) // #endif
3. 打包要求
- 云打包:插件使用纯系统 API,可直接云打包,无需额外配置
- 本地自定义基座:插件包含
AndroidManifest.xml原生配置,运行至标准基座时原生配置不生效,请使用自定义基座
快速开始
以下代码演示完整的初始化、播放、暂停、停止、跳转与事件监听:
// #ifdef APP-ANDROID
import { LosferAudioPlayer } from "@/uni_modules/losfer-audio-player";
// #endif
// #ifndef APP-ANDROID
const audioCtx = uni.getBackgroundAudioManager();
// #endif
// #ifdef APP-ANDROID
const audioCtx = new LosferAudioPlayer();
// #endif
// 注册事件监听
audioCtx.onPlay(() => {
console.log("音频播放了")
});
audioCtx.onPause(() => {
console.log("音频暂停了")
});
audioCtx.onStop(() => {
console.log("音频关闭")
});
audioCtx.onEnded(() => {
console.log("音频播放完了")
});
audioCtx.onCanplay(() => {
console.log("音频可以播放了")
});
audioCtx.onWaiting(() => {
console.log("音频缓冲中")
});
audioCtx.onTimeUpdate((currentTime, duration) => {
console.log("播放进度:", currentTime, "/", duration)
});
audioCtx.onError((message) => {
console.log("音频出错了:", message)
});
audioCtx.onPrev(() => {
console.log("点击了上一曲")
});
audioCtx.onNext(() => {
console.log("点击了下一曲")
});
// 设置音频信息并播放
audioCtx.title = "音频标题"; // 通知栏标题
audioCtx.singer = "作者"; // 通知栏歌手
audioCtx.coverImgUrl = "https://xxx.com/cover.jpg"; // 封面图(可空)
audioCtx.src = "https://xxx.com/audio.mp3"; // 音频地址
audioCtx.play();
// 暂停 / 继续播放
audioCtx.pause();
// 停止并释放播放器(移除通知、停止前台服务)
audioCtx.stop();
// 跳转到指定位置(单位:秒)
audioCtx.seek(60);
// #endif
API 文档
属性
| 属性 | 类型 | 说明 |
|---|---|---|
src |
string | 音频地址(支持 http/https 网络地址与 file:// 本地地址) |
title |
string | 标题,显示在通知栏 / 媒体中心 |
singer |
string | 作者/歌手,显示在通知栏 / 媒体中心 |
coverImgUrl |
string | 封面图地址,显示在通知栏 / 媒体中心(可空) |
currentTime |
number(只读) | 当前播放位置(秒) |
duration |
number(只读) | 音频总时长(秒) |
playing |
boolean(只读) | 是否正在播放 |
方法
| 方法 | 参数 | 说明 |
|---|---|---|
play() |
- | 开始播放(首次加载并播放;暂停后调用续播,不重新加载) |
pause() |
- | 暂停播放 |
stop() |
- | 停止并释放播放器、移除通知、停止前台服务 |
seek(seconds) |
number | 跳转到指定位置(单位:秒) |
事件监听
| 方法 | 说明 |
|---|---|
onPlay(callback) |
音频开始播放 |
onPause(callback) |
音频暂停 |
onStop(callback) |
音频停止 |
onEnded(callback) |
音频播放完毕 |
onCanplay(callback) |
音频可以播放(缓冲完成) |
onWaiting(callback) |
音频加载中/缓冲中 |
onTimeUpdate(callback) |
播放进度更新,回调参数 (currentTime, duration)(单位秒) |
onError(callback) |
播放出错,回调参数 (message) |
onPrev(callback) |
点击通知栏/媒体中心「上一曲」 |
onNext(callback) |
点击通知栏/媒体中心「下一曲」 |
回调注册采用「后注册覆盖前注册」模式:同一事件多次注册会覆盖之前的回调,如需在页面卸载时移除监听,重新传入
null即可:audioCtx.onPlay(null) // 注销播放回调
全局单例说明
插件保证同一时刻只允许一个播放器实例:
- 无论创建多少个
new LosferAudioPlayer(),只有一个能真正播放 - 重复创建时,前一个播放器会被自动停止并释放(移除通知、停止前台服务)
- 例如:列表页点播放 A 音乐 → 又点播放 B 音乐,A 会自动停止,B 开始播放,不会出现两路音频同时响
完整案例:播放器测试页面
以下为一个完整的可运行页面(输入音频地址/标题/歌手/封面,控制播放暂停停止与 ±10s 跳转,实时显示状态与进度),与插件自带的测试页 pages/system/test.uvue 播放器部分一致:
<template>
<view class="test-page">
<view class="test-section">
<text class="test-title">音频播放器测试(losfer-audio-player,仅安卓端)</text>
<input type="text" placeholder="音频地址 http/https 或 file://" v-model="audio_src"></input>
<input type="text" placeholder="标题(通知栏显示)" v-model="audio_title"></input>
<input type="text" placeholder="歌手(通知栏显示)" v-model="audio_singer"></input>
<input type="text" placeholder="封面图地址(通知栏显示)" v-model="audio_cover"></input>
<view class="btn-row">
<button size="mini" @click="audioPlay">播放</button>
<button size="mini" @click="audioPause">暂停</button>
<button size="mini" @click="audioStop">停止</button>
<button size="mini" @click="audioSeekBack">-10s</button>
<button size="mini" @click="audioSeekForward">+10s</button>
</view>
<text class="test-info">状态: {{ audioStateText }}</text>
<text class="test-info">进度: {{ audioProgressText }}</text>
<text class="test-info">当前时间: {{ audioCurrentTime.toFixed(0) }}s 总时长: {{ audioDuration.toFixed(0) }}s</text>
</view>
</view>
</template>
<script setup lang="uts">
import { ref, computed } from 'vue'
// 播放器插件仅安卓端支持
// #ifdef APP-ANDROID
import { LosferAudioPlayer } from '@/uni_modules/losfer-audio-player'
// #endif
const audio_src = ref('https://example.com/audio.mp3')
const audio_title = ref('测试音频标题')
const audio_singer = ref('Losfer')
const audio_cover = ref('https://example.com/cover.jpg')
const audioCurrentTime = ref<number>(0)
const audioDuration = ref<number>(0)
const audioPlaying = ref<boolean>(false)
const audioErrorText = ref<string>('')
const audioProgressText = computed<string>(() : string => {
return audioCurrentTime.value.toFixed(0) + 's / ' + audioDuration.value.toFixed(0) + 's'
})
const audioStateText = computed<string>(() : string => {
if (audioErrorText.value != '') return '错误: ' + audioErrorText.value
return audioPlaying.value ? '播放中' : '已暂停'
})
let player : LosferAudioPlayer | null = null
/**
* 初始化播放器并注册事件监听(仅安卓端)
*/
function initAudioPlayer() {
if (player != null) return
// #ifdef APP-ANDROID
try {
player = new LosferAudioPlayer()
const p = player!
p.onPlay(() => {
console.log('【音频】onPlay 开始播放')
audioPlaying.value = true
audioErrorText.value = ''
})
p.onPause(() => {
console.log('【音频】onPause 暂停')
audioPlaying.value = false
})
p.onStop(() => {
console.log('【音频】onStop 停止')
audioPlaying.value = false
})
p.onEnded(() => {
console.log('【音频】 播放完毕')
audioPlaying.value = false
})
p.onCanplay(() => {
console.log('【音频】onCanplay 缓冲完成可播放')
})
p.onWaiting(() => {
console.log('【音频】 缓冲中')
})
p.onTimeUpdate((currentTime : number, duration : number) => {
audioCurrentTime.value = currentTime
audioDuration.value = duration
})
p.onError((message : string) => {
console.log('【音频】onError 出错: ' + message)
audioErrorText.value = message
})
p.onPrev(() => {
console.log('【音频】onPrev 通知栏点击了上一曲')
})
p.onNext(() => {
console.log('【音频】onNext 通知栏点击了下一曲')
})
console.log('【音频】播放器初始化成功')
} catch (e) {
const err = e as Error
console.log('【音频】初始化失败: ' + err.message)
audioErrorText.value = err.message
}
// #endif
}
/**
* 设置信息并开始播放
*/
function audioPlay() {
initAudioPlayer()
const p = player
if (p == null) {
uni.showToast({ title: '仅安卓端 APP 支持', icon: 'none' })
return
}
p.title = audio_title.value
p.singer = audio_singer.value
p.coverImgUrl = audio_cover.value
p.src = audio_src.value
p.play()
}
function audioPause() {
const p = player
if (p == null) return
p.pause()
}
function audioStop() {
const p = player
if (p == null) return
p.stop()
audioCurrentTime.value = 0
audioDuration.value = 0
}
function audioSeekBack() {
const p = player
if (p == null) return
const target = audioCurrentTime.value - 10
p.seek(target > 0 ? target : 0)
}
function audioSeekForward() {
const p = player
if (p == null) return
p.seek(audioCurrentTime.value + 10)
}
</script>
<style>
.test-page {
flex-direction: column;
padding: 30rpx;
background-color: #f7f8fa;
}
.test-section {
flex-direction: column;
background-color: #ffffff;
border-radius: 20rpx;
padding: 30rpx;
margin-bottom: 30rpx;
}
.test-title {
font-size: 16px;
font-weight: bold;
color: #2d3436;
margin-bottom: 20rpx;
}
.test-info {
font-size: 13px;
color: #636e72;
margin-top: 12rpx;
}
.btn-row {
flex-direction: row;
flex-wrap: wrap;
align-items: center;
margin-top: 20rpx;
}
</style>
注意事项
- 插件仅支持安卓端 App,iOS 与小程序请使用
uni.getBackgroundAudioManager() - 播放地址与封面需要可公网访问的 URL,或本地地址(本地音频用
plus.io.convertLocalFileSystemURL('/static/xxx.mp3')转换;本地图片用'file://' + plus.io.convertLocalFileSystemURL('/static/xxx.png')) - 播放 m3u8 等串流格式需要设备 Android 系统版本支持
- 由于插件全局唯一,强烈建议将播放器封装为全局单例(Pinia / Vuex / 全局模块),避免多处创建实例导致监听回调被反复覆盖
- 页面卸载时若不再需要播放,请调用
stop()释放播放器与前台服务;若需后台继续播放则无需释放

收藏人数:
下载插件并导入HBuilderX
赞赏(0)
下载 11
赞赏 0
下载 12518030
赞赏 1943
赞赏
京公网安备:11010802035340号