媒体和相机:媒体回放

Android多媒体框架包括支持播放各种常见媒体类型,以便您可以轻松地将音频,视频和图像集成到应用程序中。 您可以使用存储在应用程序的资源(原始资源)的媒体文件,文件系统中的独立文件或通过网络连接到达的数据流,使用MediaPlayer API播放音频或视频。


本文档介绍如何编写与用户和系统交互的媒体播放应用程序,以获得良好的性能和愉快的用户体验。


注意:您只能将音频数据播放到标准输出设备。 目前,这是移动设备扬声器或蓝芽耳机。 在通话过程中,无法在通话音中播放声音文件。



一、基础

以下类用于在Android框架中播放声音和视频:

MediaPlayer

这个类是播放声音和视频的主要API。


AudioManager

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



二、清单声明

在开始使用MediaPlayer开发应用程序之前,请确保您的清单具有适当的声明以允许使用相关功能。

Internet Permission -如果您使用Media Player来流式传输基于网络的内容,则您的应用程序必须请求网络访问。

<uses-permission android:name="android.permission.INTERNET" />

Wake Lock Permission-如果您的播放器应用程序需要保持屏幕变暗或处理器不处于睡眠状态,或者使用MediaPlayer.setScreenOnWhilePlaying()或MediaPlayer.setWakeMode()方法,则必须请求此权限。

<uses-permission android:name="android.permission.WAKE_LOCK" />



三、使用MediaPlayer

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

1、本地资源

2、内部URI,例如您可以从内容解析器获取的内部URI。

3、外部网址(流媒体)


有关Android支持的媒体格式列表,请参阅“支持的媒体格式”页面。


以下是一个如何播放可用作本地原始资源(保存在应用程序的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来播放的(例如通过Content Resolver获得的)

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();

注意:如果您传递URL来流式传输在线媒体文件,该文件必须能够逐行下载。


注意:使用setDataSource()时,您必须捕获或传递IllegalArgumentException和IOException,因为您引用的文件可能不存在。



一)、异步准备

原则上使用MediaPlayer可以很简单。 但是,请务必记住,还需要更多的东西才能将它与典型的Android应用程序正确集成。 例如,对prepare()的调用可能需要很长时间才能执行,因为它可能涉及获取和解码媒体数据。 所以,与任何可能需要很长时间执行的方法一样,您不应该从应用程序的UI线程中调用它。 这样做会导致UI挂起,直到方法返回时,这是一个非常糟糕的用户体验,并可能导致ANR(应用程序无响应)错误。 即使您希望资源快速加载,请记住,在UI中需要十分之几秒钟以上的任何操作都会引起明显的暂停,并会给用户您的应用程序缓慢的印象。


为避免挂起您的UI线程,产生另一个线程来准备MediaPlayer,并在完成后通知主线程。 然而,虽然您可以自己编写线程逻辑,但是当使用MediaPlayer时,该模式非常常见,该框架通过使用prepareAsync()方法提供了一种方便的方式来完成此任务。 该方法开始在后台准备介质并立即返回。 当介质完成准备时,调用通过setOnPreparedListener()配置的MediaPlayer.OnPreparedListener的onPrepared()方法。



二)、管理状态

您应该记住的MediaPlayer的另一个方面是它是基于状态的。也就是说,MediaPlayer具有您编写代码时必须始终注意内部状态,因为某些操作仅在播放器处于特定状态时有效。如果在错误状态下执行操作,系统可能会引起异常或导致其他不良行为。


MediaPlayer类中的文档显示了一个完整的状态图,说明哪些方法将MediaPlayer从一个状态移动到另一个状态。 例如,当您创建一个新的MediaPlayer时,它处于空闲状态。 在这一点上,您应该通过调用setDataSource()来初始化它,使其处于Initialized状态。 之后,您必须使用prepare()或prepareAsync()方法进行准备。 当MediaPlayer完成准备后,它将进入“准备”状态,这意味着您可以调用start()来播放媒体。 在这一点上,如图所示,您可以通过调用start(),pause()和seekTo()等方法在“已启动”,“已暂停”和“回放完成”状态之间移动。 当您调用stop()时,请注意,在再次准备MediaPlayer之前,请再次调用start()。



在编写与MediaPlayer对象交互的代码时,始终保持状态图,因为从错误的状态调用其方法是错误的常见原因。



三)、释放MediaPlayer

MediaPlayer可以消耗宝贵的系统资源。因此,您应该总是采取额外的预防措施,以确保您挂在MediaPlayer上的实例不要长于必要的时间。完成后,您应该始终调用release(),以确保分配给它的任何系统资源都已正确释放。 例如,如果您正在使用MediaPlayer,并且您的活动接收到onStop()的调用,则必须释放MediaPlayer,因为在您的活动未与用户进行交互时,保持它不会有任何意义(除非您正在在后台播放媒体,下一节将讨论)。 当您的活动恢复或重新启动时,当然,您需要创建一个新的MediaPlayer并重新准备再次播放。


以下是您应该如何释放然后使您的MediaPlayer无效:

mediaPlayer.release();
mediaPlayer = null;

举个例子,考虑到这样一个例子-当你的活动停止,但是在活动再次启动时会创建一个性的活动,你却忘记释放MediaPlayer。您可能知道,当用户更改屏幕方向(或以其他方式更改设备配置)时,系统会通过重新启动活动(默认情况下)处理该事件,因此您可能会在用户在设备上的纵向和横向之间来回移动旋转时快速消耗所有系统资源,因为在每个方向更改时,您将创建一个您永远不会释放的新MediaPlayer。 (有关运行时重新启动的更多信息,请参阅处理运行时更改。)


您可能会想知道会发生什么如果您想继续播放“背景媒体”,即使用户离开您的活动,这与内置的音乐应用程序的行为大致相同。 在这种情况下,您需要的是由服务控制的MediaPlayer,如下一节所述



四、在服务中使用MediaPlayer

如果您希望媒体在背景中播放,即使您的应用程序不在屏幕上,也就是说,您希望在用户与其他应用程序交互时继续播放,那么您必须启动服务并从中控制MediaPlayer实例。 您需要将MediaPlayer嵌入到MediaBrowserServiceCompat服务中,并使其与另一个活动中的MediaBrowserCompat进行交互。


您应该小心这个客户端/服务器设置。对于在后台服务器中运行的播放器如何与系统的其余部分进行交互有期望。 如果您的应用程序不符合这些期望,用户可能会遇到糟糕的体验。 阅读构建音频应用程序的详细信息。


本节介绍了MediaPlayer在服务中实现时的特殊说明。



一)、异步运行

首先,像Activity一样,默认情况下,一个服务中的所有工作都是在单个线程中完成的 - 实际上,如果您从同一个应用程序运行一个活动和一个服务,则它们默认使用相同的线程(“主线程” “) 。 因此,服务需要快速处理传入意图,并且在对其进行响应时不要执行冗长的计算。 如果任何繁重的工作或阻塞调用是预期的,您必须异步执行这些任务:从您自己实现的另一个线程,或使用框架的许多工具进行异步处理。


例如,当从主线程使用MediaPlayer时,您应该调用prepareAsync()而不是prepare(),并实现MediaPlayer.OnPreparedListener,以便在准备完成后通知您,并且您可以开始播放。 例如:

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();
    }
}


二)、处理异步错误

在同步操作中,通常会通过异常或错误代码发出错误信息,但是当您使用异步资源时,应确保您的应用程序被正确通知错误。 在MediaPlayer的情况下,您可以通过实现MediaPlayer.OnErrorListener并将其设置在MediaPlayer实例中来实现:

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将移动到“错误”状态(有关完整状态图,请参阅MediaPlayer类的文档),您必须重新设置它,然后再次使用它。



三)、使用唤醒锁

当设计在后台播放媒体的应用程序时,设备可能在您的服务运行时进入睡眠状态。 由于Android系统尝试在设备休眠时节省电池,系统会尝试关闭任何不需要的功能,包括CPU和WiFi硬件。 但是,如果您的服务正在播放或流式传输音乐,则希望防止系统干扰播放。


为了确保您的服务在这些条件下继续运行,您必须使用“唤醒锁”。 唤醒锁是向系统发信号的一种方式,即您的应用程序正在使用某些功能,即使手机空闲也应该保持可用。


注意:您应该始终使用唤醒锁,并保持它们只需要真正的必要时间,因为它们会显着降低设备的电池寿命。


为确保在播放MediaPlayer时CPU继续运行,请在初始化MediaPlayer时调用setWakeMode()方法。 一旦你这样做,MediaPlayer在播放时保持指定的锁定,并在暂停或停止时释放锁定:

mMediaPlayer = new MediaPlayer();
// ... other initialization here ...
mMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);

但是,在本示例中获取的唤醒锁仅保证CPU保持唤醒。 如果您通过网络流媒体,并且正在使用Wi-Fi,您可能也希望持有WifiLock,您必须手动获取和释放。 因此,当您使用远程URL开始准备MediaPlayer时,您应该创建并获取Wi-Fi锁。 例如:

WifiLock wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))
    .createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");

wifiLock.acquire();

暂停或停止媒体时,或不再需要网络时,您应该释放锁:

wifiLock.release();


四)、执行清理

如前所述,MediaPlayer对象可以消耗大量的系统资源,所以只有在需要的时候才应该保留它,并且在完成之后调用release()。 重要的是要明确地调用这个cleanup方法,而不是依赖于系统垃圾回收,因为垃圾回收器回收MediaPlayer之前可能需要一些时间,因为它只对内存需求敏感,而不会缩短其他与媒体有关的资源。 因此,在使用服务的情况下,您应该始终覆盖onDestroy()方法,以确保您正在发布MediaPlayer:

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

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

除了在关闭时释放它,您应该总是寻找其他机会释放您的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...


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值