Android开发在线音频播放器之章节二页面PlayActivity

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

背景:播放内容为专辑,一个专辑内有多首歌曲,功能有基础的歌曲列表、播放/暂停、上/下一首、收藏、设置播放倍速、单曲/循环/随机播放、进度条滑动 等等,我以自己的网易云为例功能相当

1、首先得有网,网络权限、网络监听不能少

scss 复制代码
<uses-permission android:name="android.permission.INTERNET" />

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);
}


boolean isNetWork = false;

private BroadcastReceiver networkReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
            isNetWork = JxwApplication.isNetwork();
            if(isNetWork){ //有网
                if (netWorkDialog != null && netWorkDialog.isShowing()) {
                    netWorkDialog.dismiss();//隐藏无网弹窗
                }
            }else {//无网
                if(netWorkDialog!=null){
                    netWorkDialog.show(); //显示无网弹窗
                }
                animatorSet.pause(); //暂停封面旋转
            }
        }
    }
};

2、封面旋转的动画

scss 复制代码
AnimatorSet animatorSet;
private String book_cover; //专辑封面

//==========在onCreate 中===================
 RequestOptions options = new RequestOptions();//图片圆角
        Glide.with(ReadTextActivity.this).load(book_cover)
                .placeholder(R.mipmap.img_med_player_placeholder) // 占位图资源ID
                .error(R.mipmap.img_med_player_placeholder) // 错误占位图资源ID
                .apply(options).into(mBinding.ivCover);

animatorSet = new AnimatorSet();
        ObjectAnimator rotateAnimator = ObjectAnimator.ofFloat(mBinding.ivCover, "rotation", 0f, 360f);
        rotateAnimator.setInterpolator(new LinearInterpolator());
        rotateAnimator.setDuration(10000);
        rotateAnimator.setRepeatCount(Animation.INFINITE);
        rotateAnimator.setRepeatMode(ValueAnimator.RESTART);
        animatorSet.playTogether(rotateAnimator);
        animatorSet.start();
        animatorSet.pause();

//====================播放====================
animatorSet.resume();
//====================暂停====================
animatorSet.pause();

//==========在onDestroy 中===================
if (animatorSet != null) {
            animatorSet.cancel();
            animatorSet = null;
        }

3、播放中的变量有 专辑ID、专辑名称、是否收藏、专辑封面、循环类型、倍速、歌曲名称,其中倍速和循环类型设置完后需要用SP存储起来,在页面初始化中取用

arduino 复制代码
private String book_cover; //专辑封面
private String book_name;
private int bookId;
private boolean isCollect = false; //是否收藏
private String unit_name; //正在播放的歌曲名称
public static float speed = 1f; //预选 播放速度
public static int loop_type = 0; // 0顺序播放  1单篇循环  2随机播放
public static boolean isplay = true; //播放状态
private String jsonList; //播放的歌曲列表数据
private int pre_gb_time = 0; //定时播放的时间 0/15/30/45分钟

4、播放消息的发送和监听,我这里使用的是EventBus,我初次写播放器时由于没有经验,播放服务于UI的交互用的监听,代码设计有不少缺陷容易造成内存泄漏以及回调地狱不便维护,采用EventBus通信是迭代版本后比较高效靠谱稳定的方案。 服务端的消息订阅和发生我放到下一个章节讲解

typescript 复制代码
//引用
implementation 'org.greenrobot:eventbus:3.2.0'

//=============在onCreate() 中注册===============
EventBus.getDefault().register(this);

//=============在onDestroy()中注销===============
// 针对性移除所有粘性事件类型(与订阅方法对应)
        EventBus.getDefault().removeStickyEvent(PlayTimeShowMsg.class);
        EventBus.getDefault().removeStickyEvent(PoiToUIMsg.class);
        EventBus.getDefault().removeStickyEvent(PlayUnitPoiMsg.class);
        EventBus.getDefault().removeStickyEvent(PlayShowPoiMsg.class);
        EventBus.getDefault().removeStickyEvent(PlayStateMsg.class);
        EventBus.getDefault().removeStickyEvent(RequestMsg.class);
        EventBus.getDefault().removeStickyEvent(PlayInfoMsg.class);
// 最后注销订阅
EventBus.getDefault().unregister(this);

//===========发送播放消息============
//封装成工具类 可从任何地方调用 比如通知栏或控制栏的自定义UI中实现控制播放
public class PlayControlMsg {

    public static final String YYTS_ACTION_PAUSE = "YYTS_ACTION_PAUSE";//暂停
    public static final String YYTS_ACTION_PLAY = "YYTS_ACTION_PLAY";//播放
    public static final String YYTS_PLAY_NEXT = "YYTS_PLAY_NEXT";//播放下一个
    public static final String YYTS_PLAY_PREVIOUS = "YYTS_PLAY_PREVIOUS";//播放上一个

    public String msg;

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}
public class PlayControlTools {

    public static void setACTION_PAUSE(){//暂停
        PlayControlMsg msg = new PlayControlMsg();
        msg.setMsg(PlayControlMsg.YYTS_ACTION_PAUSE);
        EventBus.getDefault().post(msg);
    }

    public static void setACTION_PLAY(){//播放
        PlayControlMsg msg = new PlayControlMsg();
        msg.setMsg(PlayControlMsg.YYTS_ACTION_PLAY);
        EventBus.getDefault().post(msg);
    }

    public static void setPLAY_PREVIOUS(){ //上一个
        PlayControlMsg msg = new PlayControlMsg();
        msg.setMsg(PlayControlMsg.YYTS_PLAY_PREVIOUS);
        EventBus.getDefault().post(msg);
    }

    public static void setPLAY_NEXT(){ //下一个
        PlayControlMsg msg = new PlayControlMsg();
        msg.setMsg(PlayControlMsg.YYTS_PLAY_NEXT);
        EventBus.getDefault().post(msg);
    }
}

//UI页滑动进度的 监听和消息发送
mBinding.seekbar1.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) { }
            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
//                Log.e("TAG","进度条,当前值:"+seekBar.getProgress());
//                mBinding.ivReadBf.setImageResource(R.mipmap.icon_pause);
                PoiToPlayMsg.sendMsg(seekBar.getProgress()); //发送指针
            }
        });


//=============接收播放消息================
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
    public void receiveTime(PlayTimeShowMsg msg) { //接收时间显示 定时播放的倒计时
        mBinding.tvPlayTime.setText(ConstantFd.FormatMiss(msg.getTime()));
    }

    @Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
    public void receiveProcess(PoiToUIMsg msg) { //接收进度
//        Log.i("TAG", "process: "+msg.getPoi());
//        Log.i("TAG", "getMax: "+msg.getMax());
        mBinding.seekbar1.setMax((int)msg.getMax());
        mBinding.seekbar1.setProgress((int)msg.getPoi());
        if(msg.getPoi()<=msg.getMax()){
            //将int类型 进度转为String的 为分:秒 显示
            mBinding.tvNum1.setText(ConstantFd.FormatMiss(msg.getPoi())); 
        }else {
            mBinding.tvNum1.setText(ConstantFd.FormatMiss(msg.getMax()));
        }
        mBinding.tvNum2.setText(ConstantFd.FormatMiss(msg.getMax()));
    }

    @Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
    public void receivePlayPoi(PlayUnitPoiMsg msg) { //接收单元指针
        Log.i("TAG", "单元指针poi: "+msg.getPoi());
        mUnitAdapter.mPosSomeTime = msg.getPoi(); //适配器播放指针更新
        getCollectState(); //获取收藏状态
        updatePlayUI(); //刷新列表ui
        playOrPauseUI(); //播放ui和动画更新
        mBinding.recyclerView.smoothScrollToPosition(msg.getPoi()); //列表滚动到指定位置
    }

    @Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
    public void receivePlayPoi(PlayShowPoiMsg msg) { //接收单元指针
        Log.i("TAG", "单元指针poi: "+msg.getPoi());
        mUnitAdapter.mPosSomeTime = msg.getPoi();//适配器播放指针更新
    }

    @Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
    public void receivePlayState(PlayStateMsg msg) { //接收播放状态
        playOrPauseUI(); //播放状态ui更新
    }

    @Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
    public void receiveEventBus(RequestMsg msg) { //数据请求中
        if(!isFinishing()){
            mBinding.loading.setVisibility(View.VISIBLE);
        }
    }

    @Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
    public void receiveEventBus(PlayInfoMsg msg) {
        mBinding.loading.setVisibility(View.GONE);
         if(ConstantFd.unit_List.size() > 0 && mUnitAdapter!=null){ 
                mUnitAdapter.updateData(ConstantFd.unit_List);//适配器数据更新
                updatePlayUI(); //更新播放器ui 指针显示等
            }

    }

//================消息封装类===================
/**
 * 歌曲播放进度
 */
public class PoiToPlayMsg { //UI->服务
    long poi; //当前指针

    public long getPoi() {
        return poi;
    }

    public void setPoi(long poi) {
        this.poi = poi;
    }


    public static void sendMsg(long poi){
        PoiToPlayMsg msg = new PoiToPlayMsg();
        msg.setPoi(poi);
        EventBus.getDefault().post(msg); //发送指针
    }
}

/**
 * 歌曲播放进度
 */
public class PoiToUIMsg { //服务->UI
    long poi; //当前指针
    long max; //最大指针

    public long getPoi() {
        return poi;
    }

    public void setPoi(long poi) {
        this.poi = poi;
    }

    public long getMax() {
        return max;
    }

    public void setMax(long max) {
        this.max = max;
    }

    public static void sendMsg(long poi, long max){
        PoiToUIMsg msg = new PoiToUIMsg();
        msg.setPoi(poi);
        msg.setMax(max);
        EventBus.getDefault().post(msg); //发送指针
    }
}

//单个歌曲在列表中的指针和封面信息
public class PlayUnitPoiMsg {
    int poi; //播放单元指针
    String cover; //专辑封面

    public int getPoi() {
        return poi;
    }

    public void setPoi(int poi) {
        this.poi = poi;
    }

    public String getCover() {
        return cover;
    }

    public void setCover(String cover) {
        this.cover = cover;
    }

    public static void sendPoi(int poi, String cover){
        PlayUnitPoiMsg msg = new PlayUnitPoiMsg();
        msg.setPoi(poi);
        msg.setCover(cover);
        EventBus.getDefault().post(msg); //发送指针
    }
}

/**
 * 播放时间设置
 */
public class PlayTimeSetMsg { //服务端接收,ui设置的整时间 0/10/30/45分钟的定时

    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); //发送状态
    }

}

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

public class PlayTimeShowMsg {//UI端接收的 倒计时剩余时间,每秒更新

    long time;

    public long getTime() {
        return time;
    }

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

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

}

/**
 * 播放状态  UI->播放服务 用于控制播放状态
 */

public class PlayStateMsg { 

    boolean isPlay;

    public boolean isPlay() {
        return isPlay;
    }

    public void setPlay(boolean play) {
        isPlay = play;
    }

    public static void sendState(boolean isPlay){
        PlayStateMsg msg = new PlayStateMsg();
        msg.setPlay(isPlay);
        EventBus.getDefault().post(msg); //发送状态
    }

}

public class PlayShowPoiMsg { //服务发给ui 更新播放指针(歌曲)
    int poi; //播放单元指针

    public int getPoi() {
        return poi;
    }

    public void setPoi(int poi) {
        this.poi = poi;
    }


    public static void sendPoi(int poi){
        PlayShowPoiMsg msg = new PlayShowPoiMsg();
        msg.setPoi(poi);
        EventBus.getDefault().post(msg); //发送指针
    }
}

/**
 * 播放设置倍速 ui发给服务端
 */

public class PlaySetMsg {

    boolean isChange;

    public boolean isChange() {
        return isChange;
    }

    public void setChange(boolean change) {
        isChange = change;
    }

    public static void sendMsg(boolean change){
        PlaySetMsg msg = new PlaySetMsg();
        msg.setChange(change);
        EventBus.getDefault().post(msg); //发送状态
    }

}

/**
 * 播放数据改变 服务端发给ui 通知其刷新播放数据、进度条  在播放新的一首时触发
 */

public class PlayInfoMsg {

    boolean isChange;

    public boolean isChange() {
        return isChange;
    }

    public void setChange(boolean change) {
        isChange = change;
    }

    public static void sendInfo(boolean isChange){
        PlayInfoMsg msg = new PlayInfoMsg();
        msg.setChange(isChange);
        EventBus.getDefault().post(msg); //发送通知
    }

}

5、进度条int 转时分秒

ini 复制代码
// 格式化 录音时长为 时:分:秒
    public static String FormatMiss(long miss) {
        String mm,ss;
        if(((miss / 1000) % 3600) / 60 < 10){
            mm = "0"+((miss / 1000) % 3600) / 60;
        }else {
            mm = ""+((miss / 1000) % 3600) / 60;
        }
        if((miss / 1000) % 60 < 10){
            ss = "0"+(miss / 1000) % 60;
        }else {
            ss = ""+(miss / 1000) % 60;
        }
        return  mm + ":" + ss;
    }

6、列表适配器

ini 复制代码
/**
 * 列表 适配器
 */
public class PlayUnitAdapter extends RecyclerView.Adapter<PlayUnitAdapter.Holder> {
    public List<ReadDataBean> mDatas = new ArrayList();
    public Drawable[] mFrames; //列表上的播放动画
    private int isFrom = 0; //来源 0 专辑列表 1磨耳朵首页 2 AI问答

    public void setIsFrom(int from){
        this.isFrom = from;
    }

    public void updateData(List<ReadDataBean> data) {
        mDatas.clear();
        mDatas.addAll(data);
        notifyDataSetChanged();
    }

    public int mPosSomeTime = 0;//临时选中 正在播放的单元指针 这个变量极为重要

    private Context mContext;
    private View.OnClickListener mListener;

    public PlayUnitAdapter(Context context, View.OnClickListener listener) {
        this.mContext = context;
        this.mListener = listener;
        mFrames = new Drawable[]{
                context.getResources().getDrawable(R.mipmap.img_bfdx1),
                context.getResources().getDrawable(R.mipmap.img_bfdx2),
                context.getResources().getDrawable(R.mipmap.img_bfdx3),
                context.getResources().getDrawable(R.mipmap.img_bfdx4),
        };
    }

    @NonNull
    @Override
    public Holder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new Holder(LayoutInflater.from(mContext).inflate(R.layout.item_unit, parent, false));

    }

    @Override
    public void onBindViewHolder(@NonNull Holder holder, int position) {
        holder.upateData(position);
    }

    @Override
    public int getItemCount() {
        return mDatas.size();
    }

    public class Holder extends RecyclerView.ViewHolder {
        RelativeLayout rl_read;
        TextView item_num, item_name, item_time;
        ImageView img_play, img_playing;

        public Holder(@NonNull View itemView) {
            super(itemView);
            rl_read = itemView.findViewById(R.id.rl_read);
            item_num = itemView.findViewById(R.id.item_num);
            item_name = itemView.findViewById(R.id.item_name);
            item_time = itemView.findViewById(R.id.item_time);
            img_play = itemView.findViewById(R.id.img_play);
            img_playing = itemView.findViewById(R.id.img_playing);
            itemView.setOnClickListener(mListener);
        }

        public void upateData(int position) {
            itemView.setTag(position);
            item_num.setText(position+1+"");

            String unit = "";
            if(mDatas.get(position).getShowName() != null){// 名称
                unit = mDatas.get(position).getShowName();
            }
            item_name.setText(unit);

            if(position==mPosSomeTime){ //播放中的背景
                rl_read.setBackgroundResource(R.mipmap.img_med_play_list_bg); 
                item_name.setSelected(true);
            }else {
                rl_read.setBackgroundResource(R.drawable.shape_item_bg);
            }

            if(ConstantFd.isplay && position==mPosSomeTime){ //播放中
                FrameAnimation.getInstance().setImageView(img_playing);
                FrameAnimation.getInstance().setFrames(mFrames);
                FrameAnimation.getInstance().startAnimation();
                img_play.setVisibility(View.GONE);
                img_playing.setVisibility(View.VISIBLE);
            }else {
                img_play.setVisibility(View.VISIBLE);
                img_playing.setVisibility(View.GONE);
            }
            if(isFrom==2){ //AI问答过来 隐藏列表时间
                item_time.setVisibility(View.GONE);
                ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) item_name.getLayoutParams();
                params.topMargin = 20; // 单位是像素
                item_name.setLayoutParams(params);
                ViewGroup.MarginLayoutParams params2 = (ViewGroup.MarginLayoutParams) item_num.getLayoutParams();
                params2.topMargin = 20; // 单位是像素
                item_num.setLayoutParams(params2);
            }else {
                if(mDatas.get(position).getDuration() > 0){
                    int duration = mDatas.get(position).getDuration();
                    long time = duration * 1000;
                    item_time.setText(FormatMiss(time));
                    item_time.setVisibility(View.VISIBLE);
                }else {
                    item_time.setVisibility(View.GONE);
                }
            }

        }
    }
}

//在列表上点击歌曲时  发送适配器的播放指针给服务
mUnitAdapter = new PlayUnitAdapter(ReadTextActivity.this, new View.OnClickListener() {
                @Override
                public void onClick(View v) { //播放
                    mUnitAdapter.mPosSomeTime = (int)v.getTag();
                    isplay = true;
                    PlayUnitPoiMsg.sendPoi(mUnitAdapter.mPosSomeTime, book_cover); //发送指针
                }
            });
            mBinding.recyclerView.setAdapter(mUnitAdapter);

以上便是一个简易在线播放器的UI页面的重点部分,可供参考

相关推荐
Dicky-_-zhang2 小时前
分布式锁实战:Redis与ZooKeeper对比选型与实现方案
java·jvm
Refrain_zc2 小时前
Android 应用内的APK 安装(可复制)
java
杨运交2 小时前
[020][缓存模块]基于 BeanCreator 的缓存管理器创建器模式设计与实践
java·spring·缓存
risc1234563 小时前
DocumentsWriterDeleteQueue 的核心设计思想
java·全文检索·lucene
风味蘑菇干3 小时前
Stream基础题目
java·算法
2501_932750263 小时前
Java反射机制基础入门
java·开发语言
500843 小时前
HCCL 集合通信编程:多卡协同的正确姿势
java·flutter·性能优化·electron·wpf
asdfg12589633 小时前
Java中的Comparator 和JS中的回调函数好相似
java·开发语言
会编程的土豆3 小时前
消息队列(MQ)入门笔记
java·笔记·spring