前言
本章内容为Android开发者指南的 Framework Topics/Multimedia and Camera/Media Playback章节,译为"媒体播放",版本为Android 4.0 r1,翻译来自:"呆呆大虾",欢迎访问他的微博:"http://weibo.com/popapa",再次感谢"呆呆大虾" !期待你一起参与翻译Android的相关资料,联系我over140@gmail.com。
媒体播放
译者署名: 呆呆大虾
版本:Android 4.0 r1
原文
http://developer.android.com/guide/topics/media/mediaplayer.html
在本文中
Audio Focus的处理
关键类
参阅
Android的多媒体框架支持多种通用媒体的播放,因此能够很容易地在程序中集成音频、视频和图片信息。利用MediaPlayer API,可以播放多种来源的音视频数据,包括存储于程序资源(裸资源)中的媒体文件、文件系统中的独立文件、通过网络连接读取的数据流。
本文演示了如何编写一个媒体播放程序。为了兼顾良好的性能和舒适的用户体验,它还实现了播放期间用户和系统之间的交互。
注意: 只能在标准的输出设备上播放音频数据,目前即为移动设备的扬声器或蓝牙耳机。并且不能在通话期间同时播放音频文件。
简介
下列类用于在Android框架中播放音视频:
本类是播放音视频的主要API。
本类管理音频源和设备的音频输出。
Manifest声明
在开始开发MediaPlayer的应用程序之前,请确保manifest已经正确地声明了以下相关feature:
· Internet Permission —— 如果正在用MediaPlayer来播放基于网络的流媒体,应用程序必须请求网络访问权限。
<uses-permission android:name="android.permission.INTERNET" />
· Wake Lock Permission —— 如果应用程序需要防止屏幕变暗或处理器休眠,或是用到了MediaPlayer.setScreenOnWhilePlaying()、MediaPlayer.setWakeMode()方法,则必须请求本权限。
<uses-permission android:name="android.permission.WAKE_LOCK" />
媒体播放器的使用
媒体框架中最重要的组件之一就是MediaPlayer类。经过一些很少量的设置,此对象即能够读取、解码并播放音视频内容。它能支持如下多种不同的媒体来源:
· 本地资源
· 内部URI,比如可能来自Content Resolver
· 外部URL(流)
关于Android支持的媒体格式,请参阅文档Android支持的媒体格式。
下面的例子展示了如何播放本地以裸资源方式提供的音频(保存于程序的res/raw/目录下):
MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.sound_file_1);
mediaPlayer.start(); // 不必调用prepare(); create()会自动调用
这里的“裸”资源是指系统不会以任何特定方式进行解析的文件。当然,这个资源的内容不应该是原始音频数据,而应是用所支持的格式正确编码并格式化过的媒体文件。
下面是如何播放来自系统本地提供的URI资源(比如通过Content Resolver获取的):
Uri myUri = ....; // 在此初始化Uri
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(getApplicationContext(), myUri);
mediaPlayer.prepare();
mediaPlayer.start();
下例是播放来自远程URL的HTTP流:
String url = "http://........"; // 在此指定URL
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(url);
mediaPlayer.prepare(); // 可能会耗时很长! (需创建缓存等)mediaPlayer.start();
注意:如果通过URL来传送一个来自在线媒体文件的数据流,则该文件必须支持渐进下载(progressive download)。
警告:因为所引用的文件有可能会不存在,所以使用setDataSource()时必须捕捉并且传递IllegalArgumentException和IOException异常。
原则上说,MediaPlayer的使用可以非常简单。不过有一点很重要,请记住还有一些工作必须正确地加入到典型的Android程序中去。比如,因为要读取并解码媒体数据,prepare()的调用可能会运行很长时间。因此在执行这种耗时很长的方法时,应该永远避免从程序的UI线程中调用此类方法。那会导致在方法返回之前用户界面UI都处于挂起状态,这样用户体验会十分糟糕,并可能会引发ANR(程序没有响应)错误。即使预计到资源会迅速装载完毕,也请记住在用户界面上任何响应时间超过1/10秒的工作都会导致很明显的停顿,并且会给用户留下一个程序很慢的印象。
为了避免用户界面UI线程的挂起,请启动另一个线程来准备MediaPlayer并在完成后通知主线程。不过这就可能要自行编写线程逻辑,这也是使用MediaPlayer时的通常做法,利用其prepareAsync()方法,框架提供一种便利的途径来完成此类任务。这个方法在后台进行媒体的准备工作,并且立即返回。媒体准备完毕后,将会调用MediaPlayer.OnPreparedListener的onPrepared()方法,该Listener通过setOnPreparedListener()指定。
MediaPlayer另一个应该被关注的要点是其状态模型。也就是说,MediaPlayer拥有一个内部状态,在编写代码时必须时刻注意这个内部状态,因为播放器在某个给定状态下只允许进行特定的操作。如果在错误的状态下执行操作,系统可能会抛出异常或导致其它不可预知的现象发生。
MediaPlayer类的文档中已展示了完整的状态图,上面标明了哪些方法会使MediaPlayer转换状态。比如,新的MediaPlayer被创建时,处于Idle状态。这时,应通过调用setDataSource()进行初始化,进入Initialized状态。然后必须用prepare()或prepareAsync()进行准备工作。待到MediaPlayer准备完毕后,将会进入Prepared状态,这就意味着可以调用start()来播放媒体了。如状态图所示,这时可以调用start()、pause()和seekTo()在Started、Paused和PlaybackCompleted状态之间进行切换。不过请注意,一旦调用了stop(),在下次MediaPlayer准备好之前就不能再次调用start()了。
在编写有关MediaPlayer对象的代码时请时刻牢记状态图,因为常见的bug原因就是在错误的状态下调用了不合适的方法。
释放MediaPlayer
MediaPlayer可能会消耗较多的系统资源。因此应该时刻注意,避免不必要时还维持MediaPlayer实例的运行。应该总是在用完后及时调用release(),以确保所申请的系统资源得到有效释放。比如,正在使用MediaPlayer时activity收到了一个onStop()调用,这时就必须释放MediaPlayer,因为activity不与用户交互时没必要再保持播放器的运行(除非在后台播放媒体,这会在下节讨论)。当然,如果activity再次被激活或者再次被启动,则需要创建一个新的MediaPlayer并再次准备之后才能恢复播放。
下面是释放并注销MediaPlayer的语句:
mediaPlayer.release();
mediaPlayer = null;
举个例子,假如停止activity时忘记释放MediaPlayer了,但在activity再次启动时又创建了一个新的播放器,看看可能会产生的问题。众所周知,用户切换屏幕方向(或者其它方式改变设备设置)时,系统默认会重启activity,这样系统资源可能会由于用户在横向纵向间来回旋转而很快耗尽。因为每改变一次方向,就会创建一个永远不会释放的新MediaPlayer。(关于运行时的重启,详见运行时变化的处理。)
如果期望在用户离开activity后还能继续播放“后台媒体”,正如系统内置音乐播放器那样,则需要通过Service来控制MediaPlayer,这在使用带MediaPlayer的服务中讨论。
使用带MediaPlayer的服务
如果程序需要在不显示时还能在后台播放媒体——也就是说期望在用户操作其它程序时也能继续播放——那就必须启动一个Service并从服务中控制MediaPlayer实例。这种情况下应该十分小心,因为用户和系统都期望运行后台服务的应用应该能与系统其它功能同时运行。如果应用不能满足这个要求,用户体验将会很糟糕。本节描述了应注意的主要问题,并提供解决建议。
首先,如同Activity,所有Service默认运行在单个线程中——事实上,如果从同一个应用程序运行activity和服务,它们默认会使用同一个进程(“主进程”)。因此,服务就需要快速处理传入的意图,并且响应这些意图时还不能执行耗时较长的计算工作。如果需要执行繁重的工作或者阻塞调用,必须以异步方式执行这类任务:创建另一个线程来执行,或利用框架提供的异步处理功能。
比如,假设在主线程中用到MediaPlayer,就应该用prepareAsync()来代替prepare(),并实现MediaPlayer.OnPreparedListener以便在准备完毕后得到通知,然后就可以开始播放了。示例如下:
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 = ... // 在此初始化
mMediaPlayer.setOnPreparedListener(this);
mMediaPlayer.prepareAsync();
// 为了不阻塞主线程而异步准备
}
}
/** 由MediaPlayer准备完毕后调用 */
public void onPrepared(MediaPlayer player) -->
- 该日志由 awfully 于12年前发表在综合分类下,最后更新于 2012年07月12日.
- 转载请注明: Android开发指南(34) —— Multimedia and Camera – Media Playback | 学步园 +复制链接