Media Playback//媒体播放
The Android multimedia framework includes support for playing variety of common media types, so that you can easily integrate audio, video andimages into your applications. You can play audio or video from media files stored in your application's resources (raw resources), from
standalone files in the filesystem, or from a data stream arriving over a network connection, all using MediaPlayer APIs.
//安卓的多媒体框架包括支持播放各种常见的媒体类型,所以你可以很容易地整合音频,视频和图片在你的应用里面。你可以播放音频或者视频从储藏在你的应用的资源(raw resources)里的媒体文件,来自与独立文件系统中的文件,或者来自于通过网络连接获取的数据流,都使用MediaPlayer APIS.
This document shows you how to write a media-playing application that interacts with the user and the system in order to obtain good performance and a pleasant user experience.
//这个文档将向你展示如何写出一个媒体播放的应用,这个应用在用户和系统之间进行交互以获得好的性能和令人愉悦的用户体验。
Note: You can play back the audio data only to the standard output device. Currently, that is the mobile device speaker or a Bluetooth headset.
You cannot play sound files in the conversation audio during a call.
//注意:你只可以播放音频数据到标准的输出设备上。目前,这些设备是移动设备的扬声器或者一个蓝牙耳机。你不能在电话的谈话期间播放一个声音文件。
The Basics//基础知识
The following classes are used to play sound and video in the Android framework://下面的这些类在安卓框架里面被用来播放声音和视频:
1)MediaPlayer
This class is the primary API for playing sound and video.
//这个类是主要的,基本的播放声音和视频的API.
2)AudioManager
This class manages audio sources and audio output on a device.
//这个类管理音频源和视频输出到一个设备上。
Manifest Declarations//清单声明
Before starting development on your application using MediaPlayer, make sure your manifest has the appropriate declarations to allow use of related features.//在你的应用上使用MediaPlayer开发之前,确定你已经在清单文件里面为允许使用相关的特性做出了适当的声明。
Internet Permission - If you are using MediaPlayer to stream network-based content, your application must request network access.
//网络许可---如果你基于网络内容的流使用MediaPlayer,你的应用必须要求访问网络。
<uses-permission android:name="android.permission.INTERNET" />
Wake Lock Permission- If your player application needs to keep the screen from dimming or the processor from sleeping, or uses the MediaPlayer.setScreenOnWhilePlaying() or MediaPlayer.setWakeMode() methods, you must request this permission.
//锁屏开关的权限---如果你的应用需要禁止屏幕变暗或者处理器休眠,或者需要使用MediaPlayer.setScreenOnWhilePlaying()或者MediaPlayer.setWakeMode()方法,你必须请求这个权限。
<uses-permission android:name="android.permission.WAKE_LOCK" />
Using MediaPlayer//使用MediaPlayer
One of the most important components of the media framework is the MediaPlayer class. An object of this class can fetch, decode, and play both audio and video with minimal setup. It supports several different media sources such as://多媒体框架的一个最重要的组件是MediaPlayer类。这个类的一个对象可以通过最小的设置取得,解码,并且播放音频和视频.它支持几种不同的多媒体源,例如:
1)Local resources//本地资源
2)Internal URIs, such as one you might obtain from a Content Resolver//网络URIs,例如你可以从Content Resolver里面获得一个
3)External URLs (streaming)//外部的URLs(流)
For a list of media formats that Android supports, see the Android Supported Media Formats document.
//android支持的多媒体格式,参见Android Supported Media Formats文档。
Here is an example of how to play audio that's available as a local raw resource (saved in your application's res/raw/ directory):
//这里是一个例子,这个例子是如何播放本地可用的raw资源(保存在你应用的res/raw/ 目录下)的音频:
MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.sound_file_1);
mediaPlayer.start(); // no need to call prepare(); create() does that for you//不需要调用prepare(),create()已经为你调用了
In this case, a "raw" resource is a file that the system does not try to parse in any particular way. However, the content of this resource should not be raw audio. It should be a properly encoded and formatted media file in one of the supported formats.
//在这种情况下,一个"raw" 资源是一个文件,系统不会以任何特别的方式去解析的文件。但是这个资源的内容不能是一个未加工的音频。它应当被恰当地编码并且是一种被支持的媒体格式。
And here is how you might play from a URI available locally in the system (that you obtained through a Content Resolver, for instance):
//这里是如何让播放存在系统的本地可用的URI(例如,你从一个Content Resolver里面得到)
Uri myUri = ....; // initialize Uri here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(getApplicationContext(), myUri);
mediaPlayer.prepare();
mediaPlayer.start();
Playing from a remote URL via HTTP streaming looks like this:
//通过一个HTTP 流从一个远程的URL播放像这样:
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();
Note: If you're passing a URL to stream an online media file, the file must be capable of progressive download.
//注意:如果你是通过URL流播放一个在线的媒体文件,这个文件必须有渐进下载的能力。
Caution: You must either catch or pass IllegalArgumentException and IOException when usingsetDataSource(), because the file you are referencing might not exist.
//警告:你必须捕捉或者终止IllegalArgumentException和IOException当使用setDataSource()时,因为你引用的这个文件可能不存在。
Asynchronous Preparation//异步准备
Using MediaPlayer
can be straightforward in principle.However, it's important to keep in mind that a few more things are necessary to integrate it correctly with a typical Android application. For example, the call to prepare()
can take a long time to execute, because it might involve fetching and decoding media data. So, as is the case with any method that may take long to execute, you should never call it from your application's UI thread. Doing that will cause the UI to hang until the method returns, which is a very bad user experience and can cause an ANR (Application Not Responding) error. Even if you expect your resource to load quickly, remember that anything that takes more than a tenth of a second to respond in the UI will cause a noticeable pause and will give the user the impression that your application is slow.
//使用MediaPlayer在原理上是简单的.但是,记住一些事情是非常重要的,这些事情在正确地整合到一个典型的安卓应用上是必须的.例如,调用prepare会花费很长的时间去执行,因为它可能涉及到提取和解码媒体数据。因此,对于任何可能花费很长时间执行的方法,你不应当在在你应用的主线程里面调用.那样做会导致UI停止直到方法返回,那是糟糕的用户体验并且会导致ANR(应用无响应)错误。即使你希望你的自的资源加载的快些,记住在你的UI里面的响应超过十分之一秒的都会给用户一个你的应用很慢的印象。
To avoid hanging your UI thread, spawn another thread to prepare the MediaPlayer
and notify the main thread when done. However, while you could write the threading logic yourself, this pattern is so common when using MediaPlayer
that the framework supplies a convenient way to accomplish this task by using the prepareAsync()
method. This method starts preparing the media in the background and returns immediately. When the media is done preparing, the onPrepared()
method of the MediaPlayer.OnPreparedListener
, configured through setOnPreparedListener()
is called.
//避免你的UI线程停止,生产其它的线程去准备MediaPlayer并且通知主线程什么时间做。然而,当你可以自己写线程的逻辑时,这种模式是很普遍的,使用媒体播放器时,该框架提供了一个方便的方法来完成这项任务通过使用prepareAsync()方法。这个方法在后台开始准备媒体并且立即返回。当媒体已经准备了,MediaPlayer.OnPreparedListener的onPrepared()方法,通过setOnPreparedListener()的配置被调用。
Managing State//状态管理
MediaPlayer
that you should keep in mind is that it's state-based. That is, the
MediaPlayer
has an internal state that you must always be aware of when writing your code, because certain operations are only valid when then player is in specific states. If you perform an operation while in the wrong state, the system may throw an exception or cause other undesireable behaviors.
MediaPlayer
class shows a complete state diagram, that clarifies which methods move the MediaPlayer
from one state to another. For example, when you create a new MediaPlayer
, it is in the Idle state. At that point, you should initialize it by calling setDataSource()
, bringing it to the Initialized state. After that, you have to prepare it using either the prepare()
or prepareAsync()
method. When the MediaPlayer
is done preparing, it will then enter the Prepared state, which means you can call start()
to make it play the media. At that point, as the diagram illustrates, you can move between the Started, Paused and PlaybackCompleted states by calling such methods as start()
, pause()
, and seekTo()
, amongst others. When you call stop()
, however, notice that you cannot call start()
again until you prepare the MediaPlayer
again.MediaPlayer
object, because calling its methods from the wrong state is a common cause of bugs.Releasing the MediaPlayer//释放MediaPlayer
MediaPlayer
can consume valuable system resources. Therefore, you should always take extra precautions to make sure you are not hanging on to a
MediaPlayer
instance longer than necessary. When you are done with it, you should always call
release()
to make sure any system resources allocated to it are properly released. For example, if you are using a
MediaPlayer
and your activity receives a call to
onStop()
, you must release the
MediaPlayer
, because it makes little sense to hold on to it while your activity is not interacting with the user (unless you are playing media in the background, which is discussed in the next section). When your activity is resumed or restarted, of course, you need to create a new
MediaPlayer
and prepare it again before resuming playback.
MediaPlayer
:mediaPlayer.release(); mediaPlayer = null;As an example, consider the problems that could happen if you forgot to release the
MediaPlayer
when your activity is stopped, but create a new one when the activity starts again. As you may know, when the user changes the screen orientation (or changes the device configuration in another way), the system handles that by restarting the activity (by default), so you might quickly consume all of the system resources as the user rotates the device back and forth between portrait and landscape, because at each orientation change, you create a new
MediaPlayer
that you never release. (For more information about runtime restarts, see
Handling Runtime Changes
.)
MediaPlayer
controlled by a
Service
, as discussed in
Using a Service with MediaPlayer
.
Using a Service with MediaPlayer//和MediaPlayer在一起使用一个
服务
Service
and control the
MediaPlayer
instance from there. You should be careful about this setup, because the user and the system have expectations about how an application running a background service should interact with the rest of the system. If your application does not fulfil those expectations, the user may have a bad experience. This section describes the main issues that you should be aware of and offers suggestions about how to approach them.
Running asynchronously//异步的运行
Activity
, all work in a
Service
is done in a single thread by default—in fact, if you're running an activity and a service from the same application, they use the same thread (the "main thread") by default. Therefore, services need to process incoming intents quickly and never perform lengthy computations when responding to them. If any heavy work or blocking calls are expected, you must do those tasks asynchronously: either from another thread you implement yourself, or using the framework's many facilities for asynchronous processing.
MediaPlayer
from your main thread, you should call
prepareAsync()
rather than
prepare()
, and implement a
MediaPlayer.OnPreparedListener
in order to be notified when the preparation is complete and you can start playing. For example:
public class MyService extends Service implements MediaPlayer.OnPreparedListener { private static final 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(); } }
Handling asynchronous errors//处理异步的错误
MediaPlayer
, you can accomplish this by implementing a
MediaPlayer.OnErrorListener
and setting it in your
MediaPlayer
instance:
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! } }It's important to remember that when an error occurs, the
MediaPlayer
moves to the
Error
state (see the documentation for the
MediaPlayer
class for the full state diagram) and you must reset it before you can use it again.
Using wake locks//使用唤醒锁
MediaPlayer
is playing, call the setWakeMode()
method when initializing your MediaPlayer
. Once you do, the MediaPlayer
holds the specified lock while playing and releases the lock when paused or stopped:mMediaPlayer = new MediaPlayer(); // ... other initialization here ... mMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);However, the wake lock acquired in this example guarantees only that the CPU remains awake. If you are streaming media over the network and you are using Wi-Fi, you probably want to hold a
WifiLock
as well, which you must acquire and release manually. So, when you start preparing the
MediaPlayer
with the remote URL, you should create and acquire the Wi-Fi lock. For example:
WifiLock wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE)) .createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock"); wifiLock.acquire();When you pause or stop your media, or when you no longer need the network, you should release the lock:
wifiLock.release();
Running as a foreground service//作为前台服务运行
Notification
for the status bar and call startForeground()
from the Service
. For example: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);While your service is running in the foreground, the notification you configured is visible in the notification area of the device. If the user selects the notification, the system invokes the
PendingIntent
you supplied. In the example above, it opens an activity (
MainActivity
).
stopForeground()
:stopForeground(true);For more information, see the documentation about Services and Status Bar Notifications .
Handling audio focus//处理音频焦点
requestAudioFocus()
from the AudioManager
, as the example below demonstrates: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. }The first parameter to
requestAudioFocus()
is an
AudioManager.OnAudioFocusChangeListener
, whose
onAudioFocusChange()
method is called whenever there is a change in audio focus. Therefore, you should also implement this interface on your service and activities. For example:
class MyService extends Service implements AudioManager.OnAudioFocusChangeListener { // .... public void onAudioFocusChange(int focusChange) { // Do something based on focus change... } }The
focusChange
parameter tells you how the audio focus has changed, and can be one of the following values (they are all constants defined in
AudioManager
):
AUDIOFOCUS_GAIN
: You have gained the audio focus.//你已经得到了音频的焦点
AUDIOFOCUS_LOSS
: You have lost the audio focus for a presumably long time. You must stop all audio playback. Because you should expect not to have focus back for a long time, this would be a good place to clean up your resources as much as possible. For example, you should release the MediaPlayer
.
AUDIOFOCUS_LOSS_TRANSIENT
:You have temporarily lost audio focus, but should receive it back shortly. You must stop all audio playback, but you can keep your resources because you will probably get focus back shortly. transient:短暂的
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; } }Keep in mind that the audio focus APIs are available only with API level 8 (Android 2.2) and above, so if you want to support previous versions of Android, you should adopt a backward compatibility strategy that allows you to use this feature if available, and fall back seamlessly if not.
AudioFocusHelper
). Here is an example of such a class: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 } }You can create an instance of
AudioFocusHelper
class only if you detect that the system is running API level 8 or above. For example:
if (android.os.Build.VERSION.SDK_INT >= 8) { mAudioFocusHelper = new AudioFocusHelper(getApplicationContext(), this); } else { mAudioFocusHelper = null; }
Performing cleanup//执行清理
MediaPlayer
object can consume a significant amount of system resources, so you should keep it only for as long as you need and call
release()
when you are done with it. It's important to call this cleanup method explicitly rather than rely on system garbage collection because it might take some time before the garbage collector reclaims the
MediaPlayer
, as it's only sensitive to memory needs and not to shortage of other media-related resources. So, in the case when you're using a service, you should always override the
onDestroy()
method to make sure you are releasing the
MediaPlayer
: