Pro Android学习笔记(一二七):Media Frameworks(2):MediaPlayer的音频播放

时间:2022-12-26 13:14:10

文章转载只能用于非商业性质,且不能带有虚拟货币、积分、注册等附加条件。转载须注明出处http://blog.csdn.net/flowingflying/以及作者@恺风Wei

播放web音频内容的小例子片段

private final static String AUDIO_PATH =http://www.androidbook.com/akc/filestorage/android/documentfiles/3389/play.mp3;
private MediaPlayer mediaPlayer = null;
private int playbackPosition = 0;


/* 【1】需要INTERNET的权限,否则报错误:setDataSource failed.:status=0x80000000, 并由此出现MediaPlayer的unable to create player异常,由于此错误没有提示是权限问题,故不容易修复。
* 【2】在reference中有张状态机的图,但是不需要那么复杂,也可以很好地理解,就是:初始化-》准备-》播放
* 【3】如果格式不支持,例如播放wma格式,并不能不抓的异常,但是会出现MediaPlayer的错误:error(1,-2147483648)。属于MediaPlayer在播放时出现的错误,出现在start()之后,可以用setOnErrorListener进行捕抓,但是也就是给了那两个int,没什么有效信息   */
   
private void startPlayWebAudio(String url) throws Exception{
    killMediaPlayer();
  
    mediaPlayer = new MediaPlayer();
    mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
    mediaPlayer.setDataSource(url);
    mediaPlayer.setOnPreparedListener
(new OnPreparedListener() {         
        @Override
        public void onPrepared(MediaPlayer mp) { 
            Log.w("WEI","MediaPlayer is prepeared..");
            mp.start(); //开始播放
        }
    });      
    //由于audio内容,来自网络,下载需要时间,如果在UI线程,使用mediaPlayer.prepare()进行准备播放,很容易造成ANR异常, 所以采用异步的方式,并通过onPreparedListenner来获得转变成功的回调函数
    mediaPlayer.prepareAsync();  //准备过程,包括从网络下载
}

public void pausePlayingAudio( ){
    if(mediaPlayer != null && mediaPlayer.isPlaying()){
       playbackPosition = mediaPlayer.getCurrentPosition(); //记录当前位置,以便restart时在该位置开始播放
        mediaPlayer.pause(); //暂停
        Log.w("WEI","stop at " + playbackPosition/1000.0f + " secs.");
    }
}

public void restartPlayingAudio( ){
    if(mediaPlayer != null && !mediaPlayer.isPlaying()){
        mediaPlayer.seekTo(playbackPosition); //从指定位置开始播放
        mediaPlayer.start(); //播放
    }
}

public void stopPlayingAudio( ){ 
    if(mediaPlayer != null){
        //停止。对于stop(),如果需要start(),要先进行prepare()。但对于pause(),可以直接进行start()。
        mediaPlayer.stop();
        playbackPosition = 0;
    }
}

private void killMediaPlayer(){
    if(mediaPlayer != null){
        try{
            //应确保APP在不是用mediaPlayer的时候进行release()。对于Activity,可以在onDestory()中进行释放。 
            mediaPlayer.release();
            mediaPlayer = null;
        }catch(Exception e){    
            e.printStackTrace();
        }
    }
}

从抓包的情况看,通过HTTP GET请求获得音频文件,音频文件从该TCP通道返回,累计到一定内容时,开始播放,一边播放,一边接受。

对于web内容,出了支持http外,MediaPlay支持rtsp协议、HTTP/HTTPS live streaming,以及M3U播放列表。

如果循环播放,可以使用:

mediaPlayer.setLooping(true);

有时,我们需要监控什么时候播放结束,如果只播放一次,可能需要stop()和release(),或者播放其他的音频。实现方式如下:

mediaPlayer.setOnCompletionListener(new OnCompletionListener() {     
   @Override
    public void onCompletion(MediaPlayer mp) { 
        Toast.makeText(getApplicationContext(), "播放结束", Toast.LENGTH_SHORT).show();
        mp.stop(); //可能还需要加上release(),看需求,是结束播放,还是下一首。
    }
});

播放资源音频

(1)使用resource ID创建MediaPlayer对象的方式

将音频文件放置在res/raw/下,则作为apk的部分进行打包,并看通过R.raw.xxx对资源进行调用。对于这类音频内容,播放的代码片段如下:

private void startPlayRawAudio(int resourceId){ //例如R.raw.test(放入test.mp3)
    killMediaPlayer();
    mediaPlayer = MediaPlayer.create(this, resourceId);
    mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
    mediaPlayer.start(); //在此不需要prepare过程。 因为MediaPlayer.create(context,resourceID)相当于执行了new MediaPlayer(context, resourceID),以及prepare()。不需要再次进行prepare。由于资源就在本地,可以马上返回,不需要进行异步准备。
}

(2)采用file descriptor方式

对于本地音频,media player可以通过文件描述符file descriptor来获取,代码片段如下:

private void startPlayRawAndioUsingFileDescriptor(int resourceId) throws Exception{
    killMediaPlayer();
    AssetFileDescriptor fd = getResources().openRawResourceFd(resourceId);//resourceId例如R.raw.test,获取在/res/raw/下的音频文件的文件描述符
    if(fd != null){
        mediaPlayer = new MediaPlayer();
        mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
        //从音频文件的某个指定位置,播放到另一个指定文字,如果整个文件播放,可以简单地使用mediaPlayer.setDataSource(fd.getFileDescriptor()); 
        mediaPlayer.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
        fd.close(); //通过setDataSource(),mediaPlayer获取资源,可关闭fd
        mediaPlayer.prepare(); //本地资源,不需要等待,可以不采用异步处理的方式,当然用prepareAsync()也没有任何问题
        mediaPlayer.start();

    }
}

(3)对SD卡音频文件

对于存放在SD卡文件,可以和web音频那样,使用URL的方式,也可以使file descriptor的方式,使用URL更为简单。例如要播放SD卡的Music/songs.mp3,我们只需更换web音频例子中的URL地址即可,代码片段如下:

private final static String AUDIO_PATH =Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC) + "/song.mp3";

注意,这需要android.permission.READ_EXTERNAL_STORAGE的权限,否则会和要获取web资源但无访问Internet权限那样出错。

持续播放

音乐播放有时需要持续性播放,我们需要为MediaPlayer申请wake lock,相关代码如下,当然,不要忘记获取相关的权限"android.permission.WAKE_LOCK"。

mediaPlayer.setWakeMode(this, PowerManager.PARTIAL_WAKE_LOCK);

除了CPU外,WiFi也可能会关闭,如果音频资源来自网络,可以通过WiFi Lock来保持WiFi有效,相关的操作和wake lock相似。

//(1)创建WifiLock
WifiManager manager = (WifiManager)getSystemService(Context.WIFI_SERVICE);  
WifiLock lock = manager.createWifiLock("cn.wei.flowingflying");
……
//(2)持有WifiLock。对于网络资源的获取,通常通过manager.isWifiEnabled()获取是否当前Wifi有效,需要ACCESS_WIFI_STATE的权限。可以在程序中通过manager.setWifiEnabled(true|flase)来开启或关闭Wifi,需要CHANGE_WIFI_STATE的权限。WifiManager还可以获取连接信息,包括MAC地址等等。
lock.acquire();
……
//(3)释放WifiLock
if(lock.isHeld()) 
        lock.release();

因来电等原因需要对播放进行干预

在媒体播放的过程中,可能会出现来电,闹钟等,需要对正在播放的音乐进行暂停。我们可以通过AudioManager对这些事件进行监测,代码片段如下:

private AudioManager audioMrg = null;
private OnAudioFocusChangeListener audioListener = null;

@Override
protected void onCreate(Bundle savedInstanceState) { 
    … ….
    audioMrg = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
    audioListener = new OnAudioFocusChangeListener() {
        
        @Override
        public void onAudioFocusChange(int focusChange) { 
            debug("onAudioFocusChange code = " + focusChange);//我们的debug显示方法,用于跟踪信息
            if(mediaPlayer == null)
                return;

            switch(focusChange){
            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: //临时事情audio的focuse,例如有来电,进行音乐播放暂停操作 
                debug("AUDIOFOCUS_LOSS_TRANSIENT");
                pausePlayingAudio( );
                break;
               
            case AudioManager.AUDIOFOCUS_GAIN: //获得audio的focuse,对暂停的音乐继续播放
                debug("AUDIOFOCUS_GAIN");
                restartPlayingAudio( );
                break;
               
               
            case AudioManager.AUDIOFOCUS_LOSS:
                debug("AUDIOFOCUS_LOSS"); 
                stopPlayingAudio(null);
                break;
               
           
            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK://例如有短信的通知音,可以调低声音,无需silent
                debug("AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK"); 。
                //可以通过AudioManager.get调低声音,或简单地不做处理。
                debug("当前音量:"+ audioMrg.getStreamVolume(AudioManager.AUDIOFOCUS_GAIN));
                break;
               
            default:
                break;
            }
        }
    };
    int result = audioMrg.requestAudioFocus(audioListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);//返回granted或者failed,根据android的reference,对于市场未知的,例如播放音乐或者视频,采用AUDIOFOCUS_GAIN。对于短时间的,例如通知音,看采用AUDIOFOCUS_GAIN_TRANSIENT;允许叠加,共同放音,用AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK;对于不希望系统声音干扰的,例如进行memo录音、语音识别,采用AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE。
    if(result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED){
        debug("requestAudioFocus : granted");
    }else if(result == AudioManager.AUDIOFOCUS_REQUEST_FAILED){
        debug("requestAudioFocus : failed");
    }
  
}

@Override
protected void onDestroy() { 
    … ….
    audioMrg.abandonAudioFocus(audioListener);
    super.onDestroy();
}

一点补充

一旦我们设置了Data source,就很难去修改,可以通过reset()对MediaPlayer进行重新初始化,或者另外创建一个新的MediaPlayer对象。在prepare后,可以通过getCurrentPosition()、getDuration()、isPlaying()来检查当前状态,可以使用setLooping()和setVolume()进行设置。

小例子代码在:Pro Android学习:media framworks小例子


相关链接:我的Android开发相关文章