Android MediaPlayer 实现音乐播放器

一丶MediaPlayer 简介

Android 多媒体框架支持播放各种常见媒体类型,以便您轻松地将音频、视频和图片集成到应用中。您可以使用 MediaPlayer API,播放存储在应用资源(原始资源)内的媒体文件、文件系统中的独立文件或者通过网络连接获得的数据流中的音频或视频。

基本用法请参考:官网:MediaPlayer 概览

它支持多种不同的媒体源 ,例如:

  • 本地资源
  • 内部 URI,例如您可能从内容解析器那获取的 URI
  • 外部网址(流式传输)

在编写与 MediaPlayer 对象互动的代码时,请始终牢记该状态图。
在这里插入图片描述

二丶实现音乐播放器

效果图:
在这里插入图片描述

在写代码之前,我先告诉你们一个坑点,我在实现这个功能的时候,在导入raw文件时遇到了一个坑,找不到这个文件夹,如果你们也遇到了这种情况,参考我之前写的一篇博客,即可解决问题:https://blog.csdn.net/qq_27494201/article/details/96334284

MainActivity.java

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private Button btnStart,btnStop,btnPause,btnReplay;     //  播放器按钮
    private SeekBar seekBar;                    //  进度条
    private MediaPlayer mediaPlayer = null;     //  音乐播放控制对象,可以操控暂停、停止、播放、重置等等
    private Object obj = new Object();      //  对象锁,播放线程暂停时,让进度条线程进入等待状态
    private Thread seekThread;            //    线程
    private boolean isRun = false;      //  进度条线程控制
    private boolean suspended = false;  //  进度条线程等待状态控制



    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();     //  初始化控件
        initJian();     //  添加监听事件

        seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {              //  进度条的监听事件
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {

            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
                pause();        //  进度条开始前,调用 pause()
            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {              //  进度条开始后,调用 pause()
                if (mediaPlayer != null && !mediaPlayer.isPlaying()){
                    int progress2 = seekBar.getProgress();
                    int currentPosition2 = mediaPlayer.getDuration()*progress2/100;
                    continuePlay(progress2,currentPosition2);
                }
            }
        });

    }

    private void initJian() {
        btnStart.setOnClickListener(this);
        btnStop.setOnClickListener(this);
        btnReplay.setOnClickListener(this);
        btnPause.setOnClickListener(this);
    }

    private void initView() {
        btnStart = findViewById(R.id.btn_Start);
        btnStop = findViewById(R.id.btn_Stop);
        btnPause = findViewById(R.id.btn_Pause);
        btnReplay = findViewById(R.id.btn_Replay);
        seekBar = findViewById(R.id.seekBar);
    }

    @RequiresApi(api = Build.VERSION_CODES.M)
    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn_Start:                                //  播放
                if (mediaPlayer == null){           //  如果没有暂停
                    play();     //  调用 play 方法进行播放
                }else{
                    if (!mediaPlayer.isPlaying()){             //   如果处于暂停状态
                        int progress = seekBar.getProgress();   //  得到 SeekBar 的进度
                        int currentPosition = mediaPlayer.getCurrentPosition();     //  获取当前位置
                        continuePlay(progress,currentPosition);      //  在从暂停状态恢复播放时使用,除了继续播放音乐,还需要唤醒等待中的进度条绘制线程
                    }
                }
                break;
            case R.id.btn_Pause:                            //  暂停
                pause();
                break;
            case R.id.btn_Replay:                           //  停止
                if (mediaPlayer == null){          //   播放器对象还未创建或者已经销毁
                    play();
                }else{
                    if (!mediaPlayer.isPlaying()){      //  暂停状态
                        continuePlay(0,0);      //  进度条为0,第二个参数是:当前播放的位置,因为停止了,所以回到0
                    }else{                     //   正在播放状态不需要唤醒线程的操作,并且这种情况是点了一次停止以后再点停止就会变成播放的作用
                        mediaPlayer.seekTo(0);      //  从0开始播放
                        mediaPlayer.start();                //  开始播放
                    }
                }
                break;
            case R.id.btn_Stop:                             //  重播
                stop();                 //  这里重播相当于停止
                break;

        }
    }

    /*  线程用来根据音乐播放进度绘制进度条   */
    class SeekThread extends  Thread{
        int duration = mediaPlayer.getDuration();   //当前音乐总长度
        int currentPosition = 0;
        public void run(){
            while (isRun){
                currentPosition = mediaPlayer.getCurrentPosition(); //  获取当前音乐播放到了哪里
                seekBar.setProgress(currentPosition*100 / duration);
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (obj){
                    while (suspended){
                        try {
                            obj.wait();     //  音乐暂停时让进度条线程也暂停
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
    
    /*  初始化播放,一个是音乐播放,一个线程控制的进度条绘制  */
    @RequiresApi(api = Build.VERSION_CODES.M)
    private void play() {
        mediaPlayer = MediaPlayer.create(MainActivity.this,R.raw.music_bgw);    //  直接理解为拿到音频资源文件就行
        mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {    //  监听播放结束事件(停止、暂停、播放完了一首音乐)
            @Override
            public void onCompletion(MediaPlayer mp) {
                if (mediaPlayer != null){           //  如果是暂停的情况
                    stop();             //  调用暂停方法
                }
            }
        });
        isRun = true;                 //  进度条线程控制,ture为暂停进度条线程的绘制
        seekThread = new SeekThread();  //  实例化一个线程对象,开始发挥作用
        mediaPlayer.start();            //  开始播放音乐
        seekThread.start();             //  启动线程
    }

    private void stop() {
        if (mediaPlayer != null){        //     只要有资源
            seekBar.setProgress(0);      //     进度条回到0的位置
            isRun = false;               //     线程
            mediaPlayer.stop();          //     停止播放音乐
            mediaPlayer.release();       //     释放资源
            mediaPlayer = null;          //     销毁音乐对象
            seekThread = null;           //     销毁线程
        }
    }

    private void pause() {
        if (mediaPlayer != null && mediaPlayer.isPlaying()){    //  如果音乐对象是有资源 并且 音乐正在播放的状态下
            mediaPlayer.pause();         //     暂停
            suspended = true;           //  进度条线程等待状态控制
        }
    }

    //  在从暂停状态恢复播放时使用,除了继续播放音乐,还需要唤醒等待中的进度条绘制线程
    private void continuePlay(int progress, int currentPosition) {
        mediaPlayer.seekTo(currentPosition);           //   跳转到对应时间进行播放
        mediaPlayer.start();                //  开始(暂停也是一种开始,跟播放一样,只是暂停是要回到之前的位置继续播放)
        seekBar.setProgress(progress);      //  设置回到之前的位置,这个progress是之前播放到的进度!
        suspended = false;               //  进度条线程等待状态控制
        synchronized (obj){           //  对象锁,播放线程暂停时,让进度条线程进入等待状态
            obj.notify();       //  唤醒线程,开始绘制进度条
        }
    }
    
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <SeekBar
        android:id="@+id/seekBar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="50dp"/>

    <LinearLayout
        android:id="@+id/ly_main"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="200dp">

    <Button
        android:id="@+id/btn_Start"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:textSize="18dp"
        android:text="播放"/>

    <Button
        android:id="@+id/btn_Stop"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:textSize="18dp"
        android:text="重播"/>
        
    <Button
        android:id="@+id/btn_Pause"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:textSize="18dp"
        android:text="暂停"/>


    <Button
        android:id="@+id/btn_Replay"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:textSize="18dp"
        android:text="停止"/>

    </LinearLayout>

</LinearLayout>

三丶在 Service 中使用 MediaPlayer

涉及四大知识点

  • 异步运行
  • 处理异步错误
  • 使用唤醒锁定
  • 进行清理

MainActivity.java

        Intent intent = new Intent(MainActivity.this,MyService.class);
        intent.setAction("com.example.action.PLAY");
        startService(intent);

MyService.java

public class MyService extends Service implements MediaPlayer.OnPreparedListener, MediaPlayer.OnErrorListener {
    private static final String ACTION_PLAY = "com.example.action.PLAY";
    MediaPlayer mediaPlayer = null;

    public MyService() {
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("wangrui","onCreate");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("wangrui","onStartCommand");
        if (intent.getAction().equals(ACTION_PLAY)){
            //写法一:
            mediaPlayer = MediaPlayer.create(getApplicationContext(),R.raw.xiaye);
            mediaPlayer.setLooping(true);
            //在主线程中使用 MediaPlayer 时,您应该调用 prepareAsync() 而非 prepare(),并实现 MediaPlayer.OnPreparedListener,以便在准备工作完成后获得通知并开始播放
            mediaPlayer.setOnPreparedListener(this);
            //使用异步资源时,应确保以适当的方式向应用发出错误通知
            mediaPlayer.setOnErrorListener(this);
//            //防止CPU休眠,MediaPlayer框架会在播放时保持指定的锁定状态,并在暂停或停止播放时释放锁定
//            mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
//            //防止WLAN休眠
//            WifiManager.WifiLock wifiLock = ((WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE)).createWifiLock(WifiManager.WIFI_MODE_FULL,"mylock");
//            wifiLock.acquire(); //手动持锁。注意:我们在暂停或停止媒体内容,或者当您不再需要网络时,手动释放该锁定 wifiLock.release();

            try {
                mediaPlayer.prepareAsync(); //准备async以不阻塞主线程
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        return super.onStartCommand(intent, flags, startId);
    }

    //在MediaPlayer准备就绪时调用
    @Override
    public void onPrepared(MediaPlayer mediaPlayer) {
        mediaPlayer.start();
    }

    @Override
    public boolean onError(MediaPlayer mediaPlayer, int i, int i1) {
        Log.d("wangrui","MediaPlayer 发生错误,请重置");
        return false;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mediaPlayer != null) mediaPlayer.release(); //释放
    }

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

四丶总结

在本例启动了一个用来绘制进度条的线程 SeekThread,不断获取当前播放的进度,根据进度比例绘制进度条,当音乐播放暂停时,由对象锁 obj 让进度条线程进入等待状态,自爱此播放时唤醒线程,让进度条继续绘制。

如果有问题,可以联系我qq:1787424177

最后,我真诚的希望能评论一句嘛,让我知道你来过,我会很开心的

  • 16
    点赞
  • 71
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 11
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

王睿丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值