Android开发在线音频播放器之章节三核心PlayerService

本章小节主要讲述音频播放器的service相关内容。由于代码属于公司资产个人无权外发,而且代码牵扯的范围广泛也难以全部贴上来,所以只讲重点的细节实现和小段代码。

首先服务播放的权限不可少

ini 复制代码
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

1、先列举服务中的变量

arduino 复制代码
private static AudioPlayerManager apm; //音频播放管理
private static int mPosSomeTime = 0;// 播放指针 单元
private String jsonList, unit_name; //歌曲列表数据  歌曲名称
private static int bookId; //内层专辑ID
private static String book_name; //内层专辑ID
private static long process = 0; //当前歌曲播放进度
private static long max = 0; //当前歌曲最大进度
private static ExecutorService fixedThreadPool;
private static int isFrom; //来源 0 专辑列表 1首页 2 AI问答
private String book_cover; //专辑封面
private static int bizId; //外层专辑ID
private static String bizTopic; //外层专辑主题
private static long timeStart = 0;   //开始计时的时间戳
private static boolean countDowning = false; //是否倒计时中

2、初始化中 设置播放监听、eventbus注册、线程池等,其中播放结束监听是关键,很多播放逻辑在此触发。

scss 复制代码
@Override
    public void onCreate() {
        super.onCreate();
        EventBus.getDefault().register(this);
        fixedThreadPool = Executors.newFixedThreadPool(3);
        apm = AudioPlayerManager.getInstance(TbjcPlayerService.this);

        Player.Listener listener = new Player.Listener() {
            @Override
            public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
                switch (playbackState) {
                    case Player.STATE_BUFFERING://加载缓存且还未准备好时触发

                        break;
                    case Player.STATE_READY:  //播放准备
                        timeStart = System.currentTimeMillis(); //记录播放开始时间
                        break;
                    case Player.STATE_ENDED: //播放结束时触发
                        process=0;
                        fixedThreadPool.execute(command4); //保存播放进度
                        int sum = 0;
                        sum = ConstantFd.unit_List.size(); //专辑中的歌曲数量
                        if(ConstantFd.loop_type == 2 && sum > 0){ //随机播放
                            int index = (int) (Math. random() * sum-1);
                            to_play(index);
                        }else if(ConstantFd.loop_type == 0){ //顺序播放
                            Log.e("TAG", "mPosSomeTime--->  "+mPosSomeTime);
                            if(mPosSomeTime < sum - 1){//播放指针 小于 列表总数
                                play_next(1); //播放下一个
                            }else { //已播完最后一首  暂停操作
                                ConstantFd.isplay = false;
                                apm.playOrPause(false);
                                PlayStateMsg.sendState(false); //发送播放状态 通知ui
                            }
                        }else if(ConstantFd.loop_type == 1){ //单课循环
                            Log.e("TAG", "单课循环--->  重新开始");
                            playUrl = ConstantFd.unit_List.get(mPosSomeTime).getPlayUrl();
                            apm.setAudioFile2(playUrl); //在线
                            apm.start();
                        }
                        break;
                }

            }
        };
        apm.setlistener(listener); //设置播放监听
        apm.switchSpeed(ConstantFd.speed); //设置播放倍速
        //设置网络监听
        IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            registerReceiver(networkReceiver, filter, Context.RECEIVER_EXPORTED);
        }else {
            registerReceiver(networkReceiver, filter); 
        }
    }

3、首次数据加载 变量赋值、开始播放、倒计时方法

ini 复制代码
@Override
    public int onStartCommand(Intent intent, int flags, int startId) {
//        NotificationUtil.startForeground(TbjcPlayerService.this, "播放器正在运行", "点按即可了解详情或停止应用");
        if (intent != null) {
            book_cover = intent.getStringExtra("book_cover");
            book_name = intent.getStringExtra("book_name");
            bookId = intent.getIntExtra("bookId", 0);
            jsonList = intent.getStringExtra("jsonList");
            isFrom = intent.getIntExtra("isFrom", 0);
            bizId = intent.getIntExtra("bizId", 0);
            unit_name = intent.getStringExtra("unit_name");
            bizTopic = intent.getStringExtra("bizTopic");
            Log.i("TAG", "jsonList : " + jsonList);
        }else {
            Log.e("TAG", "intent null--->  ");
        }
        apm.requestAudioFocus(); //请求音频播放焦点
        if(!TextUtils.isEmpty(jsonList)){ //解析列表数据
            unit_List = new Gson().fromJson(jsonList, new TypeToken<List<ReadDataBean>>() { }.getType());
            startPlay(); //开始播放
        }else {
            reLoad();
        }
        if (!countDowning) {
            SharedPreferences sharedPreferences = getSharedPreferences(ConstantFd.PREFERENCES, MODE_PRIVATE); //上次定时关闭
            int time = sharedPreferences.getInt(ConstantFd.LastTimeSet, 0);
            if(time > 0){
                showSurplusTime = true; //显示倒计时
                countDown(time * 60 * 1000);
            }else {
                showSurplusTime = false;
            }
        }else {
            ConstantFd.showSurplusTime = true;
            if (ConstantFd.surplusTime > 0) {
                mHandler.removeMessages(2);
                mHandler.sendEmptyMessageDelayed(2, 1000);
            }
        }
        return START_NOT_STICKY;
    }

    private void reLoad(){ //加载播放
        isplay = false; //播放状态设为false
        if (!TextUtils.isEmpty(book_name) && bookId >= 0) {
            fixedThreadPool.execute(command1); //使用线程池避免播就了之后卡顿
        }
    }

private void countDown(long time) { //设置时间 开始到计时
        Log.i("TAG", "ConstantFd.surplusTime:  "+ConstantFd.surplusTime);
        ConstantFd.surplusTime = time;
        ConstantFd.setTime = time;
        mHandler.removeMessages(2);
        if (ConstantFd.surplusTime > 0) {
            countDowning = true;
            mHandler.sendEmptyMessageDelayed(2, 1000);
        } else {
            countDowning = false;
        }

    }

4、设置播放的相关方法

scss 复制代码
//开始播放
private void startPlay(){
        PlayInfoMsg.sendInfo(true); //发送消息 通知UI刷新列表和进度条
        mPosSomeTime = getPoi(unit_name);
        if(apm.isPlaying() && isFrom==1){ //如果是播放中 继续播放,做到从首页跳转过来无缝衔接播放
            PlayShowPoiMsg.sendPoi(mPosSomeTime);
            PlayStateMsg.sendState(true);
        }else {
            if(!TextUtils.isEmpty(unit_name)){ //从列表页传来的单元名称
                PlayUnitPoiMsg.sendPoi(mPosSomeTime, book_cover);
            }else {
                fixedThreadPool.execute(command3); //获取上次播放的单元指针
            }
//            apm.playOrPause(ConstantFd.isplay);
        }
        Log.i("TAG", "111  isplay:  "+ConstantFd.isplay);
        Log.i("TAG", "111  control_play:  "+ConstantFd.control_play);
        if(!ConstantFd.control_play){ //外面手动暂停 进播放页暂停
            PlayControlTools.setACTION_PAUSE();
        }
    }

    private static void setPlayPoi(long poi){ //播放当前歌曲中的指定位置
//        Log.i("TAG", "滑动的 poi: "+poi);
        apm.getMediaPlayer().seekTo(poi);
    }

private void play_next(int LeftorRight) { //播放下一首 0上一个   1下一个
        if(!ConstantFd.hasList()){ //列表无数据
            return;
        }
        int position = mPosSomeTime; //当前单元位置
        if (position == -1) {
            return;
        }
        int sum = 0;
        sum = ConstantFd.unit_List.size();
        if (LeftorRight == 0) { //左
            if (position > 0) {
                position--;
            } else {
                position = sum - 1;
            }
        } else if (LeftorRight == 1) { //右
            if (position < sum - 1) {
                position++;
            } else {
                position = 0;
            }
        }
        to_play(position);

    }

    private void to_play(int position) { //播放列表中的哪一首歌
        listenUpload();
        mPosSomeTime = position;
        PlayUnitPoiMsg.sendPoi(mPosSomeTime, book_cover); //发送播放指针  并播放
    }

5、服务中的消息通信 接收来自ui的设置或操作

scss 复制代码
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
    public void receiveTime(PlayTimeSetMsg msg) { //接收设置的定时时间
        countDown(msg.getTime());
    }

    @Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
    public void receiveProcess(PoiToPlayMsg msg) { //接收滑动的进度
        setPlayPoi(msg.getPoi());
    }

    @Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
    public void receiveSet(PlaySetMsg msg) { //播放设置倍速
        apm.switchSpeed(ConstantFd.speed);
    }

    @Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
    public void receiveEventBus_Poi(PlayUnitPoiMsg msg) { //接收指针 播这一首
        mPosSomeTime = msg.getPoi();
        ConstantFd.isplay = true;
        PlayStateMsg.sendState(true);
        playStart();
    }

    @Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
    public void receiveEventBus(PlayControlMsg msg) { //接收消息控制播放
        if (msg.getMsg().equals(PlayControlMsg.YYTS_ACTION_PAUSE)) { //暂停
            playOrPause(false);
            PlayStateMsg.sendState(false); //发送播放状态 通知ui
        } else if (msg.getMsg().equals(PlayControlMsg.YYTS_ACTION_PLAY)) { //播放
            if(apm.getMediaPlayer().getCurrentPosition() >= max){
                setPlayPoi(0);
            }
            playOrPause(true);
            PlayStateMsg.sendState(true); //发送播放状态 通知ui
        } else if (msg.getMsg().equals(PlayControlMsg.YYTS_PLAY_NEXT)) { //下一首
            int sum = 0;
            sum = ConstantFd.unit_List.size();
            if (ConstantFd.loop_type == 2 && sum > 0) { //随机播放
                int index = (int) (Math.random() * sum - 1);
                to_play(index);//播放指定位置歌曲
            } else {
                play_next(1);//播放下一首
            }
        } else if (msg.getMsg().equals(PlayControlMsg.YYTS_PLAY_PREVIOUS)) { //上一首
            int sum = 0;
            sum = ConstantFd.unit_List.size();
            if (ConstantFd.loop_type == 2 && sum > 0) { //随机播放
                int index = (int) (Math.random() * sum - 1);
                to_play(index); //播放指定位置歌曲
            } else {
                play_next(0); //播放上一首
            }
        }
    }



/**
 * 播放时间设置
 */

public class PlayTimeSetMsg {

    long time;

    public long getTime() {
        return time;
    }

    public void setTime(long time) {
        this.time = time;
    }

    public static void sendMsg(long time){
        PlayTimeSetMsg msg = new PlayTimeSetMsg();
        msg.setTime(time);
        EventBus.getDefault().post(msg); //发送状态
    }

}

6、服务中的播放线程

scss 复制代码
private static Handler mHandler = new Handler(new Handler.Callback() { //static  定时
        @Override
        public boolean handleMessage(Message msg) {
            switch (msg.what) {
                case 0: //获取当前进度并发给ui
                    max = apm.getMediaPlayer().getDuration();
                    process = apm.getMediaPlayer().getCurrentPosition();
                    if(max<0){
                        max=0;
                    }
//                    Log.i("TAG", "process:  "+process);
//                    Log.i("TAG", "max:  "+max);
                    PoiToUIMsg.sendMsg(process, max); //发送进度到UI
                    if(ConstantFd.isplay){
                        mHandler.sendEmptyMessageDelayed(0, 1000);
                    }
                    fixedThreadPool.execute(command4); //保存播放进度
                    break;
                case 1:
                    setPlayPoi(msg.arg1);//设置上次进度
                    break;
                case 2: //定时播放 倒计时
                    ConstantFd.surplusTime = ConstantFd.surplusTime - 1000;
                    if (ConstantFd.showSurplusTime) {
                        PlayTimeShowMsg.sendMsg(ConstantFd.surplusTime);
                    }
//                    Log.i("TAG", "ConstantFd.surplusTime:  "+ConstantFd.surplusTime);
                    if (ConstantFd.surplusTime > 0) {
                        if(ConstantFd.isplay){
                            mHandler.sendEmptyMessageDelayed(2, 1000); //每秒刷新
                        }
                    } else { //定时播放结束
                        ConstantFd.surplusTime = 0;
                        countDowning = false;
                        if (ConstantFd.setTime != 0) {
                            ConstantFd.isplay = false;
                            apm.playOrPause(false);
                            PlayStateMsg.sendState(false); //发送播放状态 通知ui
                        }
                    }
                    break;
            }
            return true;
        }
    });
  
private Runnable command1 = new Runnable() {
        @Override
        public void run() { //请求数据 播放列表
            RequestMsg.sendInfo(true);
            getDataList(); //获取播放数据 
            //获取数据成功后要设置播放状态为true 调用开始播放方法
        }
    };

    private Runnable command2 = new Runnable() { //获取上次进度 并保存本次单元指针
        @Override
        public void run() {
            if(!ConstantFd.hasList() || isFrom==2){
                return;
            }
            //获取上次播放进度
            Message msg = new Message();
            msg.what = 1;
            msg.arg1 = (int) getSongProcess();
//            Log.i("TAG", "获取上次播放进度 process: "+process);
            mHandler.sendMessage(msg);

            //保存播放歌曲指针
                JxwRoom.getInstance().getJxwDatabase().getLastSongDao().deleteAllInfo();
                LastSong song = new LastSong();
                song.setSong_name(ConstantFd.unit_List.get(mPosSomeTime).getShowName());
                song.setIndex(mPosSomeTime);
                song.setBook_id(bookId);
                song.setBook_name(book_name);
                song.setSong_cover(book_cover);
                song.setBiz_id(bizId);
                JxwRoom.getInstance().getJxwDatabase().getLastSongDao().insertInfo(song);
//            Log.i("TAG", "保存歌曲指针 poi: "+mPosSomeTime);

                AlbumSong as =JxwRoom.getInstance().getJxwDatabase().getAlbumSongDao().getSongPoi(book_name, bookId); //保存专辑播放到哪一曲
                if(as!=null){
                    as.setSong_name(ConstantFd.unit_List.get(mPosSomeTime).getShowName());
                    as.setIndex(mPosSomeTime);
                    JxwRoom.getInstance().getJxwDatabase().getAlbumSongDao().updateInfo(as);
                }else {
                    as = new AlbumSong();
                    as.setSong_name(ConstantFd.unit_List.get(mPosSomeTime).getShowName());
                    as.setIndex(mPosSomeTime);
                    as.setBook_id(bookId);
                    as.setBook_name(book_name);
                    as.setSong_cover(book_cover);
                    as.setBiz_id(bizId);
                    JxwRoom.getInstance().getJxwDatabase().getAlbumSongDao().insertInfo(as);
                }
        }
    };

    private Runnable command3 = new Runnable() {
        @Override
        public void run() { //获取上次播的歌 指针
            mPosSomeTime = 0;
            AlbumSong as =JxwRoom.getInstance().getJxwDatabase().getAlbumSongDao().getSongPoi(book_name, bookId); //保存专辑播放到哪一曲
                if(as!=null){
                    mPosSomeTime = as.getIndex();
                }
            PlayUnitPoiMsg.sendPoi(mPosSomeTime, book_cover); //发送播放指针
        }
    };

    private static Runnable command4 = new Runnable() {
        @Override
        public void run() { //保存歌曲播放进度
            if(!ConstantFd.hasList() || isFrom==2){
                return;
            }
            if(process >= max){//进度判断
                process = 0;
            }
            if(mPosSomeTime >= ConstantFd.unit_List.size()){
                    return;
                }
                //使用数据库保存播放信息
                String song_name = ConstantFd.unit_List.get(mPosSomeTime).getShowName();
                SongProcess sp = JxwRoom.getInstance().getJxwDatabase().getSongProcessDao().getProcess(book_name, bookId, song_name);
                if(sp!=null){
                    sp.setProcess(process);
                    JxwRoom.getInstance().getJxwDatabase().getSongProcessDao().updateInfo(sp);
                }else {
                    sp = new SongProcess();
                    sp.setProcess(process);
                    sp.setBook_id(bookId);
                    sp.setBook_name(book_name);
                    sp.setSong_name(song_name);
                    sp.setIndex(mPosSomeTime);
                    JxwRoom.getInstance().getJxwDatabase().getSongProcessDao().insertInfo(sp);
                }
        }
    };

7、每首歌曲播放开始的方法和结束服务

scss 复制代码
private void playStart() {
        String playUrl = "";
        if(!ConstantFd.isLocalPlay) { //有网
            if (ConstantFd.unit_List.size() > 0 && mPosSomeTime < ConstantFd.unit_List.size() && ConstantFd.unit_List.get(mPosSomeTime) != null) {
                playUrl = ConstantFd.unit_List.get(mPosSomeTime).getPlayUrl(); //在线
            }
        }
        if(!TextUtils.isEmpty(playUrl)){
            apm.setAudioFile2(playUrl); //设置播放连接
            apm.start();
            fixedThreadPool.execute(command2); //获取上次播放进度
            mHandler.removeMessages(0);
            mHandler.sendEmptyMessageDelayed(0, 800);//开始播放
        }

    }

private int getPoi(String name){ //获取位置  也可以用ID作为判断
        int poi = 0;
        int sum = 0;
        if(ConstantFd.isLocalPlay){ //无网本地
            sum = ConstantFd.local_unit_List.size();
        }else {
            sum = ConstantFd.unit_List.size();
        }
        for (int i = 0; i < sum; i++) {
            if(ConstantFd.isLocalPlay){
                if(ConstantFd.local_unit_List.get(i).getSound_name().equals(name)){
                    return i;
                }
            }else {
                if(ConstantFd.unit_List.get(i).getShowName().equals(name)){
                    return i;
                }
            }
        }
        return poi;
    }

 @Override
    public void onDestroy() {
        super.onDestroy();
        PlayControlTools.setACTION_PAUSE(); //暂停播放
        ConstantFd.control_play = false; //设置状态
        ConstantFd.isplay = false; //设置状态
//        stopForeground(true);
        // 关闭线程池,避免线程泄漏
        if (fixedThreadPool != null && !fixedThreadPool.isShutdown()) {
            fixedThreadPool.shutdownNow(); // 立即关闭所有任务
            fixedThreadPool = null;
        }
        // 移除所有当前 Service 订阅的粘性事件类型
        EventBus.getDefault().removeStickyEvent(ListenUploadMsg.class);
        EventBus.getDefault().removeStickyEvent(PlayTimeSetMsg.class);
        EventBus.getDefault().removeStickyEvent(PoiToPlayMsg.class);
        EventBus.getDefault().removeStickyEvent(PlaySetMsg.class);
        EventBus.getDefault().removeStickyEvent(PlayUnitPoiMsg.class);
        EventBus.getDefault().removeStickyEvent(PlayControlMsg.class);
        // 最后注销订阅
        EventBus.getDefault().unregister(this);
        if (apm != null) {
            apm.setlistener(null); // 移除监听器,切断引用链
            apm.release(); // 释放音频资源
            apm = null; // 置空,避免后续引用
        }
        ConstantFd.unit_List.clear(); //清空数据
        ConstantFd.local_unit_List.clear();
        unregisterReceiver(networkReceiver);
    }

以上就是在线播放器的服务中的主要逻辑代码,可供参考

相关推荐
日月云棠3 小时前
17 Dubbo 2.7 集群容错策略源码解析
java·后端
Refrain_zc3 小时前
Android开发纯按键文件浏览器
java
monkeyhlj3 小时前
Harness理解学习
java·人工智能·python·学习·ai编程
西凉的悲伤3 小时前
SpringBoot WebClient 介绍
java·spring boot·后端·webclient
Simon523143 小时前
mybatis执行流程、关联映射、注解开发
java·开发语言·mybatis
冷雨夜中漫步3 小时前
SQLite 深度解析:在 Java/Spring 中的使用与H2、Derby对比
java·spring·sqlite
laufing3 小时前
Java 模板引擎 FreeMarker 入门教程:语法、内建函数与常用案例
java·freemarker
wengqidaifeng3 小时前
C++从菜鸟到强手:2.类和对象(上)—— 从结构体到类的跨越
java·开发语言·c++
自律懒人3 小时前
2026年AI编程工具横评:Trae、Cursor、Claude Code、Copilot X,同一需求谁更强?
java·copilot·ai编程