Android API Guide for Media and Camera(一)—— 媒体与相机(媒体播放器)

MediaPlayer

Android多媒体框架支持播放各种常见的媒体类型,所以你可以很简单地集成视频,音频和图片到应用程序中。你可以播放来自存储在应用程序(raw资源),文件系统或网络数据流的音频或视频媒体文件。这些都可以使用MediaPlayer APIs。

本文展示如何编写一个与用户交互的媒体播放应用程序,以及系统如何获取好的性能和用户体验。

Note:你只能回放音频数据到标准输出设备中。目前有移动设备扬声器或蓝牙耳机。不能在来电话通话期间播放音频文件。

基础

以下是Android框架播放声音和视频使用类:

MediaPlayer

播放声音和视频的主类。

AudioManager

设备上管理音频资源和音频输出的类。

清单文件声明

在开始使用MediaPlayer开发应用程序之前,确保你清单文件有适当的声明来使用相关的特性:

  • 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的使用

媒体框架最重要的组件之一就是MediaPlayer类。MediaPlayer可以以最少的设置获取,解码和播放音频跟视频。它支持几种不同的媒体资源,如:

  • 本地资源
  • 内部URIs,比如可能获取来自Content Resolver的URI
  • 外部URLs(流)

获取Android支持的媒体格式,参看Android Supported Media Formats文档。

这里演示如何播放一个有效的本地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

在这种情况中,系统不会用任何方式解析原生资源文件。这个资源的内容不应该是原音频。它应该被正确地编码和格式化为一种Android所支持的媒体文件格式。

这里演示如何播放来自系统本地有效的URI(比如通过Content Resolver获取的URI)

Uri myUri = ....; // initialize Uri here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(getApplicationContext(), myUri);
mediaPlayer.prepare();
mediaPlayer.start();

播放通过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:如果你传递一个URL将在线媒体文件转成流,这文件应该支持断点下载。

注意:当使用setDataSource()时需要捕获 IllegalArgumentException 和 IOException 异常,因为文件引用可能不存在。

异步准备

原则上是可以直截了当地使用MediaPlayer的。但是,有几件事要牢记,就是关于Android应用程序正确得集成MediaPlayer所需要做的事。比如,调用prepare()会占用很长的执行时间,因为它包含了获取以及解码媒体数据。所以,诸如任何这种可能占用长时间执行的方法,都不能在应用的UI线程中去调用。在主线程调用会导致在方法返回之前用户体验非常不好的UI阻塞,以及造成ANR(Application Not Responding)的错误。即使你想快速下载资源,也要记住任何超过10秒才响应UI的事都会引起明显的停顿,这让用户留下你的应用程序很慢的印象。

为了避免UI线程阻塞,开启另外一个线程准备MediaPlayer,并在准备结束后通知主线程。然而,当你自己可以使用MediaPlayer编写线程逻辑时,你会发现这个模板太常见了,因此Android框架提供了一个简便的方式通过使用prepareAsync()完成这个工作。这个方法在后台准备媒体并立即返回。当媒体准备好之后,会调用通过setOnPreparedListener() 配置的MediaPlayer.OnPreparedListener监听器中的onPrepared()方法。

管理状态

MediaPlayer另一个需要谨记的点就是它的基本状态。MediaPlayer有一个编写代码时总要意识的内部状态,因为某些操作只在播放器指定的状态才有效。如果你在一个错误的状态中实现一个操作,系统可能抛出异常或出现其他不可预期的表现。

MediaPlayer类的文档展示一个状态完成 图,它展示MediaPlayer从一个个状态变另一个状态。例如,当你创建一个新的MediaPlayer时,它在Idle状态。在那时候,你应该通过调用setDataSource()初始化,并进入Initialized 状态。在这之后,你得使用prepare()或prepareAsync()方法。当MediaPlayer结束准备阶段后,就进入 Prepared状态,这意味着你可以调用start()来播放媒体。正如完成状态图所示,在那时候,你可以通过调用如start(),pause(),seekTo()以及其它方法进入Started,Paused和PlaybackCompleted状态。当你调用stop()时,你会发现除非你的MediaPlayer再次准备,否则不能调用start()。

当需要用MediaPlayer对象编写交互代码时,牢记状态完成图。因为在不恰当的状态中调用它方法是一个常常出现的bugs。

释放MediaPlayer

MediaPlayer会消耗系统可用的资源。因此,你应该格外注意确保MediaPlayer实例没有占用额外的时间。当你留意到这点之后,你可以调用release()确保任何分配给MediaPlayer的系统资源能正确释放。例如,你正在使用一个MediaPlayer时,你的activity调用了onStop(),你必须释放MediaPlayer,因为当activity没有与用户交互时,持有这对象是没有意义的(除非你正在后台播放媒体,这情况将在下文讨论)。当你的activity调用了onResume()或onRestart()时,当然,你需要在重新回放之前创建一个新的MediaPlayer并再次prepare。

下面是如何释放并将MediaPlayer置为null的代码:

mediaPlayer.release();
mediaPlayer = null;

举个例子,考虑下出现这种问题的情况,假如当你的activity stop掉后,你忘记释放了MediaPlayer,但是在activity再次start后你又创建了一个新的MediaPlayer。 正如你所知,当用户改变屏幕方向(或通过另一种方式改变设备的配置),系统默认是通过restart activity处理的,所以你可能很快就消耗掉所有的系统资源了,如果用户前后水平和竖直不断旋转设备,因为每次方向的改变,你都会重新创建一个没有释放MediaPlayer。(获取更多关于运行时resstart的信息,参看Handling Runtime Changes

你可能想知道当用户离开activity之后,你想跟内置音乐应用程序一样继续播放背景媒体会发生什么事。这种情况,你所需要的是一个受Service控制的MediaPlayer对象,这内容在下文讨论。

在Service中使用MediaPlayer

如果你想在后台播放媒体,即使应用程序不在当前屏幕显示时。比如用户跟其他应用程序交互时,你想让它继续播放。因此你必须开启一个Service并通过它控制MediaPlayer实例。
你需要将MediaPlayer嵌入到一个 MediaBrowserServiceCompats服务中,并通过MediaBrowserCompat让它在另一个activity中交互。

你应该注意客户/服务的建立。关于播放器如何在一个与系统交互的后台服务中运行是值得期望的。如果你的应用程序没有满足这些期望,用户可能有一个不好的体验。阅读Building an Audio App 获取全部细节。

这部分对MediaPlayer在一个service实现并对MediaPlayer的管理做了特别的介绍。

异步运行

首先,Service所有的工作像Activity一样默认是在一个单独的线程完成。事实上,如果你在同个应用程序中运行一个activity和一个Service,它们默认是使用相同的线程的(主线程)。因此,服务需要快速处理传入的intents,不能处理耗时长的计算。如果你想做一些耗时跟阻塞的工作,你必须异步做这些工作:可以在另一个线程中实现,或使用框架的其他接口实现异步处理。

比如,当你在主线程中使用MediaPlayer,你应该调用prepareAsync()而不是prepare(),并实现MediaPlayer.onPreparedListener以便在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();
    }
}

处理异步错误

在同步操作中,errors通常会用异常或错误的代码来提示,但是,无论何时使用异步资源,你都应该确保应用程序有适当的错误提示。MediaPlaye针对这种情况,通过实现MediaPlayer.OnErrorListener监听器来完成这个提示工作:

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状态(参看MediaPlayer类的文档获取完整的状态图),你必须在再次使用它之前重置它。

使用唤醒锁

当设计在后台播放媒体的应用程序时,设备可能在service运行期间进入休眠状态。由于Android系统在设备进入休眠时会尝试着节约电池,所以系统会尝试停掉手机不需要的特性,包括CPU还有WiFi硬件。然而,如果你的service正在播放或流式传输音乐,你得防止系统对播放的干扰。

为了确保你的服务能持续运行在这种条件下,你不得不使用”唤醒锁”。唤醒锁是一种提示系统你的应用程序正在使用一些特性,即使手机进入空闲状态也需要保持活跃的方式。

Note:你应该谨慎地使用唤醒锁,并在真正需要的地方使用它们,因为唤醒锁会非常消耗设备的电量。

为了确保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();

执行清理

如前面所提及的,MediaPlayer对象会消耗大量的系统资源,所以你应该在需要的时候持有该对象,并在结束工作的时候release()资源。你应该执行这个清理方法而不是等系统gc,因为它可能在系统gc回收MediaPlayer之前占用很长的时间。因为它只对内存需求敏感,而不是缺少其他媒体相关资源。所以当你使用一个service时,你要重写onDestroy()方法确保释放MediaPlayer。

public class MyService extends Service {
   MediaPlayer mMediaPlayer;
   // ...

   @Override
   public void onDestroy() {
       if (mMediaPlayer != null) mMediaPlayer.release();
   }
}

除了MediaPlayer正在释放时,你应该寻找其他释放MediaPlayer的时机。例如,当你不在希望播放媒体例如,如果你希望长时间无法播放媒体(例如,在失去音频焦点之后),你应该肯定会释放现有的MediaPlayer并稍后重新创建。另一方面,如果你想停止回访一小会,你可能需要继续持有MediaPlayer对象避免再次创建和准备它的开销。

从ContentResolver获取媒体

媒体播放器应用另一个有用的特征就是可以获取用户设备上的音乐。你可以通过如下代码查询ContentResolver来获取:

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...

原文链接:https://developer.android.google.cn/guide/topics/media/mediaplayer.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值