起因

前一阵子完成了用有线耳机控制Android手机App的音频播放,具体实现了用耳机线的按键完成播放、暂停、上一曲、下一曲的功能。在网上查阅了一些资料,但不是特别尽如人意,记得有一篇写的很不错的这方面的文章里有两处很细微的错误,我不经分辨拿来用的时候没有达到我想要的线控效果。后来从头做起,每写完一块代码,就进行打Log测试,查看输出日志是否实现了我期待的结果。就这样一步一步,最后完美解决。我想这种方式可能是我以后处理问题的一种可以遵循的步骤,故此在这里简略记录我当时的操作过程,也许未来于己于人有细微的帮助呢~

初始状态

  • 耳机为有线耳机,耳机上共有3个按键,分别是音量+键,暂停/播放键,音量-键;
  • app播放音频,耳机音量+/-键可控制系统音量,app内的音频音量亦增/减
  • 按压耳机播放/暂停键,启动系统的音乐播放器,app内音频无影响

期待的状态

  • 在耳机上中下3按键分别是音量+、播放/暂停、音量-的功能时,单击中键实现暂停/播放,双击中键实现下一曲,三连击中键实现上一曲
  • 在耳机上中下3按键分别是上一曲、播放/暂停、下一曲的功能时,单击上键实现上一曲,单击中键实现播放/暂停,单击下键实现下一曲
  • App在后台播放音频时,依然接收耳机按键的控制

尝试1

在AndroidManifest.xml 中添加:

".audio.PlayerService.HeadsetButtonReceiver">    "1000">        "android.intent.action.MEDIA_BUTTON" />    
解释上述代码:
许多线控或者无线耳机都会有许多媒体播放控制按钮,例如:播放,停止,暂停,下一曲,上一曲等。无论用户按下设备上任意一个控制按钮,系统都会广播一个带有ACTION_MEDIA_BUTTON的Intent。为了正确地响应这些操作,需要在Manifest文件中注册一个针对于该Action的BroadcastReceiver( 详见)。
第一行:声明要接收到MEDIA_BUTTON广播的广播接收器,该接收器是一个类,需要程序猿在对应的位置新建这样的广播接收器
第二行:注册Intent Filter过滤器,设置该App接收到MEDIA_BUTTON广播的优先级为1000
第三行:设置该接收器要接收的广播
在程序的对应位置新建类:
public class HeadsetButtonReceiver extends BroadcastReceiver {    @Override    public void onReceive(Context context, Intent intent) {        Log.e("headSet","HeadsetButtonReceiver:"+"onReceive");        if(Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())){            Log.e("headSet","HeadsetButtonReceiver:"+"onReceive:"+"if:");            KeyEvent keyEvent = (KeyEvent) intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);            if(keyEvent.KEYCODE_MEDIA_PLAY == keyEvent.getKeyCode()){                Log.e("headSet","HeadsetButtonReceiver:"+"onReceive:"+"if:"+"if");            }        }    }}
解释上述代码:
在新建的广播接收器HeadsetButtonReceiver中重写onReceive()方法,当耳机按键广播被该接收器接收到后,将进入onReceive()中
在实现中,需要判断这个广播来自于哪一个按钮,Intent通过EXTRA_KEY_EVENT这一Key包含了该信息,另外,KeyEvent类包含了一系列诸如KEYCODE_MEDIA_*的静态变量来表示不同的媒体按钮,例如KEYCODE_MEDIA_PLAY_PAUSE 与 KEYCODE_MEDIA_NEXT。
根据三层不同的嵌套深度,分别输出日志,意欲判断程序运行时按压耳机按键,程序会执行到哪一步。

尝试1的结果

并不能拦截按键事件,启动App音频后点击耳机”播放“键,直接启动系统音乐播放器,而不是暂停App音频。

尝试2

继续网络搜索,发现需要首先注册/取消注册receiver,故在HeadsetButtonReceiver类中添加如下两个方法:

public void registerHeadsetReceiver(Context context) {    AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);// 另说context.AUDIO_SERVICE    ComponentName name = new ComponentName(context.getPackageName(), HeadsetButtonReceiver.class.getName());// 另说MediaButtonReceiver.class.getName()    audioManager.registerMediaButtonEventReceiver(name);}public void unregisterHeadsetReceiver(Context context){    AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);    ComponentName name = new ComponentName(context.getPackageName(), HeadsetButtonReceiver.class.getName());    audioManager.unregisterMediaButtonEventReceiver(name);}
解释上述代码:
两个方法的功能分别是注册该广播接收器、销毁该广播接收器。
注意到registerHeadsetReceiver()方法中的第一行和第二行的注释,第一行的注释为之前看过某篇文档的写法;第二行注释提醒说ComponentName(参数1,参数2)中参数2是当前广播接收器的类名。

之后在App定义的音频播放类的onCreate方法中调用registerHeadsetReceiver,在该类的onDestroy方法中调用un registerHeadsetReceiver。

尝试2的结果

打开app,按压耳机的”播放“按钮,可以监听到:

03-01 15:08:16.432 11059-11059/com.xxxxx.xxx E/headSet: HeadsetButtonReceiver:onReceive
03-01 15:08:16.432 11059-11059/com.xxxxx.xxx E/headSet: HeadsetButtonReceiver:onReceive:if:

尝试3

因为尝试2的结果只打印出了两个Log,程序不满足第三个if的判断条件。故更新该类(广播接收器)中的onReceive方法如下:

public void onReceive(Context context, Intent intent) {    Log.e("headSet","HeadsetButtonReceiver:"+"onReceive");    if(Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())){        Log.e("headSet","HeadsetButtonReceiver:"+"onReceive:"+"if:");        KeyEvent keyEvent = (KeyEvent) intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);        if(keyEvent.KEYCODE_MEDIA_PLAY == keyEvent.getKeyCode()){            Log.e("headSet","HeadsetButtonReceiver:"+"onReceive:"+"if:"+"if");        }else {            Log.e("headSet","keyEvent="+keyEvent);        }    }}

尝试3的结果

打开app,按压耳机的”播放“按钮,可以监听到:

03-01 15:08:16.432 11059-11059/com.xxxxx.xxx E/headSet: HeadsetButtonReceiver:onReceive
03-01 15:08:16.432 11059-11059/com.xxxxx.xxx E/headSet: HeadsetButtonReceiver:onReceive:if:
03-01 15:08:16.435 11059-11059/com.xxxxx.xxx E/headSet: keyEvent=KeyEvent { action=ACTION_DOWN, keyCode=KEYCODE_HEADSETHOOK, scanCode=164, metaState=0, flags=0x8, repeatCount=0, eventTime=19326753, downTime=19326534, deviceId=4, source=0x101 }
03-01 15:08:16.445 11059-11059/com.xxxxx.xxx E/headSet: HeadsetButtonReceiver:onReceive
03-01 15:08:16.445 11059-11059/com.xxxxx.xxx E/headSet: HeadsetButtonReceiver:onReceive:if:
03-01 15:08:16.445 11059-11059/com.xxxxx.xxx E/headSet: keyEvent=KeyEvent { action=ACTION_UP, keyCode=KEYCODE_HEADSETHOOK, scanCode=164, metaState=0, flags=0x8, repeatCount=0, eventTime=19326753, downTime=19326534, deviceId=4, source=0x101 }

从输出的Log日志可以看到,按压耳机的播放/暂停键时,广播接收器接收到了两个动作,分别是action=ACTION_DOWN和action=ACTION_UP,keyCode均为keyCode=KEYCODE_HEADSETHOOK。故此修改尝试3中的代码,并充实onReceive方法。要实现的功能是播放/暂停,下一曲,上一曲。针对市面上有线耳机按键大多数包括两个+-键,一个功能键,需要处理两种情况:1)+-为音量键,单击功能键实现播放/暂停,双击实现下一曲,三连击实现上一曲。2)+-为下一曲/上一曲键,单击功能键实现播放/暂停。如下:

    @Override    public void onReceive(Context context, Intent intent) {        Log.e("headSet","HeadsetButtonReceiver:"+"onReceive:");        if(Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())) {            Log.e("headSet","HeadsetButtonReceiver:"+"onReceive:"+"if:");            KeyEvent keyEvent = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);            if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_HEADSETHOOK && keyEvent.getAction() == KeyEvent.ACTION_UP) {                Log.e("headSet","HeadsetButtonReceiver:"+"onReceive:"+"if:"+"if"+" HEADSETHOOK");            } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_NEXT) {                Log.e("headSet","HeadsetButtonReceiver:"+"onReceive:"+"if:"+"if"+" KEYCODE_HEADSETHOOK");            } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PREVIOUS) {                Log.e("headSet","HeadsetButtonReceiver:"+"onReceive:"+"if:"+"if"+ " KEYCODE_MEDIA_PREVIOUS");            }        }    }

打开app,按压耳机的”播放“按钮,可以监听到Log: “HeadsetButtonReceiver:onReceive:if:if KEYCODE_HEADSETHOOK”

尝试4

到目前为止,可以收到耳机按键广播,并可进入指定位置。接下来开始实现具体功能,即:三连击中键,实现上一曲;双击中键,实现下一曲;单击中键,实现播放/暂停。要求在1000ms内监听按中键的次数,并将监听按键次数的动作、响应按键次数的事件放到子线程中执行。故丰富onReceive()方法,新建定时器类HeadsetTimerTask,执行定时结束后的响应;新建子线程,由子线程执行具体的响应。

    @Override    public void onReceive(Context context, Intent intent) {        if(Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())) {            KeyEvent keyEvent = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);            if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_HEADSETHOOK && keyEvent.getAction() == KeyEvent.ACTION_UP) {                clickCount = clickCount + 1;                if(clickCount == 1){                    HeadsetTimerTask headsetTimerTask = new HeadsetTimerTask();                    timer.schedule(headsetTimerTask,1000);                }            } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_NEXT) {                handler.sendEmptyMessage(2);            } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PREVIOUS) {                handler.sendEmptyMessage(3);            }        }    }    class HeadsetTimerTask extends TimerTask {        @Override        public void run() {            try{                if(clickCount==1){                    handler.sendEmptyMessage(1);                }else if(clickCount==2){                    handler.sendEmptyMessage(2);                }else if(clickCount>=3){                    handler.sendEmptyMessage(3);                }                clickCount=0;            }catch (Exception e){                e.printStackTrace();            }        }    }    Handler handler = new Handler() {        @Override        public void handleMessage(Message msg) {            super.handleMessage(msg);            try {                if (msg.what == 1) {                    headsetListener.playOrPause();                }else if(msg.what == 2){                    headsetListener.playNext();                }else if(msg.what == 3){                    headsetListener.playPrevious();                }            }catch (Exception e){                e.printStackTrace();            }        }    };    interface onHeadsetListener{        void playOrPause();        void playNext();        void playPrevious();    }    public void setOnHeadsetListener(onHeadsetListener newHeadsetListener){        headsetListener = newHeadsetListener;    }
上述代码解释:
onHeadsetListener是一个接口,内中定义的抽象方法应在对应的音频管理类中实现。

总结

耳机线控一共分为三部分:

第一部分

在AndroidManifest.xml中定义广播接收器,以及需要接收的广播:

".audio.PlayerService.HeadsetButtonReceiver">    "1000">        "android.intent.action.MEDIA_BUTTON" />    

第二部分

实现该广播接收器:

package com.wdbible.app.wedevotebible.audio.PlayerService;import android.content.BroadcastReceiver;import android.content.ComponentName;import android.content.Context;import android.content.Intent;import android.media.AudioManager;import android.os.Handler;import android.os.Message;import android.view.KeyEvent;import java.util.Timer;import java.util.TimerTask;/** * Created by Jerre on 2018/3/1. */public class HeadsetButtonReceiver extends BroadcastReceiver {    private Context context;    private Timer timer = new Timer();    private static int clickCount;    private static onHeadsetListener headsetListener;    public HeadsetButtonReceiver(){        super();    }    public HeadsetButtonReceiver(Context ctx){        super();        context = ctx;        headsetListener = null;        registerHeadsetReceiver();    }    @Override    public void onReceive(Context context, Intent intent) {        if(Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())) {            KeyEvent keyEvent = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);            if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_HEADSETHOOK && keyEvent.getAction() == KeyEvent.ACTION_UP) {                clickCount = clickCount + 1;                if(clickCount == 1){                    HeadsetTimerTask headsetTimerTask = new HeadsetTimerTask();                    timer.schedule(headsetTimerTask,1000);                }            } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_NEXT) {                handler.sendEmptyMessage(2);            } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PREVIOUS) {                handler.sendEmptyMessage(3);            }        }    }    class HeadsetTimerTask extends TimerTask {        @Override        public void run() {            try{                if(clickCount==1){                    handler.sendEmptyMessage(1);                }else if(clickCount==2){                    handler.sendEmptyMessage(2);                }else if(clickCount>=3){                    handler.sendEmptyMessage(3);                }                clickCount=0;            }catch (Exception e){                e.printStackTrace();            }        }    }    Handler handler = new Handler() {        @Override        public void handleMessage(Message msg) {            super.handleMessage(msg);            try {                if (msg.what == 1) {                    headsetListener.playOrPause();                }else if(msg.what == 2){                    headsetListener.playNext();                }else if(msg.what == 3){                    headsetListener.playPrevious();                }            }catch (Exception e){                e.printStackTrace();            }        }    };    interface onHeadsetListener{        void playOrPause();        void playNext();        void playPrevious();    }    public void setOnHeadsetListener(onHeadsetListener newHeadsetListener){        headsetListener = newHeadsetListener;    }    public void registerHeadsetReceiver() {        AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);        ComponentName name = new ComponentName(context.getPackageName(), HeadsetButtonReceiver.class.getName());        audioManager.registerMediaButtonEventReceiver(name);    }    public void unregisterHeadsetReceiver(){        AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);        ComponentName name = new ComponentName(context.getPackageName(), HeadsetButtonReceiver.class.getName());        audioManager.unregisterMediaButtonEventReceiver(name);    }}
第三部分

在音频管理类中的构造方法中新建该广播接收器的实例对象(新建实例对象的过程中已完成该接收器的注册),在该类的onDestory()或者close()方法中注销该广播接收器。

在音频管理类中实现接口onHeadsetListener,并重写其中的抽象方法。

以上。

更多相关文章

  1. Android(安卓)监听系统媒体音量变化
  2. Android音频焦点处理服务
  3. 本地音乐播放器(三)
  4. android在线播放mp4/3gp
  5. Android视频播放器横竖屏自动切换
  6. Android--多媒体
  7. 求教 MediaPlayer 音乐播放器 歌曲循环播放
  8. Android(安卓)利用广播接收器启动服务
  9. android之视频播放控件VideoView简单应用

随机推荐

  1. android开机自启动的后台Service的实现 .
  2. Android(安卓)Game
  3. Android上传图片工具类
  4. java.lang.NoClassDefFoundError: Failed
  5. android 完美获取状态栏高度
  6. android整合--屏幕旋转触发事件
  7. Android获取应用程序的信息
  8. Android自定义属性,format详解
  9. Android笔记【外观部分】
  10. [Android] View动画特效(三)