Android -- MediaPlayer之Media Playback基础介绍
文章翻译自Android官方文档:http://www.android-dev.cn/guide/topics/media/mediaplayer.html#viacontentresolver
多媒体回放
Android多媒体框架包含了对各种常见媒体类型文件的播放支持,所以我们可以简单地将音频、视频、图片整合进我们的应用程序之中。我们可以使用MediaPlayer播放存储在应用程序中的原始资源,也可以播放文件系统中的独立文件,也可以播放通过网络获取的数据流。
这篇文档向我们展示了如何写出一个拥有良好表现和体验的用户与系统交互的多媒体程序。
提醒:我们只能在标准输出设备上播放音频数据。当前,这些设备是手机设备的扬声器或者蓝牙头戴式耳机。我们不能在通话过程中播放声音文件。
基础
下面这些类是Android framework中用来播放声音和视频的:
MediaPlayer:该类是播放声音和视频的主要API
AudioManager:该类管理一个设备上的音频数据和音频输出
清单声明
在使用MediaPlayer开始开发你的应用之前,确保你的清单文件中有适当的声明(权限声明),让你允许使用一些关联的特性。
- 网络权限-如果你正在使用MediaPlayer处理网络内容,你的应用程序必须需要网络接入。
<uses-permission android:name="android.permission.INTERNET" />
- Wake Lock 权限-如果你的播放程序需要保持屏幕常亮或使设备不进入睡眠,或者调用了 MediaPlayer.setScreenOnWhilePlaying() 、MediaPlayer.setWakeMode() 方法,你就必须请求这个权限。
<uses-permission android:name="android.permission.WAKE_LOCK" />
使用MediaPlayer
- 本地资源
- 内部URI资源, 例如通过某个Content Resolver获取的URI
- 外部URL资源(流)
MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.sound_file_1);
mediaPlayer.start(); // no need to call prepare(); create() does that for you
这个例子中,一个原始资源是一个系统不会以任何特定方式去解析的文件。但是,该资源的内容不应该是原始音频。它应该是一个被正确编码且以Android支持的格式封装成的媒体文件。
Uri myUri = ....; // initialize Uri here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(getApplicationContext(), myUri);
mediaPlayer.prepare();
mediaPlayer.start();
String url = "http://........"; // your URL here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(url);
mediaPlayer.prepare(); // might take long! (for buffering, etc)
mediaPlayer.start();
提醒:如果你是靠传入一个URL地址去引入在线的媒体文件,你必须保证它是可以被渐进载入的。
异步Preparation
管理状态
释放MediaPlayer
mediaPlayer.release();
mediaPlayer = null;
举个例子,考虑这样一种可能发生的问题:当你的activity被停止时,你没有释放当前的MediaPlayer;但是在activity再次启动时,你又创建了一个新MediaPlayer。正如你所知道的,当用户改变了屏幕方向(或者以其他的方式改变了设备配置),系统会通过重启activity来处理这种情况(默认方式);所以,当用户频繁这样做时,我们也许会很快地耗尽所有的系统资源,因为每次方向改变时,我们都创建了一个新的你从没释放的MediaPlayer。
在Service中使用MediaPlayer
异步运行
public class MyService extends Service implements MediaPlayer.OnPreparedListener {
private static final String ACTION_PLAY = "com.example.action.PLAY";
MediaPlayer mMediaPlayer = null;
public int onStartCommand(Intent intent, int flags, int startId) {
...
if (intent.getAction().equals(ACTION_PLAY)) {
mMediaPlayer = ... // initialize it here
mMediaPlayer.setOnPreparedListener(this);
mMediaPlayer.prepareAsync(); // prepare async to not block main thread
}
}
/** Called when MediaPlayer is ready */
public void onPrepared(MediaPlayer player) {
player.start();
}
}
处理异步错误
public class MyService extends Service implements MediaPlayer.OnErrorListener {
MediaPlayer mMediaPlayer;
public void initMediaPlayer() {
// ...initialize the MediaPlayer here...
mMediaPlayer.setOnErrorListener(this);
}
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
// ... react appropriately ...
// The MediaPlayer has moved to the Error state, must be reset!
}
}
我们要记住,当一个错误发生时,MediaPlayer会移动到Error状态,这是很重要的;再次使用它之前,我们必须重置这个对象。
使用唤醒锁
mMediaPlayer = new MediaPlayer();
// ... other initialization here ...
mMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
然而,这个示例中获取到的锁只能保证CPU保持唤醒。如果你正在通过网络且使用WiFi载入一个网络媒体,你也许还需要持有一个WiFi Lock,你必须手动请求和释放它。所以,当你开始通过一个远程URL准备MediaPlayer时,你应该创建并请求WiFi Lock。例如:
WifiLock wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))
.createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");
wifiLock.acquire();
当你暂停或停止媒体播放,或者你不再需要使用网络时,你应该释放这个锁:
wifiLock.release();
作为前台服务运行
String songName;
// assign the song name to 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 |= Notification.FLAG_ONGOING_EVENT;
notification.setLatestEventInfo(getApplicationContext(), "MusicPlayerSample",
"Playing: " + songName, pi);
startForeground(NOTIFICATION_ID, notification);
当你的服务运行在前台时,你配置的notification在设备的通知区域是可见的。如果用户选择了这个notification,系统就会请求你提供的PendingIntent。下面的例子中,它会打开一个Activity(MainActivity)。
stopForeground(true);
如果需要更多信息,可以看有关
Service和
Status Bar Notification的文档。
处理音频焦点
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.
}
requestAudioFcous()方法的第一个参数是一个AudioManager.OnAudioFocusChangeListener回调对象,任何时刻如果音频焦点发生变化,它的onAudioFocusChange()方法就会被调用。因此,你应该在你的service和Activity中实现这个接口。例如:
class MyService extends Service
implements AudioManager.OnAudioFocusChangeListener {
// ....
public void onAudioFocusChange(int focusChange) {
// Do something based on focus change...
}
}
参数focusChange会告诉你音频焦点发生了怎样的改变,它可以是下面值中的某一个:
- AUDIOFOCUS_GAIN:你获取到了音频焦点。
- AUDIOFOCUS_LOSS:长时间内,你失去了音频焦点。你必须停止所有音频播放。因为长时间内你都可能不会有音频焦点,这将会是一个你尽可能清理资源的好地方。例如,这时你应该释放MediaPlayer。
- AUDIO_LOSS_TRANSIENT:你只是暂时失去了音频焦点,在很短时间内你会重新获取到它。你必须停止所有音频播放,但是你可以保持你的资源,因为短时间内你可能将会重新得到音频焦点。
- AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:你暂时失去了音频焦点,但允许你继续安静地播放音频(低音量),而不是彻底关掉。
public void onAudioFocusChange(int focusChange) {
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN:
// resume playback
if (mMediaPlayer == null) initMediaPlayer();
else if (!mMediaPlayer.isPlaying()) mMediaPlayer.start();
mMediaPlayer.setVolume(1.0f, 1.0f);
break;
case AudioManager.AUDIOFOCUS_LOSS:
// Lost focus for an unbounded amount of time: stop playback and release media player
if (mMediaPlayer.isPlaying()) mMediaPlayer.stop();
mMediaPlayer.release();
mMediaPlayer = null;
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
// Lost focus for a short time, but we have to stop
// playback. We don't release the media player because playback
// is likely to resume
if (mMediaPlayer.isPlaying()) mMediaPlayer.pause();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
// Lost focus for a short time, but it's ok to keep playing
// at an attenuated level
if (mMediaPlayer.isPlaying()) mMediaPlayer.setVolume(0.1f, 0.1f);
break;
}
}
请记住,音频焦点的API只在API 8(Android 2.2)及更高版本中提供。所以如果你想支持原先老版本的Android,如果有的话,你应该采用那些允许你使用这项特性的向后兼容的策略;否则,那就只能让步。
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
}
}
只有当检测到系统运行在API 8及以上版本时,你才可以创建一个AudioFocusHelper类的实例。例如:
if (android.os.Build.VERSION.SDK_INT >= 8) {
mAudioFocusHelper = new AudioFocusHelper(getApplicationContext(), this);
} else {
mAudioFocusHelper = null;
}
执行清理操作
public class MyService extends Service {
MediaPlayer mMediaPlayer;
// ...
@Override
public void onDestroy() {
if (mMediaPlayer != null) mMediaPlayer.release();
}
}
除了在服务在被停止时进行释放,你应该时常寻找其他的时机去释放你的MediaPlayer。例如,如果你在一段较长的时间内都不会播放媒体资源(例如,在失去了音频焦点之后),你就应该明确地释放存在的MediaPlayer,并在之后再次创建它。另一方面,如果你只是在非常短的时间内停止播放,你就大概可以继续持有你的MediaPlayer,以避免再次创建和准备它的系统开销。
处理AUDIO_BECOMING_NOISY Intent
<receiver android:name=".MusicIntentReceiver">
<intent-filter>
<action android:name="android.media.AUDIO_BECOMING_NOISY" />
</intent-filter>
</receiver>
这里为该Intent注册了MusicIntentReceiver类作为广播接收器。并且你应该实现这个类:
public class MusicIntentReceiver extends android.content.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)
}
}
}
从Content Resolver获取媒体
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中使用它,你可以这样做:
long id = /* retrieve it from somewhere */;
Uri contentUri = ContentUris.withAppendedId(
android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id);
mMediaPlayer = new MediaPlayer();
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mMediaPlayer.setDataSource(getApplicationContext(), contentUri);
// ...prepare and start...