音乐播放器第二版

音乐播放器第二版

简介

音乐播放器基于第一版所做的改进为增加了后台服务,也就是退出app后音乐在后台播放。

整体构思:因为要实现在后台播放,所以播放功能整体放入服务中。点击事件依然在主活动中实现,这样做的原因有两个:1、点击事件是发送在主界面中的活动,与服务无关。2、点击事件之后会发送UI更新,服务不能进行UI操作。

实现步骤:

  1. 新建MusicList类作为数据源(即把第一版中的加载数据源方法提取为一个类,目的是服务和活动都能获取此数据源,不需要靠intent进行传递。)
  2. 新建服务,把有关播放的代码放入服务中,新增播放逻辑(因为实现了后台播放,如果不新增播放逻辑的话,当你点击另一首歌曲时,原有歌曲不会停止,会继续播放,这也是初步写完第二版之后所遇到的bug)
  3. 在主活动中和服务进行通信,由点击事件对服务进行指挥。

新建MusicList类作为数据源

public class MusicList {

    static LocalMusicBean musicBean;

    @SuppressLint("NotifyDataSetChanged")
    public static List<LocalMusicBean> getMusic(Context context){
        List<LocalMusicBean> mData = new ArrayList<>();
        //获取ContentResolver对象
        String[] selectionArgs = new String[]{context.getString(R.string.music)};
        String selection = MediaStore.Audio.Media.DATA + context.getString(R.string.like);
        // 媒体库查询语句
        @SuppressLint("Recycle") Cursor cursor = context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, selection, selectionArgs, MediaStore.Audio.AudioColumns.IS_MUSIC);
        //遍历Cursor
        int id = 0; //歌曲排序的序列号
        while (cursor.moveToNext()) {
            String song = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));
            String singer = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST));
            String album = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM));
            long duration = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION));
            @SuppressLint("SimpleDateFormat") SimpleDateFormat dateFormat = new SimpleDateFormat(context.getString(R.string.timeformat));
            String time = dateFormat.format(new Date(duration));
            //将一行当中的数据封装到对象当中
            if (!time.equals(context.getString(R.string.timeiszeero))) {
                id++;
                String sid = String.valueOf(id);
                String path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA));
                musicBean = new LocalMusicBean(sid, song, singer, album, time, path);
                mData.add(musicBean);
            }
        }
        return mData;
    }
}

注意点:

  1. 与第一版的加载数据源方法整体代码一致,但是这里需要注意的是:适配器的更新不放在此类中,适配器的更新是发送在主活动中事项,MusicList类只作为数据源类,即搜索本地歌曲并且添加到集合中。

新建服务

public class MusicService extends Service {

    //记录当前正在播放的音乐的位置
    private  int currentPosition = -1;

    //记录上次播放音乐的位置
    private int lastPosition;

    //记录当前播放的音乐播放到哪了
    private  int currentPositionInMusic = 0;

    //音乐实例
    private LocalMusicBean musicBean;

    //数据源
    private List<LocalMusicBean> mData;

    //当前播放的音乐
    MediaPlayer currentMediaPlayer;

    //上次播放的音乐
    MediaPlayer lastMediaPlayer;

    Bundle bundle;
    
    public MusicService() {
    }

    @Override
    public void onCreate() {
        super.onCreate();
        mData = MusicList.getMusic(this);
    }

    class MusicBinder extends Binder{
        public MusicService getService(){
            return MusicService.this;
        }
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //获取点击的音乐实例
        bundle = intent.getExtras();
        currentPosition = bundle.getInt(getString(R.string.key));
        musicBean = mData.get(currentPosition);

        if (lastPosition != currentPosition){
            playMusicInMusicBean(musicBean);
            lastPosition = currentPosition;
        }
        else
        {
            currentMediaPlayer = lastMediaPlayer;
            currentPosition = lastPosition;
        }
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public IBinder onBind(Intent intent) {
       return new MusicBinder();
    }

    //根据歌曲来进行播放
    public void playMusicInMusicBean(LocalMusicBean musicBean) {

        //如果当前有音乐播放,停止播放
        if (lastMediaPlayer != null){
            lastMediaPlayer.stop();
            lastMediaPlayer.release();
        }

        currentMediaPlayer = new MediaPlayer();

        lastMediaPlayer = currentMediaPlayer;

        try {
            currentPositionInMusic = 0;
            currentMediaPlayer.setDataSource(musicBean.getPath());
            playMusic();
        } catch (IOException ioException) {
            ioException.printStackTrace();
        }
    }


    //播放音乐
    void playMusic(){
        if (currentPositionInMusic == 0){
            //从头开始播放
            if (currentMediaPlayer != null && !currentMediaPlayer.isPlaying()){
                try {
                    currentMediaPlayer.prepare();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                currentMediaPlayer.start();
            }
        }else{
            //从暂停处开始播放
            currentMediaPlayer.seekTo(currentPositionInMusic);
            currentMediaPlayer.start();
        }
    }

    //暂停播放音乐
    void pauseMusic() {
        if (currentMediaPlayer != null && currentMediaPlayer.isPlaying()){
            currentPositionInMusic = currentMediaPlayer.getCurrentPosition();
            currentMediaPlayer.pause();
        }
    }

    //上一首或者下一首
    void lastOrNext(int position){
        currentPosition = position;
        currentPositionInMusic = 0;
        musicBean = mData.get(currentPosition);
        playMusicInMusicBean(musicBean);
    }
}

新增逻辑:

创建一个记录上次播放的音乐的位置的变量,创建一个上次播放对象。

在播放前判断本次要播放的音乐和正在播放的音乐是否为同一个,不是就播放要播放的音乐,是不进行操作。

播放时要判断当前是否有音乐在播放,有则需停止,这里需要注意的是此时还在播放的音乐术语lastMediaPlayer对象播放的,所以停止操作要针对lastMediaPlayer对象。同时更新lastMediaPlayer对象。

上一首下一首的代码合并,由活动给出位置信息,是加还是减,然后服务更新位置信息并进行播放,代码变得简洁且重复性代码减少。

在主活动中和服务进行通信

 protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main_test);
        initView();

        mData = MusicList.getMusic(this);

        //设置适配器
        musicAdapter = new LocalMusicAdapter(this, mData);
        //设置适配器
        musicRv.setAdapter(musicAdapter);
        //设置布局管理器
        LinearLayoutManager layoutManager = new LinearLayoutManager(this, RecyclerView.VERTICAL, false);
        musicRv.setLayoutManager(layoutManager);

        //加载本地数据源

        musicAdapter.notifyDataSetChanged();

        //设置每一项的点击事件
        setEventListener();
    }
    private final ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            musicService = ((MusicService.MusicBinder) service).getService();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };


    //设置recyclerView的item点击事件
    private void setEventListener() {
        musicAdapter.setOnItemClickListener((view, position) -> {
            currentPosition = position;
            Intent intent = new Intent(MainActivity.this, MusicService.class);
            Bundle bundle = new Bundle();
            bundle.putInt(getString(R.string.key),currentPosition);
            intent.putExtras(bundle);
            startService(intent);
            bindService(intent, conn, BIND_AUTO_CREATE);
            initCurrentView();
            playIv.setImageResource(R.mipmap.icon_pause);
        });
    }
    private void initCurrentView(){
        musicBean = mData.get(currentPosition);
        songTv.setText(musicBean.getSong());
        singerTv.setText(musicBean.getSinger());
    }



    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(conn);
    }

代码逻辑:

数据源由MusicList类提供。这里需要注意的是,活动需要传入上下文给MusicList类,因为该类需要搜索手机本地的所有歌曲。

增加一个ServiceConnection内部类,在此类中获取Service的实例。

在recyclerView的item的点击事件中对服务进行绑定和启动,原因:服务是用来实现音乐后台播放功能的,只有当recyclerView的item被点击后,播放音乐的功能才正式启动,此时启动服务是最好的。

在启动服务之前需要向intent传入位置信息,Service中取出。这里通过bundle放入,Service中也通过bundle取出。试验过putIntEctrat,失败,debug发现无论点击何处,传入和取出的位置都为-1,失败后进行学习和试验发现bundle可以实现目的。

服务的解绑在onDestroy中进行。

public void onClick(View view) {
        switch (view.getId()){
            case R.id.iv_current_last:
                if (currentPosition == -1){
                    //没有选中要播放的音乐
                    Toast.makeText(this,"请选择要播放的音乐",Toast.LENGTH_LONG).show();
                    return;
                }
                if (currentPosition == 0){
                    Toast.makeText(this,"已经是第一首了 ",Toast.LENGTH_LONG).show();
                }
                else {
                    currentPosition--;
                    musicService.lastOrNext(currentPosition);
                }
                break;
            case R.id.iv_current_play:
                if (currentPosition == -1){
                    //没有选中要播放的音乐
                    Toast.makeText(this,"请选择要播放的音乐",Toast.LENGTH_LONG).show();
                    return;
                }
                if (musicService.currentMediaPlayer.isPlaying()){
                    //处于播放状态,需要暂停音乐
                    musicService.pauseMusic();
                    playIv.setImageResource(R.mipmap.icon_play);
                }else {
                    musicService.playMusic();
                    playIv.setImageResource(R.mipmap.icon_pause);
                }
                break;
            case R.id.iv_current_next:
                if (currentPosition == -1){
                    Toast.makeText(this,"请选择要播放的音乐 ",Toast.LENGTH_LONG).show();
                    return;
                }else if (currentPosition == mData.size()-1){
                    Toast.makeText(this,"已经是最后一首了",Toast.LENGTH_LONG).show();
                }else {
                    currentPosition++;
                    musicService.lastOrNext(currentPosition);
                }
                break;
            default:
                break;
        }
    }

按钮的点击事件的响应由Service中的方法完成。但是UI更新在主活动中进行。

音乐播放器第一版文章链接:https://blog.csdn.net/qq_45779316/article/details/119802909?spm=1001.2014.3001.5501

完整代码:https://gitee.com/luozhaosong/new-music-app

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值