【Android】MediaPlayer之音频播放

作者: 吾非言 | 来源:发表于2017-10-15 18:26 被阅读579次

    作者:邹峰立,微博:zrunker,邮箱:zrunker@yahoo.com,微信公众号:书客创作,个人平台:www.ibooker.cc

    本文选自书客创作平台第45篇文章。阅读原文

    书客创作

    如果对MediaPlayer不理解推荐这篇文章【Android】MediaPlayer生命周期分析

    要想利用MediaPlayer实现音频的播放,首先要对MediaPlayer进行初始化工作,得到MediaPlayer对象,在通过MediaPlayer进行相应的操作。

    一般过程:初始化MediaPlayer - 加载媒体源 - 准备 - 开始播放

    MediaPlayer mediaPlayer = new MediaPlayer();
    mediaPlayer.setDataSource("...");
    mediaPlayer.prepare();
    mediaPlayer.start();
    

    MediaPlayer支持多种不同的媒体源: 本地资源、内部的URI,比如一个你可能会从ContentResolver获取的uri、外部URL(流)。

    1、raw文件中媒体源:假如res/raw文件中包含一个sound_music.mp3文件。

    MediaPlayer mediaPlayer = MediaPlayer.create(this, R.raw.sound_music);
    

    2、assets文件中媒体源:假如在assets中包含一个sound_music.mp3文件。

    try {
        AssetFileDescriptor fd = getAssets().openFd("sound_music.mp3");
        MediaPlayer mediaPlayer = new MediaPlayer();
        mediaPlayer.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
    } catch (IOException e) {
        e.printStackTrace();
    }
    

    3、SD卡中媒体源:假如在SD卡中包含一个sound_music.mp3文件。

    try {
        MediaPlayer mediaPlayer = new MediaPlayer();
        String path = "/sdcard/sound_music.mp3";
        mediaPlayer.setDataSource(path);
    } catch (IOException e) {
        e.printStackTrace();
    }
    

    4、网络资源:假如有一个网络资源http://ibooker.cc/ibooker/musics/sound_music.mp3

    MediaPlayer mediaPlayer = new MediaPlayer();
    // 方式一
    //    Uri uri = Uri.parse("http://ibooker.cc/ibooker/musics/sound_music.mp3");
    //    mediaPlayer.setDataSource(this, uri);
    
    // 方式二
    mediaPlayer.setDataSource("http://ibooker.cc/ibooker/musics/sound_music.mp3");
    

    MediaPlayer常用方法

    int getCurrentPosition();// 得到当前播放位置(ms)
    int getDuration();// 得到文件的时间(ms)
    void setLooping(boolean var1);// 设置是否循环播放
    boolean isLooping();// 是否循环播放
    boolean isPlaying();// 是否正在播放
    void pause();// 暂停
    void prepare();// 同步准备
    void prepareAsync();// 异步准备
    void release();// 释放MediaPlayer对象
    void reset();// 重置MediaPlayer对象
    void seekTo(int msec);// 指定播放位置(以毫秒为单位)
    void setDataSource(String path);// 设置播放资源
    void setScreenOnWhilePlaying(boolean screenOn);// 设置播放的时候一直让屏幕变亮
    void setWakeMode(Context context, int mode);// 设置唤醒模式
    void setVolume(float leftVolume, float rightVolume);// 设置音量,参数分别表示左右声道声音大小,取值范围为0~1
    void start();// 开始播放
    void stop();// 停止播放
    

    MediaPlayer常用事件监听

    播放出错监听

    MediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
        @Override
        public boolean onError(MediaPlayer mediaPlayer, int i, int i1) {
            return false;
        }
    });
    

    播放完成监听

    MediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
        @Override
        public void onCompletion(MediaPlayer mediaPlayer) {
            // todo
        }
    });
    

    网络流媒体缓冲监听

    MediaPlayer.setOnBufferingUpdateListener(new MediaPlayer.OnBufferingUpdateListener() {
        @Override
        public void onBufferingUpdate(MediaPlayer mediaPlayer, int i) {
            // i 0~100
            Log.d("Progress:", "缓存进度" + i + "%");
        }
    });
    

    准备Prepared完成监听

    MediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
        @Override
        public void onPrepared(MediaPlayer mediaPlayer) {
            // todo
        }
    });
    

    进度调整完成SeekComplete监听,主要是配合seekTo(int)方法

    MediaPlayer.setOnSeekCompleteListener(new MediaPlayer.OnSeekCompleteListener() {
        @Override
        public void onSeekComplete(MediaPlayer mediaPlayer) {
            // todo
        }
    });
    

    使用锁

    当音频在后台播放时,设备可能进入到休眠状态,此时系统会关闭一些不必要的资源,包括CPU和wifi等等...如果想要在后台播放或者缓冲音乐并且不想被系统干扰必须使用锁,并且在paused或者stopped状态下释放它。

    // 设置设备进入锁状态模式-可在后台播放或者缓冲音乐-CPU一直工作
    mediaPlayer.setWakeMode(this, PowerManager.PARTIAL_WAKE_LOCK);
    

    同时需要在清单文件AndroidManifest.xml中添加权限

    <uses-permission android:name="android.permission.WAKE_LOCK" />
    

    上面代码只会确保cpu一直工作,如果你使用wifi播放流媒体,你还需要持有wifi锁

    // 如果你使用wifi播放流媒体,你还需要持有wifi锁
    WifiManager.WifiLock wifiLock = ((WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE)).createWifiLock(WifiManager.WIFI_MODE_FULL, "wifilock");
    wifiLock.acquire();
    

    当你停止播放或者不再需要连接网络释放

    wifiLock.release();
    

    处理音频焦点

    android是一个支持多任务的系统,这给使用audio的程序带来了一定的风险,因为可能几个程序会来竞争音频输出设备,在android2.2之前没有内置的机制来处理这个问题,导致体验很差。比如用户在听音乐时,另一个应用需要向用户提示一个非常重要的通知,但音乐声盖过了通知音,导致用户错失了最佳接收提示的时间,为了协调设备的音频输出,android提出了Audio Focus机机制,获取audio focus必须调用AudioManager的requestAudioFocus()方法。

    // 处理音频焦点-处理多个程序会来竞争音频输出设备
    AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        // 征对于Android 8.0+
        AudioFocusRequest audioFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
                        .setOnAudioFocusChangeListener(focusChangeListener).build();
        audioFocusRequest.acceptsDelayedFocusGain();
        audioManager.requestAudioFocus(audioFocusRequest);
    } else {
        // 小于Android 8.0
        int result = audioManager.requestAudioFocus(focusChangeListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
        if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
            // could not get audio focus.
        }
    }
    

    focusChange参数值如下

      1. AUDIOFOCUS_GAIN:获取audio focus
      1. AUDIOFOCUS_LOSS:失去audio focus很长一段时间,必须停止所有的audio播放,清理资源
      1. AUDIOFOCUS_ LOSS_TRANSIENT:暂时失去audio focus,但是很快就会重新获得,在此状态应该暂停所有音频播放,但是不能清除资源
      1. AUDIOFOCUS_ LOSS_TRANSIENT _CAN_DUCK:暂时失去 audio focus,但是允许持续播放音频(以很小的声音),不需要完全停止播放。
    AudioManager.OnAudioFocusChangeListener focusChangeListener = new AudioManager.OnAudioFocusChangeListener() {
        @Override
        public void onAudioFocusChange(int focusChange) {
            switch (focusChange) {
                case AudioManager.AUDIOFOCUS_GAIN:
                    // 获取audio focus
                    if (mediaPlayer == null)
                        mediaPlayer = new MediaPlayer();
                    else if (!mediaPlayer.isPlaying())
                        mediaPlayer.start();
                    mediaPlayer.setVolume(1.0f, 1.0f);
                    break;
                case AudioManager.AUDIOFOCUS_LOSS:
                    // 失去audio focus很长一段时间,必须停止所有的audio播放,清理资源
                    if (mediaPlayer.isPlaying())
                        mediaPlayer.stop();
                    mediaPlayer.release();
                    mediaPlayer = null;
                    break;
                case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                    // 暂时失去audio focus,但是很快就会重新获得,在此状态应该暂停所有音频播放,但是不能清除资源
                    if (mediaPlayer.isPlaying())
                        mediaPlayer.pause();
                    break;
                case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                    // 暂时失去 audio focus,但是允许持续播放音频(以很小的声音),不需要完全停止播放。
                    if (mediaPlayer.isPlaying())
                        mediaPlayer.setVolume(0.1f, 0.1f);
                    break;
            }
        }
    };
    

    处理AUDIO_ BECOMING_NOISY意图

    当用户插着耳机听音乐突然拔掉耳机,如果没有对以上意图进行处理,音频将会通过外部扬声器来播放音频,这也许不会是用户想要的,所以我们必须对此处理。

    可以使用动态广播broadcastReceiver

    broadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {
                // 拔掉耳机时候进行相应的操作
            }
        }
    };
    registerReceiver(broadcastReceiver, new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY));
    

    然后在页面onDestory方法中注销广播即可。

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unregisterReceiver(broadcastReceiver);
    }
    

    当然也可以使用静态广播

    1、在manifest文件中注册一个广播

    <receiver android:name=".MusicIntentReceiver">
           <intent-filter> 
                  <action android:name="android.media.AUDIO_BECOMING_NOISY" />              
          </intent-filter>
    </receiver>
    

    2、注册MusicIntentReceiver 广播来处理这个intent

    public class MusicIntentReceiver extends BroadcastReceiver {
    
        @Override
        public void onReceive(Context ctx, Intent intent) {
            if (intent.getAction().equals(android.media.AudioManager.ACTION_AUDIO_BECOMING_NOISY)) {
                // signal your service to stop playback
                // (via an Intent, for instance)
            }
        }
    }
    

    检索媒体文件

    // 通过ContentResolver来获取外部媒体文件
    ContentResolver contentResolver = getContentResolver();
    Uri uri = android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
    Cursor cursor = contentResolver.query(uri, null, null, null, null);
    if (cursor == null) {
        // query failed, handle error.
    } else if (!cursor.moveToFirst()) {
        // no media on the device
    } else {
        int titleColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media.TITLE);
        int idColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media._ID);
        do {
            long thisId = cursor.getLong(idColumn);
            String thisTitle = cursor.getString(titleColumn);
            // ...process entry...
        } while (cursor.moveToNext());
    }
    
    // 配合MediaPlayer使用
    try {
        long id = /* retrieve it from somewhere */;
        Uri contentUri = ContentUris.withAppendedId(android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, thisId);
        mediaPlayer = new MediaPlayer();
        mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
        mediaPlayer.setDataSource(getApplicationContext(), contentUri);
        // ...prepare and start...
    } catch (IOException e) {
        e.printStackTrace();
    }
    

    简易播放音乐器案例

    常见的音乐播放器都有,上一首、下一首、播放、暂停、停止等功能,那接下来就用一个简易的音乐播放器来说明MediaPlayer的使用。首先看一下布局效果图:

    布局效果图
    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#FFFFFF">
    
        <LinearLayout
            android:id="@+id/layout_bottom"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:layout_alignParentBottom="true"
            android:background="#F5F5F5"
            android:orientation="horizontal">
    
            <TextView
                android:id="@+id/tv_pre"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:gravity="center"
                android:text="上一首"
                android:textSize="14sp" />
    
            <TextView
                android:id="@+id/tv_pause"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:gravity="center"
                android:text="暂停"
                android:textSize="14sp" />
    
            <TextView
                android:id="@+id/tv_start"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:gravity="center"
                android:text="播放"
                android:textSize="14sp" />
    
            <TextView
                android:id="@+id/tv_stop"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:gravity="center"
                android:text="停止"
                android:textSize="14sp" />
    
            <TextView
                android:id="@+id/tv_next"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:gravity="center"
                android:text="下一首"
                android:textSize="14sp" />
    
        </LinearLayout>
    
        <TextView
            android:id="@+id/tv_music"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_above="@id/layout_bottom"
            android:gravity="center"
            android:orientation="vertical"
            android:text="@string/app_name"
            android:textSize="20sp" />
    
    </RelativeLayout>
    

    在该实例中以加载网络资源为例,这里定义一个字符串数据保存网络URL。

    private String[] musics = {"http://ibooker.cc/ibooker/musics/1234.mp3",
                "http://ibooker.cc/ibooker/musics/2345.mp3"}; // 设置音频资源(网络)
    

    在程序一来是,需要对MediaPlayer进行初始化工作,所以定义一个全局变量MediaPlayer。

    // 初始化MediaPlayer
    private void initMediaPlayer() {
        if (mediaPlayer == null)
            mediaPlayer = new MediaPlayer();
        // 设置音量,参数分别表示左右声道声音大小,取值范围为0~1
        mediaPlayer.setVolume(0.5f, 0.5f);
        // 设置是否循环播放
        mediaPlayer.setLooping(false);
    }
    

    当点击播放按钮,程序要马上播放相应的音乐文件,因为是加载网络资源,所以在这里采用prepareAsync进行准备,同时设置OnPreparedListener监听是否准备完成。

    // 播放
    private void play() {
        try {
            if (mediaPlayer == null)
                initMediaPlayer();
            if (isPause) {
                mediaPlayer.start();
                updateDescTv();
            } else {
                // 重置mediaPlayer
                mediaPlayer.reset();
                // 重新加载音频资源
    //         Uri uri = Uri.parse(musics[current_item]);
    //         mediaPlayer.setDataSource(this, uri);
                mediaPlayer.setDataSource(musics[current_item]);
                // 准备播放(同步)-预期准备,因为setDataSource()方法之后,MediaPlayer并未真正的去装载那些音频文件,需要调用prepare()这个方法去准备音频
    //         mediaPlayer.prepare();
                // 准备播放(异步)
                mediaPlayer.prepareAsync();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    

    注:isPause是用来标记当前播放是否处于暂停状态。

    // 异步准备Prepared完成监听
    mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
        @Override
        public void onPrepared(MediaPlayer mediaPlayer) {
            // 开始播放
            mediaPlayer.start();
            updateDescTv();
        }
    });
    

    updateDescTv();方法是用来显示播放进度。这里是通过开启一个线程来时时监听播放进度。

    // 开启线程,修改descTv
    private void updateDescTv() {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    while (mediaPlayer != null && mediaPlayer.isPlaying()) {// 判断音频是否正在播放
                        myHandler.sendEmptyMessage(100);
                        Thread.sleep(1000);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
        if (executorService == null || executorService.isShutdown())
            executorService = Executors.newSingleThreadExecutor();
        executorService.execute(thread);
    }
    
    MyHandler myHandler = new MyHandler(this);
    
    private static class MyHandler extends Handler {
        private WeakReference<Activity> mActivity;
    
        MyHandler(Activity activity) {
            mActivity = new WeakReference<>(activity);
        }
    
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            MainActivity currentActivity = (MainActivity) mActivity.get();
    
            switch (msg.what) {
                case 100:
                    if (currentActivity.mediaPlayer != null)
                        currentActivity.descTv.setText("播放进度:" + currentActivity.mediaPlayer.getCurrentPosition() * 100 / currentActivity.mediaPlayer.getDuration() + "%");
                    break;
            }
        }
    }
    

    要想实现下一首和上一首的功能,还需要定义一个变量current_item标记当前播放哪一首。

    // 下一首
    private void nextMusic() {
        current_item++;
        if (current_item >= musics.length)
            current_item = 0;
        play();
    }
    
    // 上一首
    private void preMusic() {
        current_item--;
        if (current_item < 0)
            current_item = musics.length - 1;
        play();
    }
    

    当点击暂停时

    if (mediaPlayer.isPlaying()) {
        isPause = true;
        mediaPlayer.pause();
    }
    

    当点击停止时

    if (mediaPlayer.isPlaying())
        mediaPlayer.reset();
    

    最后在页面onStop()中回收资源即可

    @Override
    protected void onStop() {
        super.onStop();
        // 释放mediaPlayer
        if (mediaPlayer != null) {
            mediaPlayer.stop();
            mediaPlayer.release();
            mediaPlayer = null;
        }
    }
    

    Github地址
    阅读原文


    微信公众号:书客创作

    相关文章

      网友评论

      本文标题:【Android】MediaPlayer之音频播放

      本文链接:https://www.haomeiwen.com/subject/rnsfuxtx.html