前言

  之前博客里已经将了MediaPlayer的简单应用,如何使用MediaPlayer在Android应用中播放音频。这篇博客在MediaPlayer使用的基础上,讲解一下MediaPlayer的一些高级功能的使用,以及它的状态转换。对MediaPlayer还不了解的朋友可以先看看之前那篇博客:Android--MP3播放器MediaPlayer。

  本篇博客主要内容如下:

  1. MediaPlayer的状态变换
  2. MediaPlayer的唤醒锁
  3. MediaPlayer的音频焦点

MediaPlayer的状态变换

  之前讲到,使用MediaPlayer播放音频,主要使用的是start()、pause()、stop()等方法操作MediaPlayer。但是除了开始、暂停、停止等,MediaPlayer还涉及到一些其他的状态切换,有些状态是可以双向转换的,有些只能单向环形转换。如果在某状态下,强行转换状态,会应发程序错误,例如在Preparing状态下切换到Started状态,是准备中强行开始播放,会出错。下图是官方文档上的图例,可以很清晰的表名MediaPlayer各个状态的转换情况。

  上图已经对MediaPlayer的各种状态转换有的清晰的介绍,这里不再详细讲解了,只是提一下需要注意的地方:

  • Started(开始)/Paused(暂停)到Stopped(停止)是单向转换,无法再从Stopped直接转换到Started,需要经历Prepared重新装载才可以重新播放。
  • Initialized(初始化)状态需要装载数据才可以进行start()播放,但是如果使用prepareAsync()方法异步准备,需要等待准备完成再开始播放,这里需要使用一个回调方法:setOnPreparedListener(),它会在异步装载完成后调用。
  • End(结束)状态是游离在其他状态之外的,在任何状态皆可切换,一般在不需要继续使用MediaPlayer的时候,才会使用release()回收资源。
  • Error(错误)状态是游离在其他状态之外的,只有在MediaPlayer发生错误的时候才会转换。为了保持应用的用户体验,一般我们回监听setOnErrorListener()回调方法,它会在MediaPlayer发生错误的时候被回调。

MediaPlayer的唤醒锁

  一般使用MediaPlayer播放音频流,推荐使用一个Service来承载MediaPlayer,而不是直接在Activity里使用。但是Android系统的功耗设计里,为了节约电池消耗,如果设备处于睡眠状态,系统将试图降低或者关闭一些没设备必须的特性,包括CUP和Wifi硬件,然后,如果是一个后台播放音乐的应用,降低CUP可能导致在后台运行的时候干扰音频的正常播放,关闭Wifi将可能导致网络音频流的获取出现错误。

  为了确保MediaPlayer的承载的服务在系统睡眠的时候继续正常运行下去,Android为我们提供了一种唤醒锁(wake locks)的机制。它可以在系统睡眠的,依然保持锁定硬件的正常工作。

  确保在MediaPlayer运行的时候,哪怕系统睡眠了CUP也能正常运行,需要使用MediaPlayer.setWakeMode()为MediaPlayer设定唤醒锁。下面是setWakMode()的签名:

    setWakeMode(Context context, int mode)

  第一个参数是当前上下文,第二个参数为需要加锁的状态,被设定为int类型的常量,定义在PowerManager这个final类中。PowerManager是专门用来管理Android功率消耗的锁定状态,与锁定CUP相关的,有四种,分别设定CUP、屏幕、键盘等的各种保持唤醒的状态,在这里只需要设定为PARTIAL_WAKE_LOCK即可。

                 mediaPlayer = new MediaPlayer();
// 设定CUP锁定
mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);

  一般对于锁而言,锁定了通常需要解锁,但是这里的唤醒说与MediaPlayer关联,所以只需要在使用完之后release()释放MediaPlayer即可,无需显式的为其解锁。在使用setWakeMode设定唤醒锁的时候,还必须为应用赋予相应的权限:

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

  

  再来说说如何锁定wifi硬件在系统睡眠的时候保持正常运行。wifi锁通过WifiLock进行操作,而WifiLock通过WifiManager进行管理,通过WifiManager.createWifiLock()进行Wifi锁定。

    WifiManager.WifiLock createWifiLock(int lockType, String tag)

  这个方法有多个重载,这里介绍的这个,第一个参数设定锁的状态,为一个int类型的常量,定义在Context类中,这里的应用场景一般设定为WIFI_MODE_FULL即可。第二个参数为WifiLock的标志,用于确定wifiLock的。

         wifiLock= ((WifiManager) getSystemService(this.WIFI_SERVICE))
.createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");
wifiLock.acquire();

  当然,在应用中把Wifi锁定之后,还需要在MediaPlayer.release()的时候为wifi硬件解锁,为避免意外关闭的情况,最好在Android组件的onDestory()里对其进行释放,释放Wifi锁使用WifiLock.release()。  

     /**
* 停止播放
*/
protected void stop() {
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer = null;
// 释放wifi锁
wifiLock.acquire();
btn_play.setEnabled(true);
Toast.makeText(this, "停止播放", 0).show();
} } @Override
protected void onDestroy() {
// 在activity结束的时候回收资源
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer = null;
// 释放wifi锁
wifiLock.acquire();
}
super.onDestroy();
}

MediaPlayer的音频焦点

  众所周知,Android是一个多任务的操作系统,所以对于音频的播放,也许有几个不同的媒体服务会同时播放,这样可能导致一个比较杂乱的声音环境,而错过一些重要的声音提醒。在Android2.2之后,Android提供了一种应用协商使用设备音频输出的机制,这种机制称为音频焦点。

  当应用程序需要输出音频或通知的时候,需要请求音频焦点,当请求得到音频焦点之后,监听音频焦点的变换,当音频焦点变换了,根据返回回来的音频焦点码进行相应的处理。音频焦点的注册使用音频管理器的AudioManager.requestAudioFocus()方法设定。它的签名如下:

    int requestAudioFocus(AudioManager.OnAudioFocusChangeListener l, int streamType, int durationHint)

  这个方法的返回值是int类型,其含义被定义在AudioManager中以常量表示AUDIOFOCUS_REQUEST_FAILED(获取音频焦点成功)AUDIOFOCUS_REQUEST_GRANTED(获取音频焦点失败)。其中重要的是第一个参数,为音频焦点变化的回调函数,在其中可以设定如果音频焦点变换了,当前应用如何管理MediaPlayer,第二个参数为媒体流的类型,第三个参数为持续的状态。

  AudioManager.OnAudioFocusChangeListener为音频焦点变换的监听器,其中需要实现一个方法:onAudioFocusChange(int focusChange)在音频焦点变换的时候回调。它有一个参数,为当前表示音频焦点对于当前应用的状态码,通过这个状态码指定对应的操作,有些时候音频状态改变了,并不一定需要停止音频的播放。

  focusChange有一下几种状态码:

  • AUDIOFOCUS_GAIN:获得音频焦点。
  • AUDIOFOCUS_LOSS:失去音频焦点,并且会持续很长时间。这是我们需要停止MediaPlayer的播放。
  • AUDIOFOCUS_LOSS_TRANSIENT:失去音频焦点,但并不会持续很长时间,需要暂停MediaPlayer的播放,等待重新获得音频焦点。
  • AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:暂时失去音频焦点,但是无需停止播放,只需降低声音方法。
                 audioManager = (AudioManager) getSystemService(this.AUDIO_SERVICE);
int result = audioManager.requestAudioFocus(
new OnAudioFocusChangeListener() { @Override
public void onAudioFocusChange(int focusChange) {
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN:
// 获得音频焦点
if (!mediaPlayer.isPlaying()) {
mediaPlayer.start();
}
// 还原音量
mediaPlayer.setVolume(1.0f, 1.0f);
break; case AudioManager.AUDIOFOCUS_LOSS:
// 长久的失去音频焦点,释放MediaPlayer
if (mediaPlayer.isPlaying())
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer = null;
break; case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
// 展示失去音频焦点,暂停播放等待重新获得音频焦点
if (mediaPlayer.isPlaying())
mediaPlayer.pause();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
// 失去音频焦点,无需停止播放,降低声音即可
if (mediaPlayer.isPlaying()) {
mediaPlayer.setVolume(0.1f, 0.1f);
}
break;
}
}
}, AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN);

  

  源码下载

总结 

  以上就讲解了MediaPlayer的一些高级的内容,在掌握了MediaPlayer的使用之后,开发有关音乐播放类的应用的时候就可以得心应手了。从用户体验的方面出发,如果真实开发一款播放器类的软件,需要监听AUDIO_BECOMING_NOISY的广播,它会在音频输出源从其他输出源变换到设备扬声器的时候发出此广播,监听广播在音频输出源改变到设备扬声器的时候,停止播放,这样确保在耳机或额外的音频输出硬件与设备断开连接的时候,不至于重新从扬声器继续输出音频播放。

Android--MediaPlayer高级的更多相关文章

  1. 第一部分 Android MediaPlayer 概述

    [IT168 技术文档]本文主要介绍的是Android中很重要也最为复杂的媒体播放器(MediaPlayer)部分的架构.对于Android这样一个完整又相对复杂的系统,一个MediaPlayer功能 ...

  2. Android MediaPlayer Error/Info Code

    1. 常见错误 error(-38, 0) 我觉得-38表示在当前的MediaPlayer状态下,不能运行你的操作. 详细怎样做请參考:Android MediaPlayer 另外我在其它资料中.发现 ...

  3. Android(java)学习笔记180:Android MediaPlayer 播放prepareAsync called in state 8解决办法

    使用android MediaPlayer播放音频文件时,有时会出现prepareasync called in state 8错误. 以下方法可以避免这个异常出现.  第一种方法: private ...

  4. Android MediaPlayer架构 -- MediaPlayer的创建过程

    本文系作者自己学习之所用,文章内容仅出自作者拙劣之思考,问题之处烦请不吝指教. MediaPlayer 能被用来控制音/视频文件或流媒体的回放.Android中以MediaPlayer类作为音视频播放 ...

  5. Android MediaPlayer接口及状态迁移

    [时间:2016-09] [状态:Open] [关键词:android,mediaplayer,播放接口,播放状态图] 引言 本文内容相对简单,作为后续处理的起点,简要整理了Android Media ...

  6. Android MediaPlayer 常用方法介绍

    Android MediaPlayer 常用方法介绍 方法:create(Context context, Uri uri) 解释:静态方法,通过Uri创建一个多媒体播放器. 方法:create(Co ...

  7. 《Android传感器高级编程》

    <Android传感器高级编程> 基本信息 原书名:Professional Android Sensor Programming 原出版社: Wrox 作者: (美)米内特(Greg M ...

  8. Android(java)学习笔记123:Android MediaPlayer 播放prepareAsync called in state 8解决办法

    1. 使用android MediaPlayer播放音频文件时,有时会出现prepareasync called in state 8错误. 以下方法可以避免这个异常出现.  第1种方法: priva ...

  9. Android MediaPlayer 基础简介

    本文链接: Android MediaPlayer 基础简介 简单介绍MediaPlayer的基本概念,状态,常用的方法与监听器. 什么是MediaPlayer MediaPlayer类可以用来播放音 ...

  10. Android MediaPlayer 播放音频

    本文链接: Android MediaPlayer 播放音频 主要介绍使用MediaPlayer播放音频的方式.关于MediaPlayer的基础知识,比如状态,可以参考Android MediaPla ...

随机推荐

  1. Codeforces 938D. Buy a Ticket (最短路+建图)

    <题目链接> 题目大意: 有n座城市,每一个城市都有一个听演唱会的价格,这n座城市由m条无向边连接,每天变都有其对应的边权.现在要求出每个城市的人,看一场演唱会的最小价值(总共花费的价值= ...

  2. 机器学习入门之sklearn介绍

    SKlearn简介 scikit-learn,又写作sklearn,是一个开源的基于python语言的机器学习工具包.它通过NumPy, SciPy和Matplotlib等python数值计算的库实现 ...

  3. D3---01基础的柱状图制作(转)

    ---文章转自 http://d3.decembercafe.org/index.html  ,Created by 十二月咖啡馆. 一个完整的柱形图包含三部分:矩形.文字.坐标轴. 首先要布置一个大 ...

  4. Java下载文件的几种方式

    转发自博客园Sunny的文章 1.以流的方式下载 public HttpServletResponse download(String path, HttpServletResponse respon ...

  5. Python3从零开始爬取今日头条的新闻【一、开发环境搭建】

    Python3从零开始爬取今日头条的新闻[一.开发环境搭建] Python3从零开始爬取今日头条的新闻[二.首页热点新闻抓取] Python3从零开始爬取今日头条的新闻[三.滚动到底自动加载] Pyt ...

  6. JavaScript(九)

    内置对象 1.document document.referrer //获取上一个跳转页面的地址(需要服务器环境) 2.location window.location.href //获取或者重定ur ...

  7. Mac自动化环境

    1. JDK安装 下载JDK for Mac 我这里使用的是  jdk-7u79-macosx-x64.dmg 验证安装open Terminal  java -version java versio ...

  8. mybatis逆向工程的注意事项,以及数据库表

    1.选择性更新,如果有新参数就更换成新参数,如果参数是null就不更新,还是原来的参数 2.mybatis使用逆向工程,数据库建表的字段user_id必须用下滑线隔开,这样生成的对象private L ...

  9. mysql根据查询结果批量更新多条数据(插入或更新)

    mysql根据查询结果批量更新多条数据(插入或更新) 1.1 前言 mysql根据查询结果执行批量更新或插入时经常会遇到1093的错误问题.基本上批量插入或新增都会涉及到子查询,mysql是建议不要对 ...

  10. JS Fetch

    使用Fetch 1.进行 fetch 请求 一个基本的 fetch请求设置起来很简单.看看下面的代码: fetch('http://example.com/movies.json') .then(fu ...