【Audio&Video】MediaPlayer概述(20)

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

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

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

基础


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

MediaPlayer
这个类是播放声音和视频的主要API。
AudioManager
该课程管理设备上的音频源和音频输出。

清单声明


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

Internet权限 - 如果您使用MediaPlayer传输基于网络的内容,则您的应用程序必须请求网络访问。

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

唤醒锁许可 - 如果您的播放器应用程序需要防止调光或处理器进入休眠状态,或使用MediaPlayer.setScreenOnWhilePlaying()或 MediaPlayer.setWakeMode()方法,则必须请求此权限。

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

使用MediaPlayer


媒体框架最重要的组成部分之一就是 MediaPlayer 班级。该类的一个对象可以用最少的设置获取,解码和播放音频和视频。它支持多种不同的媒体来源,例如:

本地资源
内部URI,例如您可能从内容解析器获得的内部URI
外部URL(流媒体)
有关Android支持的媒体格式列表,请参阅支持的媒体格式页面。

扫描二维码关注公众号,回复: 1656491 查看本文章

以下是如何播放可作为本地原始资源(保存在应用程序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流从远程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,则该文件必须能够进行渐进式下载。

警告:由于您引用的文件可能不存在,因此您必须捕获或传递 IllegalArgumentException并IOException使用 setDataSource()它们。

异步准备

MediaPlayer原则上使用可以是直截了当的。但是,请务必记住,需要将其与一个典型的Android应用程序正确集成,还需要做一些其他事情。例如,调用prepare()可能需要很长时间才能执行,因为它可能涉及获取和解码媒体数据。因此,与任何可能需要很长时间执行的方法一样,您不应该从应用程序的UI线程调用它。这样做会导致用户界面挂起,直到方法返回,这是非常糟糕的用户体验,并可能导致ANR(应用程序无响应)错误。即使您希望快速加载资源,请记住,在UI中响应超过十分之一秒的任何内容都会导致明显的暂停,并给用户留下应用程序速度缓慢的印象。

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

管理状态

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

MediaPlayer该类中的文档 显示了一个完整的状态图,阐明了哪些方法MediaPlayer将从一个状态移动到另一个状态。例如,当你创建一个新的MediaPlayer,它处于空闲 状态。此时,您应该通过调用来初始化它 setDataSource(),使其进入初始化状态。之后,你必须使用prepare()or prepareAsync()方法来准备它 。当MediaPlayer完成准备时,进入准备 状态,这意味着你可以调用start() 使其发挥媒体。在这一点上,如下图所示,您可以在之间移动开始,暂停和PlaybackCompleted通过调用这些方法的状态 start(), pause()和 seekTo(),等等。stop()但是,当你打电话时,请注意,在你start()再次准备之前,你不能再打电话MediaPlayer。

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

释放MediaPlayer

A MediaPlayer可以消耗宝贵的系统资源。因此,您应该始终采取额外的预防措施,以确保您不会MediaPlayer超出必要的时间。完成之后,您应该始终致电 release()以确保分配给它的任何系统资源都已正确释放。例如,如果您正在使用a MediaPlayer并且您的活动接到呼叫onStop(),则必须释放该呼叫MediaPlayer,因为在您的活动未与用户进行交互时按住该呼叫并没有什么意义(除非您正在后台播放媒体,这在下一节讨论)。当您的活动恢复或重新启动时,当然,您需要创建一个新的MediaPlayer并在恢复播放之前重新进行准备。

以下是您应如何释放然后取消您的MediaPlayer:

mediaPlayer.release();
mediaPlayer = null;

作为一个例子,考虑一下MediaPlayer当您的活动停止时忘记释放时可能发生的问题,但当活动再次开始时创建一个新问题。如您所知,当用户更改屏幕方向(或以其他方式更改设备配置)时,系统通过重新启动活动(默认情况下)来处理该活动,以便在用户旋转时快速占用所有系统资源该设备在纵向和横向之间来回切换,因为在每次方向更改时,都会创建一个MediaPlayer永不释放的新设备。(有关运行时重新启动的更多信息,请参阅处理运行时更改。)

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

在服务中使用MediaPlayer


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

你应该小心这个客户端/服务器设置。人们对运行在后台服务中的玩家如何与系统的其他部分进行交互存在期望。如果您的应用程序不能满足这些期望,用户可能会遇到不好的经历。阅读 构建一个音频应用程序 的全部细节。

本节介绍在服务内部实施时管理MediaPlayer的特殊说明。

异步运行

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

例如,在MediaPlayer主线程中使用a时,应该调用prepareAsync()而不是 prepare()执行a 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();
    }
}

处理异步错误

在同步操作中,错误通常会通过异常或错误代码发出信号,但是无论何时使用异步资源,都应该确保您的应用程序得到适当的错误通知。在a的情况下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硬件。但是,如果您的服务正在播放或流式传输音乐,则需要防止系统干扰播放。

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

注意:您应该始终谨慎地使用唤醒锁,并且只在真正需要的时候保持它们,因为它们会显着缩短设备的电池寿命。

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

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

但是,本例中获得的唤醒锁只能保证CPU保持唤醒状态。如果您通过网络流媒体并且正在使用Wi-Fi,那么您可能还想保留一个WifiLock必须手动获取和释放的媒体 。因此,当您开始准备 MediaPlayer使用远程URL时,您应该创建并获取Wi-Fi锁定。例如:

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

wifiLock.acquire();

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

wifiLock.release();

执行清理

正如前面提到的,一个MediaPlayer对象可以消耗大量的系统资源,因此只有在需要release()时才能保留该对象,并在完成后调用 它。显式调用此清理方法非常重要,而不是依靠系统垃圾收集,因为垃圾回收器可能需要一段时间才能回收它MediaPlayer,因为它只对内存需求敏感,并且不会影响其他与介质相关的资源。因此,在使用服务的情况下,您应该始终重写该 onDestroy()方法以确保您释放MediaPlayer:

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

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

MediaPlayer 除了在关闭时释放它之外,您应该始终寻找其他机会来释放它。例如,如果您预计无法长时间播放媒体(例如在失去音频焦点之后),则您应该释放现有的MediaPlayer并稍后再创建它。另一方面,如果您只希望在很短的时间内停止播放,则应该坚持MediaPlayer以避免重新创建和准备它的开销。

数字版权管理(DRM)


从Android 8.0(API级别26)开始,MediaPlayer包含支持播放受DRM保护的材料的API。它们与提供的低级API类似 MediaDrm,但是它们在更高级别上运行,并且不公开底层提取器,drm和加密对象。

虽然MediaPlayer DRM API没有提供完整的功能 MediaDrm,但它支持最常见的用例。当前的实现可以处理以下内容类型:

Widevine保护的本地媒体文件
Widevine保护的远程/流媒体文件
以下代码片段演示了如何在简单的同步实现中使用新的DRM MediaPlayer方法。

要管理DRM控制的媒体,您需要将新方法与MediaPlayer通常的流程一起包括在内,如下所示:

setDataSource();
setOnDrmConfigHelper(); // optional, for custom configuration
prepare();
if (getDrmInfo() != null) {
  prepareDrm();
  getKeyRequest();
  provideKeyResponse();
}

// MediaPlayer is now ready to use
start();
// ...play/pause/resume...
stop();
releaseDrm();

像往常一样, 首先使用初始化MediaPlayer对象并设置其来源setDataSource()。然后,要使用DRM,请执行以下步骤:

如果您希望自己的应用执行自定义配置,请定义一个 OnDrmConfigHelper界面,然后使用它将其附加到播放器 setOnDrmConfigHelper()。
打电话prepare()。
打电话getDrmInfo()。如果源具有DRM内容,则该方法返回一个非空 MediaPlayer.DrmInfo值。
如果MediaPlayer.DrmInfo存在:

检查可用UUID的地图并选择一个。
通过调用为当前源准备DRM配置prepareDrm()。
如果您创建并注册了 OnDrmConfigHelper回调,则在prepareDrm() 执行时调用它。这使您可以在打开DRM会话之前执行DRM属性的自定义配置。该回调在调用的线程中同步调用 prepareDrm()。要访问DRM属性,请拨打 getDrmPropertyString()和 setDrmPropertyString()。避免执行冗长的操作。
如果设备尚未配置, prepareDrm()还会访问配置服务器以配置设备。这可能需要很长时间,具体取决于网络连接。
要获取不透明的密钥请求字节数组以发送到许可证服务器,请致电 getKeyRequest()。
要通知DRM引擎有关从许可证服务器收到的密钥响应,请致电 provideKeyResponse()。结果取决于密钥请求的类型:
如果响应是针对脱机密钥请求的,则结果是密钥集标识符。您可以使用此密钥集标识符restoreKeys()将密钥恢复到新会话。
如果响应是针对流或释放请求的,则结果为空。

prepareDrm()异步运行

默认情况下,prepareDrm() 同步运行,阻塞,直到准备完成。但是,新设备上的首次DRM准备工作可能还需要配置,该配置由内部处理 prepareDrm(),并且可能需要一些时间才能完成,因为涉及网络操作。您可以prepareDrm()通过定义和设置一个来避免阻塞 MediaPlayer.OnDrmPreparedListener。

当您设置时OnDrmPreparedListener, prepareDrm() 在后台执行配置(如果需要)和准备。当配置和准备完成后,监听器被调用。您不应对调用序列或侦听器运行的线程作出任何假设(除非侦听器使用处理程序线程注册)。可以在prepareDrm() 返回之前或之后调用侦听器 。

异步设置DRM

您可以通过创建和注册MediaPlayer.OnDrmInfoListenerDRM准备和 MediaPlayer.OnDrmPreparedListener启动播放器来异步初始化DRM 。他们配合使用 prepareAsync(),如下所示:

setOnPreparedListener();
setOnDrmInfoListener();
setDataSource();
prepareAsync();
// ....

// If the data source content is protected you receive a call to the onDrmInfo() callback.
onDrmInfo() {
  prepareDrm();
  getKeyRequest();
  provideKeyResponse();
}

// When prepareAsync() finishes, you receive a call to the onPrepared() callback.
// If there is a DRM, onDrmInfo() sets it up before executing this callback, so you can start the player.
onPrepared() {

start();
}

处理加密媒体


从Android 8.0(API级别26)开始,MediaPlayer还可以为基本流类型H.264和AAC解密通用加密方案(CENC)和HLS样本级加密媒体(METHOD = SAMPLE-AES)。以前支持全段加密媒体(METHOD = AES-128)。

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

示例代码


android- SimpleMediaPlayer代码示例展示了如何构建一个独立的播放器。在Android的BasicMediaDecoder和 Android的DeviceOwner样品进一步证明使用覆盖此页面上的API。

学到更多


这些页面涵盖了有关录制,存储和播放音频和视频的主题。


MediaRecorder
数据存储

联系我

QQ:94297366
微信打赏:https://pan.baidu.com/s/1dSBXk3eFZu3mAMkw3xu9KQ

公众号推荐:

【Audio&Video】MediaPlayer概述(20)

猜你喜欢

转载自blog.51cto.com/4789781/2130808