MediaPlay Api的使用

MediaPlay 用来控制音频、视频文件和流的回放。

 

  • 状态图

    MediaPlayer状态图

      

  • 回放控制是通过一个状态机来管理的。

    椭圆图代表MediaPlay实例可能存在的一个状态;

    弧线代表使状态之间转换的操作;

    单箭头代表同步的方法调用;

    双箭头代表异步的方法调用。

     

    一个MediaPlay实例有以下状态:

  • 当new或者reset被调用,来创建MediaPlay实例后,它是Idle状态。
  • 但release被调用后,它是end状态,在这两者之间是它的生命周期。
  • 通过new来构造实例和通过reset来产生实例,是有区别的,通过new实际就是通过create为给定的uri来创建mediaplayer,如果创建成功,其prepare方法会被自动调用,也自动调用了setDataSource,即完成了初始化。Reset方法会把实例转到未初始化状态,在调用reset之后,需要再次通过setDataSource,prepare方法来初始化它。
  • 在创建好MediaPlayer实例后,可以通过OnErrorListener.onError()指定错误的回调函数。当在不正确的状态下,调用某些方法时出错,会调用onError,同时把状态转到Error状态。
  • Mediaplayer实例不在使用,要立即调用release释放相关的资源。Release之后回到end状态,它将不可再使用,也没有任何方法可以让它回到其他状态。
  • 当有Error发生,就会转到Error状态,从Error状态恢复、在Error状态下再次使用,就要用reset来把它恢复到Idle状态。
  • 一个很好的编程实践是要注册OnErrorListener,来提防来自内部播放引擎的错误提示。
  • setDataSource会把状态转移到初始化状态。
  • Mediaplayer在回放started之前,必须先进入Prepared状态。
  • 有两种方式可以进入到Prepared状态,一种是同步的方式prepare(),一种是异步的方式prepareAsync()。Prepare()是同步的,对于文件来说,这个调用时Ok的,它会blocks直到mediaplayer准备好了回放;prepareAsync()是一步的,对于流类型的数据源,应该调用这个,它会立即返回,而不是blocking直到缓冲了足够的数据,当调用返回时它会先进入到Preparing状态,然后内部的播放引擎会继续完成剩下的准备工作,直到准备工作完成。当准备工作完成或者prepare()返回,如果注册了OnPreparedListener.OnPreapred()接口会被调用。在Prepared状态,一些属性才可以通过相关的方法调用。
  • 调用start()开始回放,start()成功返回,就会进入到stared状态,isPlaying()可以被调用来检查当前是否在started状态。
  • 在开始状态,播放引擎会调用用户提供的OnBufferingUpdateListener.onBufferingUpate()回调接口,当然前提是这个listener事先被注册了。当操作音视频流时,这个回调让应用程序可以记录跟踪缓冲区的状态。
  • 通过pause(),让回放进入到暂停状态。
  • 通过start(),可以恢复暂停状态的回放,然后状态回到Started状态。
  • Stop(),让回放从started、Paused、Prepared、PlaybackCompleted状态到Stopped状态。一旦进入到Stopped状态,只有再次通过Prepare,进入到Prepared状态才能继续。
  • 通过seekTo(long,int)调整播放位置。即使是异步调用,这个方法也是立即返回的,尤其是在流类型的情况下,实际的seek操作可能需要花些时间完成。当实际的seek操作完成,播放引擎调用注册的OnSeekComplete.onSeekComplete()回调。seekTo可以在prepared、Paused、playbackCompleted状态调用,当在这些状态调用时,如果这个流有视频帧,并且请求的位置有效,相应的那帧视频帧会被显示。
  • 可以通过getCurrentPosition来检索当前的播放位置,这可以用于跟踪播放进度。
  • 当回放到达流末尾,回放结束。如果通过setlooping设置了循环,mediaplayer应该停留在Started状态。如果回放是false,注册的OnCompletion.onCompletion()的回调被调用,这个调用表示现在是播放完成状态。

二,下面是怎样写一个媒体播放的应用。

MediaPlayer 是播放音频和视频的主要api。

AudioManager 管理设备上的音频源和音频输出通道。

Internet Permission,如果需要使用网络上的流,请求访问网络,需要添加权限

<uses-permission android:name=”android.permission.INTERNET”>

Wake Lock Permission,如果需要防止屏幕变暗,或者防止处理器休眠,或者使用MediaPlayer.setScreenOnWhilePlaying(),MediaPlayer.setWakeMode(),需要申请权限

<uses-permission android:name=”android.permission.WAKE_LOCK”>

MediaPlayer.java 可以获取,解码,播放音视频,支持的数据源:本地数据,来自数据库的URI,来自网络的URL。

 

播放一个本地元数据(保存在res/raw/目录下的资源):

MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.sound_file);
mediaPlayer.start();

这里的raw数据源,是一个文件,系统不需要对它做任何解析,但是文件内容应该是有合适的编码,并且是被支持的格式格式化过的媒体数据。

 

播放一个URL资源,来自于ContentResolver:

Uri myUri = ;
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(getApplicationContext(), myUri);
mediaPlayer.prepare();
mediaPlayer.start();

播放来自网络的资源:

String url = “http://”;
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(url);
mediaPlayer.prepare();//这个调用可能耗时,因为要缓冲…
mediaPlayer.start();

异步的Preparation。

Prepare()方法,可能执行较长时间,因为这可能涉及到获取、解码媒体数据,所以这个方法不应该在UI线程调用。相对的prepareAsync()可以在UI线程被调用,它在后台开始准备数据之后,立即返回了。

看下这两个方法的区别,java层没有太多注意的地方,直接看c++层代码:

mediaplayer.cpp
status_t MediaPlayer::prepareAsync()
{
    ALOGV("prepareAsync");
    Mutex::Autolock _l(mLock);
    return prepareAsync_l();
}
status_t MediaPlayer::prepare()
{
    ALOGV("prepare");
    Mutex::Autolock _l(mLock);
    mLockThreadId = getThreadId();
    if (mPrepareSync) {
        mLockThreadId = 0;
        return -EALREADY;
    }
    mPrepareSync = true;
    status_t ret = prepareAsync_l();
    if (ret != NO_ERROR) {
        mLockThreadId = 0;
        return ret;
    }

    if (mPrepareSync) {
        mSignal.wait(mLock);  // wait for prepare done
        mPrepareSync = false;
    }
    ALOGV("prepare complete - status=%d", mPrepareStatus);
    mLockThreadId = 0;
    return mPrepareStatus;
}

从以上源码可以看出,这两个方法都会调用prepareAsync_l,再往下调用播放引擎开始准备工作,区别是preapre()方法在调用prepareAsync_l之后,通过mSignal.wait(mLock)进入等待,唤醒的条件是prepare done,也即是收到MEDIA_PREPARED消息。

 

使用Service控制MediaPlayer。

如果希望app不在前台时,media可以在后台播放,需要启动一个service来控制mediaplayer实例。

异步的运行:

默认情况,所有service中的工作都在一个线程,这点跟activity类似。在同一个应用里面运行一个activity和一个service,他们默认是在同一个主线程中。因此,这种情况下,service需要快速的处理输入事件,在返回结果前,不应该做太多计算。如果有重量级的工作或者阻塞的调用,应该异步的来做:使用另外的线程,或者使用framework中的异步处理组件。

下面的例子,在主线程使用Mediaplayer,所以用prepareAsync,而不是prepare,同时应该实现MediaPlayer.onPreparedListener接口,以便在准备工作完成时被通知到。

Public class MyService extends Service implements MediaPlayer.OnPreparedListener {
	Private static final String ACTION_PLAY= “com.example.action.PLAY”;
MediaPlay mMediaPlayer = null;

Public int onStartCommond(Intent intent, int flags, int startId){
	If (intent.getAction().equals(ACTION_PLAY)){
		mMediaPlayer = …//initialize it
		mMediaPlayer.setOnPreparedListener(this);
		mMediaPlayer.prepareAsync();//这里不会block。
}
}

 

//准备完成,会被调用

Public void onPrepared(MediaPlayer player){
	Player.start();
}
}

处理异步操作的错误:

在同步调用时,错误会被正常的通知,同时带有exception信息,错误码。但是,在使用异步资源时,要确保错误可以适当的通知到应用,可以通过注册MediaPlayer.OnErrorListener来实现:

Public class MyService extends Service implements MediaPlayer.OnErrorListener{
	MediaPlayer mMediaPlayer;
	Public void initMediaPlayer(){
		//initialize the mediaplayer
		mMediaPlayer.setOnErrorListener(this);
}

@Override
Public Boolean onError(MediaPlayer mp, int what, in extra){
	//mediaplayer moved to Error state.再次使用,必须reset。
}
}

 

使用唤醒锁。

当回放音视频时,不想让系统休眠,可以使用wake locks,它可以给系统发个信号,表示应用正在使用系统功能,即使当前手机是idle状态,也要保持可用。

在初始化MediaPlayer之后,可以通过setWakeMode来让cpu保持运行,在playing期间,mediaplayer会持有一个特定锁,在paused、stopped之后释放这个锁。

mMediaPlayer.setWakeMode(getApplicationContext(), PowerManger.PARTIAL_WAKE_LOCK);

上面的方式,只是让cpu保持唤醒,如果需要使用网络资源,就要使用wifi-lock来保持wifi可用。这时要手动申请、释放。

在使用remote URL开始preparing时,创建、申请wifi-lock:

WifiLock wifiLock = ((WifiManager)getSystemService(Context.	WIFI_SERVICE)).createWifiLock(WifiManager.WIFI_MODE_FULL, “myLock”);
WifiLock.acquire();

在pause、stop时,或不在需要网络时,释放:

wifiLock.release();

 

作为一个前台Servie来运行。

通常service是放到后台去执行,这种情况service被中断然后又重新执行,用户通常是意识不到的。但是,如果用service来播放音乐,任何中断,都会被明显的感受到。另外,用户可能需要在service执行的过程中与其交互。这些情况下,service应该以foreground service来运行,前台service有更高的优先级,系统尽量不去kill它。当运行与前台时,service必须提供一个状态栏提示,以确保用户可以意识到这个运行的service,允许用户跟这个service交互。

在创建一个Notification后,调用startForeground使service运行与前台:

String songName;
PendingIntent pi = PendingIntent.getActivity(getApplicationContext(), 0, 
New Intent(getApplicationContext(),MainActivity.class),
PendingIntent.FLAG_UPDATE_CURRENT);
Notification notification = new Notification();
Notification.tickerText = text;
Notification.icon = R.drawable.play0;
Notification.flags |= Notificaiton.FLAG.ONGOING_EVENT;
Notification.setLatestEventInfo(getApplicationContext(), “MusicPlayerSample”,
“Playing:”+songName, pi);
   StartForeground(NOTIFICATION_ID, notificaiton);

 

当点击状态栏的这个notification时,系统会调用PendingIntent,来启动指定的Activity。

   不在需要这个service时,记得调用:

       stopForeground(true);

 

 

处理音频焦点。

Android是一个多任务的环境,这对于使用audio的app是一个挑战,因为只有一个音频输出通道,会有多个媒体服务竞争它的使用。从android2.2开始,平台提供了一种方式来交涉设备的音频输出通道的使用,这个机制就是Audio Focus。

当app需要输出music或者notification这样的音频时,需要去请求audio focus,一旦获取到focus,就可以自由的使用音频的输出通道,但是要保持对focus changes的监听,一旦被通知失去了焦点,应该立即kill这个audio或者把它转为安静的级别(也叫ducking),再次得到焦点时,恢复正常的回放。

音频焦点本质上是协作的方式,也就是说,建议app去遵从音频焦点的准则,但是这个规则不会被系统强制执行。如果app在失去焦点后,仍然要大声的播放music,系统是不会做什么来阻止这个行为。但是,用户的体验会较差。

AudioManager audioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
Int result = audioManager.requestAudioFocus(this,AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN);
If (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED){
	//could not get audio focus.
}

requestFocus()的第一个参数是AudioManager.OnAudioFocusChangeListener,在audio focus有变化时,onAudioFocusChange()会被调用,在service和activity中最好实现这个接口。

Class MyService extends Servie implements AudioManager.OnAudioFocusChangeListener {
	Public void onAudioFocusChange(int focusChange){
		//
}
}

 

其中的参数focusChange,值是AudioManager中定义的一些常量:

AUDIOFOCUS_GAIN:已经获取了audio focus。

AUDIOFOCUS_LOSS:你会失去audio focus一段时间,必须停止所有的音频回放,因为你可能一段时间不会重新获取到焦点,尽可能在这个时候去清理资源,比如释放掉MediaPlayer实例。

AUDIOFOCUS_LOSS_TRANSIENT:短暂的失去焦点,很快会再次获取到,也应该停止掉所有的音频回放,但是可以保留所有的资源,因为可能很快再次获取焦点。

AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:临时地失去焦点,但是允许继续播放音频(以较低的声音播放),而不是完全停止掉audio。

Public void onAudioFocusChange(int focusChange){
	Switch (focusChange) {
		Case AudioManager.AUDIOFOCUS_GAIN:
			//resume playback
			If (mMediaPlayer == null) initMediaPlayer();
			Else if (!mMediaPlayer != null) mMediaPlayer.start();
			
			mMediaPlayer.setVolume(1.0f, 1.0f);
			break;

		case AduioManager.AUDIOFOCUS_LOSS:
			if (mMediaPlayer.isPlaying()) mMediaPlayer.stop();
			mMediaPlayer.release();
			mMediaPlayer = null;
			break;

		case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
			if (mMediaPlayer.isPlaying()) mMediaPlayer.pause();
			break;

		case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
			if (mMediaPlayer.isPlaying()) mMediaPlayer.setVolume(0.1f, 0.1f);
			break;
}
}

 

AudioFocus这个api要在Android2.2之后使用,如果要向前兼容,可以通过反射机制调用AudioFocus的方法,或者通过一个单独的类实现所有audio focus 的特性,如AudioFocusHelper类:

public class AudioFocusHelper implements AudioManager.OnAudioFocusChangeListener {
    AudioManager mAudioManager;

    // other fields here, you'll probably hold a reference to an interface
    // that you can use to communicate the focus changes to your Service

    public AudioFocusHelper(Context ctx, /* other arguments here */) {
        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
        // ...
    }

    public boolean requestFocus() {
        return AudioManager.AUDIOFOCUS_REQUEST_GRANTED ==
            mAudioManager.requestAudioFocus(mContext, AudioManager.STREAM_MUSIC,
            AudioManager.AUDIOFOCUS_GAIN);
    }

    public boolean abandonFocus() {
        return AudioManager.AUDIOFOCUS_REQUEST_GRANTED ==
            mAudioManager.abandonAudioFocus(this);
    }

    @Override
    public void onAudioFocusChange(int focusChange) {
        // let your service know about the focus change
    }
}

 

如果系统运行在api8之前,可以创建AudioFocusHelper实例。

If (android.os.Build.VERSION.SDK_INT >= 8)
MAudioFocusHelper = new AudioFocusHelper(getApplicationContext(), this);
Else 
	mAudioFocusHelper = null;

执行清理。

MediaPlayer实例,会消耗一些重要的系统资源,所以应该只在需要的时间内保留,在完成后调用release();明确的调用清除方法,比依赖系统的垃圾回收更重要,因为垃圾回收机制在回收mediaplayer之前会花些时间,因为他只对内存需求敏感,而对media相关的资源缺乏不敏感。所以,重写onDestory方法时有必要的。

Public class MyService extends Servie {
	@Override
	Public void onDestory(){
		If (mMediaPlayer 1= null) mMediaPlayer.release();
}
}

 

当然,除了在关闭时释放,也应该寻找其他的机会释放mediaplayer。

 

处理AUDIO_BECOMING_NOISY intent。

优秀的app,当会让声音变成噪音的事件发生时(比如通过外放输出),应该要自动停止回放。比如当用耳机听音乐时,耳机突然跟设备断开了,不管怎样,这个行为不是自动发生的,如果没有实现这个特性,那么声音会通过外放输出,这个情况可能不是用户想要的。

通过在manifest注册这个intent,可以确保你的应用在这种情况下能停止音乐播放。

<receiver android:name = “.MusicIntentReceiver”>
	<Intent-filter>
		<action android:name=”android.media.AUDIO_BECOMING_NOISY”/>
	</Intent-filter>
</receiver>

Public class MusicIntentReceiver extends android.content.BroadcastReceiver {
	@Override
	Public void onReceiver(Context ctx, Intent intent){
		If (intent.gatAction().equals(
Android.media.AudioManager.ACTION_AUDIO_BECOMING_NOISY)) {
			//single to stop playback
}
}
}

 

从content resolver检索media。

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.
} else if (!cursor.moveFirst()) {
	//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);
}while (cursor.moveToNext())
}

Long id = //retrieve it from somewhere;
Uri contentUri = ContentUris.withAppendedId(
Android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id);
mMediaPlayer.setDataSource(getApplicationContext(), contentUri);

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值