虽然直播落幕,但 Android 的音、视频技术仍然倍受关注!react
Android 提供了常见的音频、视频的编码、解码机制。借助于多媒体类 MediaPlayer 的支持,开发人员能够很方便地在应用中播放音频、视频。只不过使用 MediaPlayer 播放视频时,没有提供图像输出界面。android
Android 框架中使用如下类播放音频和视频:api
MediaPlayer : 这个类是播放音频和视频的主要API网络
AudioManager : 该类管理设备上的音频源和音频输出app
Android 下对于音频、视频的支持均须要使用到 MediaPlayer,它主要用来控制 Android 下播放文件或流的类。MediaPlayer 处于 Android 多媒体包下 “android.media.MediaPlayer”,仅有一个无参的构造函数,虽然仅为咱们提供了一个无参的构造函数,但为了开发方便,还为咱们提供了几个静态的 create() 方法用于完成MediaPlayer 初始化的工做。框架
static MediaPlayer create(Context context,int resid):经过音频资源的 Id 来建立一个 MediaPlayer 实例
static MediaPlayer create(Context context,Uri uri):经过一个音频资源的 Uri 地址来建立一个 MediaPlayer 实例异步
MediaPlayer 除了经过上面两个 create() 方法在初始化的时候指定媒体资源,还能够经过 MediaPlayer.setDataSource() 方法为初始化后的 MediaPlayer 设置媒体资源,setDataSource() 具备多个重载函数,适用于不一样的媒体资源来源,如下讲解几个经常使用的,其余的能够查阅官方文档。async
MediaPlayer 支持的数据源有:本地文件、内部的 Uri(内容提供者)、外部 Uri。ide
在使用 MediaPlayer 对应用程序进行开发以前,请确保清单中有适当的声明,容许使用相关特性。函数
Internet 权限——若是您正在使用 MediaPlayer 来播放流基于网络的内容,那么应用程序必须请求网络访问。
<uses-permission android:name="android.permission.INTERNET" />
Wake Lock 权限——若是您的播放器应用程序须要阻止屏幕变暗或处理器休眠,或 MediaPlayer.setWakeMode() 方法,必须请求此权限。
<uses-permission android:name="android.permission.WAKE_LOCK" />
MediaPlayer 状态机图
MediaPlayer 类中的文档显示了一个完整的状态机,它阐明了哪些方法将 MediaPlayer 从一个状态移动到另外一个状态。例如,当您建立一个新的 MediaPlayer 时,它处于空闲状态。这时,您应该经过调用 setDataSource() 来初始化它,使它处于初始化状态。以后,您必须使用 prepare() 或 prepareAsync() 方法来准备它。当 MediaPlayer 完成准备工做时,它进入准备状态,这意味着您能够调用 start() 来让它播放媒体。此时,您能够经过调用 start()、pause() 和 seekTo() 等方法在 start、pause() 和 PlaybackCompleted 状态之间切换。可是,当您调用 stop() 时,请注意,在从新准备 MediaPlayer 以前,您不能再次调用 start()。
MediaPlayer 是基于状态的。也就是说,MediaPlayer 有一个内部状态,与 MediaPlayer 对象交互的代码时,必定要记住状态图,在编写代码时必须始终注意到,由于只有当 player 处于特定状态时,某些操做才有效。若是在错误的状态下执行操做,系统可能会抛出异常或引起其余不但愿看到的行为。
MediaPlayer 实际上是一个封装的很好的音频、视频流媒体操做类,其内部是调用的 native 方法,因此它实际上是有 C++ 实现的。既然是一个流媒体操做类,那么必然涉及到,播放、暂停、中止等操做,实际上 MediaPlayer 也为咱们提供了相应的方法来直接操做流媒体。
经过上面三个方法,只要设定好流媒体数据源,便可在应用中播放流媒体资源,为了更好的操做流媒体,MediaPlayer 还为咱们提供了一些其余的方法,这里列出一些经常使用的,详细内容参阅官方文档。
大部分方法看方法名就能够理解,可是有几个方法须要单独说明一下。
在使用 MediaPlayer 播放一段流媒体的时候,须要使用 prepare() 或 prepareAsync() 方法把流媒体装载进 MediaPlayer,才能够调用 start() 方法播放流媒体。
setAudioStreamType() 方法用于指定播放流媒体的类型,它传递的是一个 int 类型的数据,均以常量定义在 AudioManager 类中, 通常咱们播放音频文件,设置为AudioManager.STREAM_MUSIC 便可。
除了上面介绍的一些方法外,MediaPlayer 还提供了一些事件的回调函数,这里介绍几个经常使用的:
媒体框架最重要的组件之一是 MediaPlayer 类,这个类的对象可使用最少的设置获取、解码和播放音频和视频。它支持几种不一样的媒体来源,如:
有关 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
在本例中,“raw” 资源是系统不尝试以任何特定方式解析的文件。然而,这个资源的内容不该该是原始音频。它应该是一个以支持的格式之一适当编码和格式化的媒体文件。
下面是您如何从系统中本地可用的 URI(例如,您经过内容解析器得到的 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();
注意:
使用 MediaPlayer 原则上很简单。可是,须要记住的是,要将它正确地集成到典型的 Android 应用程序中,还须要作一些其余的事情。例如,prepare() 的调用可能须要很长时间执行,由于它可能涉及到获取和解码媒体数据。所以,就像任何须要很长时间才能执行的方法同样,永远不要从应用程序的UI线程调用它。这样作会致使 UI 挂起,直到方法返回,这是一种很是糟糕的用户体验,并可能致使 ANR(应用程序没有响应)错误。即便您但愿您的资源可以快速加载,也要记住,在 UI 中任何须要超过十分之一秒才能响应的内容都会引发明显的暂停,并给用户留下您的应用程序很慢的印象。
为了不挂起UI线程,生成另外一个线程来准备 MediaPlayer,并在完成时通知主线程。然而,虽然您能够本身编写线程逻辑,可是在使用 MediaPlayer 时,这种模式很是常见,所以框架提供了一种方便的方法来经过使用 prepareAsync() 方法来完成此任务。该方法开始在后台准备媒体并当即返回。当媒体完成准备工做时,将调用 经过 setOnPreparedListener() 配置的 MediaPlayer.OnPreparedListener() 的 onPrepared() 方法。
MediaPlayer 可能会消耗有价值的系统资源。所以,您应该始终采起额外的预防措施,以确保您没有过多地依赖 MediaPlayer 实例。处理完它以后,应该始终调用 release(),以确保分配给它的任何系统资源都被正确释放。例如,若是您使用的是一个媒体播放器和活动接收 onStop() 调用,您必须释放媒体播放器,由于当你的活动不与用户进行交互,继续持有实例毫无心义(除非你是在后台播放媒体)。当您的活动恢复或从新启动时,固然,您须要建立一个新的 MediaPlayer,并在恢复回放以前从新准备。
下面是应该如何释放并取消 MediaPlayer:
mediaPlayer.release(); mediaPlayer = null;
做为一个例子,考虑一下若是您在活动中止时忘记释放 MediaPlayer,而在活动从新开始时建立一个新的,可能会发生的问题。正如你可能知道的,当用户更改屏幕的方向(或更改设备配置以另外一种方式),系统处理,经过从新启动活动(默认状况下),因此你可能会很快消耗掉全部系统资源的用户旋转设备之间来回的肖像和风景,由于在每个方向变化,您建立一个新的媒体播放器,你永远不会释放。
若是您想要您的媒体在后台播放,即便您的应用程序不是在屏幕上——也就是说,您想要它在用户与其余应用程序交互时继续播放——那么您必须启动一个服务并从那里控制 MediaPlayer 实例。您须要将 MediaPlayer 嵌入到 MediaBrowserServiceCompat 服务中,并让它与另外一个活动中的 MediaBrowserCompat 交互。
要当心这个 client/server 设置。人们对在后台服务中运行的播放器如何与系统的其余部分进行交互抱有指望。若是您的应用程序没有知足这些指望,用户可能会有一个糟糕的体验。阅读创建一个音频应用程序的完整细节。
首先,与活动同样,服务中的全部工做在默认状况下都是在单个线程中完成的——事实上,若是您从同一个应用程序运行活动和服务,默认状况下它们使用相同的线程(“主线程”)。所以,服务须要快速处理传入意图,而且在响应它们时从不执行冗长的计算。若是预期有任何繁重的工做或阻塞调用,您必须异步执行这些任务:要么从另外一个您本身实现的线程执行,要么使用框架的许多异步处理工具。
例如,在使用主线程中的 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 将切换到错误状态,必须在再次使用它以前重置它。
当设计在后台播放媒体的应用程序时,设备可能会在服务运行时休眠。因为 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()。显式调用这种清理方法而不是依赖于系统垃圾收集是很重要的,由于垃圾收集器从新声明 MediaPlayer 可能须要一些时间,由于它只对内存需求敏感,而不缺少其余与媒体相关的资源。所以,在使用服务时,您应该老是重写 onDestroy() 方法,以确保释放 MediaPlayer:
public class MyService extends Service { MediaPlayer mMediaPlayer; // ... @Override public void onDestroy() { super.onDestroy() if (mMediaPlayer != null) mMediaPlayer.release(); } }
在使用 MediaPlayer 的使用过程当中,有些小技巧须要说明一下:
这里最好使用 prepareAsync() 用异步的方式装载流媒体资源。由于流媒体资源的装载是会消耗系统资源的,在一些硬件不理想的设备上,若是使用 prepare() 同步的方式装载资源,可能会形成 UI 界面的卡顿,这是很是影响用于体验的。由于推荐使用异步装载的方式,为了不尚未装载完成就调用 start() 而报错的问题,须要绑定MediaPlayer.setOnPreparedListener() 事件,它将在异步装载完成以后回调。异步装载还有一个好处就是避免装载超时引起 ANR((Application Not Responding)错误。
mediaPlayer = new MediaPlayer(); mediaPlayer.setDataSource(path); mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); // 经过异步的方式装载媒体资源 mediaPlayer.prepareAsync(); mediaPlayer.setOnPreparedListener(new OnPreparedListener() { @Override publicvoid onPrepared(MediaPlayer mp) { mediaPlayer.start(); } });
MediaPlayer 是很消耗系统资源的,因此在使用完 MediaPlayer,不要等待系统自动回收,最好是主动回收资源。
if (mediaPlayer != null && mediaPlayer.isPlaying()) { mediaPlayer.stop(); mediaPlayer.release(); mediaPlayer = null; }
实际上,就算是直接使用 Activity 承载 MediaPlayer,也最好在销毁的时候判断一下 MediaPlayer 是否被回收,若是未被回收,回收其资源,由于底层调用的 native 方法,若是不销毁仍是会在底层继续播放,而承载的组件已经被销毁了,这个时候就没法获取到这个 MediaPlayer 进而控制它。
@Override protectedvoid onDestroy() { if (mediaPlayer != null && mediaPlayer.isPlaying()) { mediaPlayer.stop(); mediaPlayer.release(); mediaPlayer = null; } super.onDestroy(); }
对于单曲循环之类的操做,除了可使用 setLooping() 方法进行设置以外,还能够为 MediaPlayer 注册回调函数,MediaPlayer.setOnCompletionListener(),它会在 MediaPlayer 播放完毕被回调。
// 设置循环播放 // mediaPlayer.setLooping(true); mediaPlayer.setOnCompletionListener(new OnCompletionListener() { @Override publicvoid onCompletion(MediaPlayer mp) { // 在播放完毕被回调 play(); } });
由于 MediaPlayer 一直操做的是一个流媒体,因此无可避免的可能一段流媒体资源,前半段能够正常播放,而中间一段由于解析或者源文件错误等问题,形成中间一段没法播放问题,须要咱们处理这个错误,不然会影响 UX(用户体验)。能够为 MediaPlayer 注册回调函数 setOnErrorListener() 来设置出错以后的解决办法,通常从新播放或者播放下一个流媒体便可。
mediaPlayer.setOnErrorListener(new OnErrorListener() { @Override publicboolean onError(MediaPlayer mp, int what, int extra) { play(); returnfalse; } });
Android 多媒体框架支持播放各类常见媒体类型,所以您能够轻松地将音频、视频和图像集成到应用程序中。您可使用 MediaPlayer api 从存储在应用程序资源(原始资源)中的媒体文件、文件系统中的独立文件或经过网络链接到达的数据流中播放音频或视频。
更多信息能够点击关于我 , 很是但愿和你们一块儿交流 , 共同进步