更新记录

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>

注意事项

  1. 插件仅支持安卓端 App,iOS 与小程序请使用 uni.getBackgroundAudioManager()
  2. 播放地址与封面需要可公网访问的 URL,或本地地址(本地音频用 plus.io.convertLocalFileSystemURL('/static/xxx.mp3') 转换;本地图片用 'file://' + plus.io.convertLocalFileSystemURL('/static/xxx.png')
  3. 播放 m3u8 等串流格式需要设备 Android 系统版本支持
  4. 由于插件全局唯一,强烈建议将播放器封装为全局单例(Pinia / Vuex / 全局模块),避免多处创建实例导致监听回调被反复覆盖
  5. 页面卸载时若不再需要播放,请调用 stop() 释放播放器与前台服务;若需后台继续播放则无需释放

隐私、权限声明

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

android.permission.INTERNET、android.permission.FOREGROUND_SERVICE、android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK

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

插件不采集任何数据

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

许可协议

MIT协议