Android的多媒体框架包括支持播放多种常见的媒体类型,使您可以轻松地把音频、视频和图像集成到你的应用。你可以播放音频或视频媒体文件,这些文件是存储在你的应用程序的资源文件中的。应用程序的资源文件可以是文件系统中独立的文件,或通过网络连接获取的一个数据流,所有使用MediaPlayer APIS的资源文件。
1.注意:
如果你通过一个URL来获取一个在线媒体文件,该文件必须能够支持渐进式下载。
2.警告:
当你使用setDataSource()方法时,必须捕捉或通过非法数据异常和输入输出异常,因为正在被你引用的文件可能不存在在.
3.异步准备 - Asynchronous Preparation
使用MediaPlayer 的原则很简单。然而,重要的是要记住,有必要将更多的一些东西正确地集成到一个典型的Android应用程序。比如,调用prepare()方法可能需要较长的时间来执行,因为它可能涉及获取和解码媒体数据。因此,如同任何方法,可能需要很长时间执行,你不应该从应用程序的UI线程调用它。这样做将导致UI挂到方法返回,这是一个非常糟糕的用户体验,也可能会引起应用程序没有响应的错误。 即使你预期你的资源能快速加载,记住,任何超过十分之一秒的反应在界面上会造成明显的停顿,将导致给用户的印象是:你的应用程序是缓慢的。
为了避免你的UI线程挂起,产生另一个线程准备MediaPlayer 当完成时通知主线程。你可以写自己的线程的逻辑,框架提供prepareAsync() 方法,方便的使用MediaPlayer 。该方法在后台开始准备媒体并立即返回。当媒体准备好了,MediaPlayer.OnPreparedListener的onPrepared 方法,通过配置setonpreparedlistener()方法来调用。
4.状态管理 - Managing State
关于MediaPlayer ,你需要记住的另一点是它的状态。即,在你编写自己的代码的时候,必须时刻意识到MediaPlayer 有一个内部状态,因为 只有当玩家在特定状态,某些特定的操作才会有效。如果您在错误的状态执行一个操作,系统可能会抛出一个异常或引起其它令人不快的行为。
MediaPlayer 类里有文件显示一个完整的状态转换图,阐明哪些方法可以把MediaPlayer 从一个状态改变到另一个状态。例如,当您创建一个新的MediaPlayer ,它就处于闲置状态。这时,你应该调用android.net.Uri) setDataSource()
方法来初始化它,把它设置为初始化状态。然后,你必须使用prepare()方法或prepareAsync()方法。当MediaPlayer 准备好了,它将进入准备状态,这就意味着你可以调用start()方法来播放媒体。另外,在状态转换图上阐明了,你可以调用start(),pause()和 seekTo()这些方法在Started,Paused和PlaybackCompleted状态之间进行转换。如果您调用stop()方法,这时请注意,你需要再次准备MediaPlayer ,才可以再一次调用start()方法。
在编写代码与MediaPlayer 对象交互时,心里要随时想着状态转换图,因为从错误的状态调用它的方法,是引起错误的常见原因。
释放MediaPlayer-Releasing the MediaPlayer
MediaPlayer 会消耗宝贵的系统资源。因此 ,你应该经常采取额外的预防措施来确保及时把不需要的MediaPlayer 取消掉。您需要调用release() 方法来确保系统分配给它的资源正确释放。例如,您正在使用MediaPlayer ,同时,你的活动调用onStop()方法,这时你必须释放MediaPlayer,因为你的活动并非与用户交互,留着它没什么意义(除非你是在后台播放多媒体,这是下一节中将讨论的内容)。当你的活动恢复或者重新启动,恢复播放之前,您需要创建一个新的MediaPlayer并且重新准备。
下面是释放和取消你的MediaPlayer的方法:
mediaPlayer.release();
mediaPlayer = null;
作为思考题,考虑一下如果当活动停止的时候你忘了释放MediaPlayer,活动重启后新建一个MediaPlayer,可能会发生的问题。正如你可能知道的,当用户更改屏幕的方向(或以另一种方式更改设备配置),该系统通过重启活动处理(通过默认方式),所以当用户频繁在纵向和横向之间切换时,你可能会很快消耗掉所有的系统资源,原因是你没有释放方向变化时各个方向上创建的新MediaPlayer。(更多关于运行时重启的资料,请查看Handling Runtime Changes)。
你可能会想知道在用户离开活动时后台继续播放媒体是如何实现的,采用同样的方式实现的,如内置的音乐应用程序的行为。在这种情况下,你需要通过一个Service来控制MediaPlayer,所以我们开始学习Using a Service with MediaPlayer。
5.使用服务控制MediaPlayer - Using a Service with MediaPlayer
如果你希望后台播放媒体,你希望用户操作其他应用时继续播放,你必须开始一个Service并且从那里控制MediaPlayer实例。你必须慎重考虑这个设置,因为用户与系统期望应用程序运行的后台服务应该与系统的其余部分相互作用。如果应用程序不满足这些预期,就不能有良好的用户体验。本节介绍的主要内容是:告诉你相关知识,并提供建议如何接触它们。
异步运行 - Running asynchronously
首先,如一个Activity,服务里的所有任务默认在单一线程中完成。如果你从同一个应用程序里运行一个Activity和一个Service,它们默认使用相同的线程(“主线程”)。因此,Service需要迅速处理传入的意图并且响应它们的时候从不执行冗长的计算。如果预计调用一些复杂的任务或阻塞,你必须异步处理这些任务:由另一个线程自己实现自己,或使用框架处理异步。
例如,当你从主要线程使用一个MediaPlayer ,你应该调用prepareAsync()方法而不是prepare() 方法,实现MediaPlayer.OnPreparedListener,以便当你准备工作完毕后,得到可以开始播放的通知。
import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.IBinder;
import android.support.annotation.Nullable;
/**
* Created by ylwang on 2016/5/13.
*/
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
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
public void onPrepared(MediaPlayer player) {
player.start();
}
}
处理异步错误 - Handling asynchronous errors
在同步操作中,错误通常会出现异常或错误代码信息。但当你使用异步资源时,您需要确保您的应用程序有错误提示,在MediaPlayer 中,要做到这一点,可以通过实现MediaPlayer.OnErrorListener,并且将它设置在你的MediaPlayer 实体中。
mport android.app.Service;
import android.media.MediaPlayer;
/**
* Created by ylwang on 2016/5/13.
*/
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!
return false;
}
}
请牢记,当出现错误,将这个MediaPlayer 设置为错误状态(请参考MediaPlayer 类文档的完整的状态关系图)您再次使用它之前,必须重置这个状态。
使用唤醒锁 - Using wake locks
应用程序在后台播放媒体,其服务在运行期间,设备可能会进入休眠状态。因为Android系统希望在设备休眠时节省电池。系统试图关闭手机的应用程序,是没有必要的,包括CPU和WiFi硬件。但是,如果你的服务正在运行或播放着音乐,你希望防止系统干扰你的回放。
为了确保您的服务在这些条件下能继续运行,你需要使用“wake locks”。唤醒锁是一种信号系统,它发出信号,显示:应用程序正在使用或可用的功能,或手机闲置。
注意:你应该尽量少用唤醒锁,只有在必要时候才使用它们。它们会使设备的电池寿命大大降低。
你MediaPlayer 正在播放时,需要确保CPU持续运行,当初始化你的MediaPlayer时,调用setWakeMode()方法。一旦你这样做了,当暂停或停止时候,MediaPlayer 持有指定的锁:
mMediaPlayer=new MediaPlayer();
// ... other initialization here ...
mMediaPlayer.setWakeMode(getApplicationContext(),
PowerManager.PARTIAL_WAKE_LOCK);
在这个例子中获得唤醒锁是指在保证CPU在唤醒状态。当你通过网络获取媒体和您正在使用WiFi时,你可能希望有个WifiLock,可以手动获取并释放。当你开始通过远程URL准备MediaPlayer,你应该创建并获得wi - fi锁。 代码如下:
WifiLock wifiLock=
((WifiManager)getSystemService(Context.WIFI_SERVICE)) .createWifiLock(WifiManager.WIFI_MODE_FULL,"mylock");
wifiLock.acquire();
当你暂停或停止你的媒体时,或当你不再需要这样的网络,你应该释放该锁: 代码如下:
wifiLock.release();
作为前景服务运行 - Running as a foreground service
服务通常用于执行后台任务,例如获取电子邮件,同步数据,下载内容,或其他。在这些情况下,用户不会意识到这个服务的执行,甚至可能不会注意到这些服务被打断,后来重新启动。毫无疑问,后台播放音乐是一个服务,用户能意识到,任何中断都会严重影响到用户体验。此外,用户可能会希望在这个服务执行期间作用于它。这种情况,服务应该运行一个“前景服务”。前台服务在系统中持有一个更高水平的重要性,系统几乎从未将服务扼杀,因为它对用户有着直接的重要性。当应用在前台运行,该服务还必须提供一个状态栏来通知用户意识有服务正在运行同时允许他们打开一个活动,可以与服务进行交互。
为了把你的服务变为前景服务,您必须为状态栏创建一个Notification,并且从Service调用startForeground()方法。
代码如下:
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);
通知区域可见的设备告诉你,服务在前台运行。如果用户选择了这个通知,系统将调用你提供的PendingIntent。在上面的例子中,它打开了一个Activity。(MainActivity)
实际执行一些用户能够意识到的服务时,你应该保留“foreground service”的状态。相反情况下,你应该调用stopForeground()方法来释放它。 代码如下:
stopForeground(true);
更多信息,请参考Service和 Status Bar Notifications的相关文档。
处理音频焦点 - Handling audio focus=
在给定的时间尽管只有一个活动可以运行,但Android是一个多任务环境。这对应用程序使用音频 造成了一个特别大的难度,由于只有一个音频输出,可能会有好几个媒体服务争夺使用它。Android 2.2之前,没有内置机制来解决这个问题,这可能在某些情况下导致糟糕的用户体验。例如,一个用户正在听音乐,同时,另一个应用程序有很重要的事需要通知用户,由于吵闹的音乐用户可能不会听到提示音。从Android 2.2开始,Android平台为应用程序提供了一个方式来协商设备的音频输出。这个机制被称为音频焦点。
当您的应用程序需要输出音频如音乐或一个通知,这时你就必须请求音频焦点。一旦得到焦点,它就可以自由的使用声音输出设备,同时它会不断监听焦点的更改。如果它被通知已经失去了音频焦点,它会要么立即杀死音频或立即降低到一个安静的水平(被称为“ducking”——有一个标记,指示哪一个是适当的)当它再次接收焦点时,继续不断播放。
音频焦点是自然的合作。应用程序都期望(强烈鼓励)遵守音频焦点指南,但规则并不是系统强制执行的。如果应用程序失去音频焦点后想要播放嘈杂的音乐,在系统中没有什么会阻止他。然而,这样可能会让用户有更糟糕的体验,并可能卸载这运行不当的应用程序。
请求音频焦点,您必须从AudioManager调用requestAudioFocus()方法,下面展示一个例子:
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.
}
requestAudioFocus()的第一个参数是AudioManager.OnAudioFocusChangeListener,每当音频焦点有变动的时候其onAudioFocusChange()方法被调用。您还应该在你的服务和活动上实现这个接口。
代码如下:
class MyService extends Service implements AudioManager.OnAudioFocusChangeListener {
// ....
public void onAudioFocusChange(int focusChange) { // Do something based on focus change...
}
}
}
focusChange 参数告诉你音频焦点是如何改变的,并且可以使用以下的值之一(他们都是在AudioManager中定义常量的):
- AUDIOFOCUS_GAIN: 你已经得到了音频焦点。
- AUDIOFOCUS_LOSS: 你已经失去了音频焦点很长时间了。
你必须停止所有的音频播放。因为你应该不希望长时间等待焦点返回,这将是你尽可能清除你的资源的一个好地方。例如,你应该释放MediaPlayer。
- AUDIOFOCUS_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
break;
}
}