Android耳机线控-播放/暂停/上一曲/下一曲

起因

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

初始状态

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

期待的状态

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

尝试1

在AndroidManifest.xml 中添加:

<receiver android:name=".audio.PlayerService.HeadsetButtonReceiver">
    <intent-filter android:priority="1000">
        <action android:name="android.intent.action.MEDIA_BUTTON" />
    </intent-filter>
</receiver>
解释上述代码:
许多线控或者无线耳机都会有许多媒体播放控制按钮,例如:播放,停止,暂停,下一曲,上一曲等。无论用户按下设备上任意一个控制按钮,系统都会广播一个带有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中定义广播接收器,以及需要接收的广播:

<receiver android:name=".audio.PlayerService.HeadsetButtonReceiver">
    <intent-filter android:priority="1000">
        <action android:name="android.intent.action.MEDIA_BUTTON" />
    </intent-filter>
</receiver>

第二部分

实现该广播接收器:

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,并重写其中的抽象方法。

以上。

发布了85 篇原创文章 · 获赞 95 · 访问量 22万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览