Android 多媒体框架支持播放各种常见媒体类型,以便您轻松地将音频、视频和图片集成到应用中。您可以使用 MediaPlayer API,播放存储在应用资源(原始资源)内的媒体文件、文件系统中的独立文件或者通过网络连接获得的数据流中的音频或视频。
本文档向您介绍了如何编写与用户和系统互动的媒体播放应用,以实现良好的性能和用户体验。
以下类用于在 Android 框架中播放声音和视频:
此类是用于播放声音和视频的主要 API。
此类用于管理设备上的音频源和音频输出。
在开始使用 MediaPlayer 开发应用之前,请确保您的清单具有适当的声明,这样才能使用相关功能。
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
MediaPlayer 类是媒体框架最重要的组成部分之一。此类的对象能够获取、解码以及播放音频和视频,而且只需极少量设置。它支持多种不同的媒体源,例如:
本地资源
内部 URI,例如您可能从内容解析器那获取的 URI
外部网址(流式传输)
以下示例展示了如何播放作为本地原始资源(保存在应用的 res/raw/ 目录中)提供的音频:
MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.sound_file_1);
mediaPlayer.start(); // no need to call prepare(); create() does that for you
在本例中,“原始”资源是指系统不会尝试以任何特定方式解析的文件。不过,该资源的内容不应为原始音频。它应该是采用某种支持的格式且经过适当编码和格式化的媒体文件。
播放系统中本地可用的 URI(例如,您可以通过内容解析器获取)中的内容方法如下:
Uri myUri = ....; // initialize Uri here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(getApplicationContext(), myUri);
mediaPlayer.prepare();
mediaPlayer.start();
通过 HTTP 流式传输并播放远程网址上的内容如下所示:
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();
原则上,使用 MediaPlayer 会非常简单。不过,请务必注意,要正确地将其与典型的 Android 应用集成,还需执行一些额外操作。例如,对 prepare() 的调用可能需要很长时间来执行,因为它可能涉及获取和解码媒体数据。因此,与任何可能需要很长时间来执行的方法一样,切勿从应用的界面线程中调用它。这样做会导致界面挂起,直到系统返回该方法,这是一种非常糟糕的用户体验,并且可能会导致 ANR(应用无响应)错误。即使您预计资源会快速加载,但也请记得,界面中任何响应时间超过十分之一秒的操作都会导致明显的暂停,并让用户觉得您的应用运行缓慢。
为避免界面线程挂起,请生成其他线程来准备 MediaPlayer,并在准备工作完成后通知主线程。不过,尽管您可以自行编写线程逻辑,但此模式在使用 MediaPlayer 时非常普遍,因此框架通过 prepareAsync() 方法提供了一种完成此任务的便捷方式。此方法会在后台开始准备媒体,并立即返回。当媒体准备就绪后,系统会调用通过 setOnPreparedListener() 配置的 MediaPlayer.OnPreparedListener 的 onPrepared() 方法。
您还应该记住,MediaPlayer 以状态为基础。也就是说,MediaPlayer 具有内部状态,您在编写代码时必须始终注意,某些操作仅在播放器处于特定状态时才有效。如果您在错误的状态下执行某项操作,则系统可能会抛出异常或导致其他不良行为。
MediaPlayer 类的参考文档显示了一个完整的状态图,该图说明了哪些方法可将 从一种状态变为另一种状态。例如,当您创建新的 MediaPlayer 时,它处于“Idle”状态。此时,您应该通过调用 setDataSource() 初始化该类,使其处于“Initialized”状态。然后,您必须使用 prepare() 或 prepareAsync() 方法完成准备工作。当 MediaPlayer 准备就绪后,它便会进入“Prepared”状态,这也意味着您可以通过调用 start() 使其播放媒体内容。此时,如图所示,您可以通过调用 start()、pause() 和 seekTo() 等方法在“Started”、“Paused”和“PlaybackCompleted”状态之间切换。不过请注意,当您调用 stop() 时,除非您再次准备 MediaPlayer,否则将无法再次调用 start()。
在编写与 MediaPlayer 对象互动的代码时,请始终牢记该状态图,因为从错误的状态调用其方法是导致错误的常见原因。
MediaPlayer 会占用宝贵的系统资源。因此,您应该始终采取额外的预防措施,确保 MediaPlayer 实例保留的时间不会过长。完成该操作后,您应始终调用 release() 以确保分配给它的所有系统资源均已正确释放。例如,如果您使用 MediaPlayer,并且您的 Activity 接收到对 onStop() 的调用,则您必须释放该 MediaPlayer,因为当 Activity 未与用户互动时,保留该 MediaPlayer 并没有什么意义(除非您在后台播放媒体内容,这将在下一部分中介绍)。当然,当 Activity 恢复或重启时,您需要先创建一个新的 MediaPlayer 并再次完成准备工作,然后才能恢复播放。
以下代码段介绍了如何释放并取消 MediaPlayer:
mediaPlayer.release();
mediaPlayer = null;
例如,思考一下:如果您忘记在 Activity 停止时释放 MediaPlayer,但是在 Activity 重新启动时新建一个 MediaPlayer,则可能会出现哪些问题。如您所知,当用户更改屏幕方向(或以其他方式更改设备配置)时,系统会重启 Activity(默认情况下),因此当用户在纵向和横向之间来回旋转设备时,您可能会很快消耗掉所有的系统资源,因为每当方向更改时,您都会创建一个永远不会释放的新 MediaPlayer。(如需详细了解运行时重启,请参阅处理运行时更改。)
您可能想知道:当用户离开您的 Activity 后仍继续播放“后台媒体”(这与内置音乐应用的行为十分相似)会发生什么。在这种情况下,您需要的是由 Service 控制的 MediaPlayer,如下一部分中所述
在surfaceView中播放自媒体。
private Callback mSurfaceCallback = new Callback(){
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
Log.i(TAG, "surfaceCreated...");
mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
Uri uri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + getPackageName() + "/" + R.raw.video);
try {
mediaPlayer.setDataSource(MainActivity.this, uri);
} catch (IOException e) {
ToastUtil.showToast(MainActivity.this, "播放失败");
e.printStackTrace();
}
mediaPlayer.setDisplay(holder);
mediaPlayer.prepareAsync();
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
mediaPlayer.start();
}
});
}
@Override
public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
Log.i(TAG, "surfaceDestroyed...");
if (mediaPlayer.isPlaying()) {
mediaPlayer.stop();
}
}
};