1. 监听蓝牙设备(音频)连接状态

所有代码已测试在Android11也能正常使用 (Android SDK 30)

首先新建一个广播类 BluetoothStateReceiver

/**
* @author komine
* 监听蓝牙音频设备的连接状态
*/
class BluetoothStateReceiver : BroadcastReceiver(){ override fun onReceive(context: Context?, intent: Intent?) {
when(intent?.action){ //蓝牙已连接,如果不需要判断是否为音频设备,下面代码块的判断可以不要
BluetoothDevice.ACTION_ACL_CONNECTED -> {
val device =
intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
?: return
//如果在Android32 SDK还需要声明 android.permission.BLUETOOTH_CONNECT权限
if (isHeadPhone(device.bluetoothClass)) {
Log.d("BluetoothStateReceiver", "蓝牙音频设备已连接")
}
} //蓝牙状态发生改变,断开,正在扫描,正在连接
BluetoothAdapter.ACTION_STATE_CHANGED -> {
val blueState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0)
if (blueState == BluetoothAdapter.STATE_OFF) {
Log.d("BluetoothStateReceiver", "蓝牙设备已断开")
}
}
}
} companion object{
private const val PROFILE_HEADSET = 0 private const val PROFILE_A2DP = 1 private const val PROFILE_OPP = 2 private const val PROFILE_HID = 3 private const val PROFILE_PANU = 4 private const val PROFILE_NAP = 5 private const val PROFILE_A2DP_SINK = 6
} private fun isHeadPhone(bluetoothClass: BluetoothClass?): Boolean {
if (bluetoothClass == null) {
return false
}
return if (doesClassMatch(bluetoothClass, PROFILE_HEADSET)) true else doesClassMatch(bluetoothClass, PROFILE_A2DP)
} //系统源码拷贝
//可以参考 http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/bluetooth/BluetoothClass.java
private fun doesClassMatch(bluetoothClass: BluetoothClass, profile: Int): Boolean {
return if (profile == PROFILE_A2DP) {
if (bluetoothClass.hasService(BluetoothClass.Service.RENDER)) {
return true
}
when (bluetoothClass.deviceClass) {
BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO, BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES, BluetoothClass.Device.AUDIO_VIDEO_LOUDSPEAKER, BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO -> true
else -> false
}
} else if (profile == PROFILE_A2DP_SINK) {
if (bluetoothClass.hasService(BluetoothClass.Service.CAPTURE)) {
return true
}
when (bluetoothClass.deviceClass) {
BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO, BluetoothClass.Device.AUDIO_VIDEO_SET_TOP_BOX, BluetoothClass.Device.AUDIO_VIDEO_VCR -> true
else -> false
}
} else if (profile == PROFILE_HEADSET) {
// The render service class is required by the spec for HFP, so is a
// pretty good signal
if (bluetoothClass.hasService(BluetoothClass.Service.RENDER)) {
return true
}
when (bluetoothClass.deviceClass) {
BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE, BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET, BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO -> true
else -> false
}
} else if (profile == PROFILE_OPP) {
if (bluetoothClass.hasService(BluetoothClass.Service.OBJECT_TRANSFER)) {
return true
}
when (bluetoothClass.deviceClass) {
BluetoothClass.Device.COMPUTER_UNCATEGORIZED, BluetoothClass.Device.COMPUTER_DESKTOP, BluetoothClass.Device.COMPUTER_SERVER, BluetoothClass.Device.COMPUTER_LAPTOP, BluetoothClass.Device.COMPUTER_HANDHELD_PC_PDA, BluetoothClass.Device.COMPUTER_PALM_SIZE_PC_PDA, BluetoothClass.Device.COMPUTER_WEARABLE, BluetoothClass.Device.PHONE_UNCATEGORIZED, BluetoothClass.Device.PHONE_CELLULAR, BluetoothClass.Device.PHONE_CORDLESS, BluetoothClass.Device.PHONE_SMART, BluetoothClass.Device.PHONE_MODEM_OR_GATEWAY, BluetoothClass.Device.PHONE_ISDN -> true
else -> false
}
} else if (profile == PROFILE_HID) {
bluetoothClass.deviceClass and BluetoothClass.Device.Major.PERIPHERAL == BluetoothClass.Device.Major.PERIPHERAL
} else if (profile == PROFILE_PANU || profile == PROFILE_NAP) {
// No good way to distinguish between the two, based on class bits.
if (bluetoothClass.hasService(BluetoothClass.Service.NETWORKING)) {
true
} else bluetoothClass.deviceClass and BluetoothClass.Device.Major.NETWORKING == BluetoothClass.Device.Major.NETWORKING
} else {
false
}
}
}

然后在 MainActivity中动态注册广播

class MainActivity : AppCompatActivity() {
private val mBluetoothStateReceiver:BluetoothStateReceiver = BluetoothStateReceiver() override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) registerBluetoothReceiver()
} private fun registerBluetoothReceiver(){
val intentFilter = IntentFilter()
intentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED)
intentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED)
intentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED)
intentFilter.addAction("android.bluetooth.BluetoothAdapter.STATE_OFF")
intentFilter.addAction("android.bluetooth.BluetoothAdapter.STATE_ON") registerReceiver(mBluetoothStateReceiver,intentFilter)
}
}

最后在 AndroidManifest.xml声明权限

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

记得取消注册广播,不然打开其他音乐应用的时候,程序可能会crash

2. 监听蓝牙设备键按下

####################################2022-04-28更新############################

1.疑似Bug,需要在初始化MediaPlayer之后再注册才会生效,MediaButtonReceiver(applicationContext,this)放在MeidePlayer初始化之后代码

2.MediaSession只能有一个实例,如果在程序中创建了多个实例也会导致接收不到,其实看它命名也应该知道了...

####################################2022-04-28更新############################

要监听蓝牙设备的按键按下,你的应用需要处于音乐播放的状态,比如应用正在使用 MediaPlayer播放音频,

如果未处于音频播放状态是不会触发回调方法.所以在演示代码中添加了MediaPlayer来播放音频.

首先新建一个类MediaButtonReceiver类

class MediaButtonReceiver(mContent: Context, mKeyDownListener: IKeyDownListener) {

    //创建一个MediaSession的实例,参数2是一个字符串,可以随便填
private val mMediaSession = MediaSession(mContent, javaClass.name) internal annotation class KeyActions {
companion object {
//好像还支持手柄按键...
//所有keyCode参考:https://www.apiref.com/android-zh/android/view/KeyEvent.html
var PLAY_ACTION: Int = 126
var PAUSE_ACTION: Int = 127
var PREV_ACTION: Int = 88
var NEXT_ACTION: Int = 87
}
} init {
mMediaSession.setCallback(object : MediaSession.Callback() {
override fun onMediaButtonEvent(mediaButtonIntent: Intent): Boolean {
val keyEvent: KeyEvent =
mediaButtonIntent.getParcelableExtra(Intent.EXTRA_KEY_EVENT)
?: return false mKeyDownListener.onKeyDown(keyEvent.keyCode) //返回值的作用跟事件分发的原理是一样的,返回true代表事件被消费,其他应用也就收不到了
return true
}
})
mMediaSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS or MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS)
mMediaSession.isActive = true
} interface IKeyDownListener {
fun onKeyDown(@KeyActions keyAction: Int)
}
}

然后在 MainActivity添加播放的代码

class MainActivity : AppCompatActivity(),MediaButtonReceiver.IKeyDownListener {
private val tag = javaClass.simpleName
private val mMediaPlay:MediaPlayer = MediaPlayer() override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) mMediaPlay.reset()
val audioAttributesBuilder = AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
mMediaPlay.setAudioAttributes(audioAttributesBuilder.build())
//注意文件读写权限
mMediaPlay.setDataSource(Environment.getExternalStorageDirectory().toString() + File.separator + "1.ape")
mMediaPlay.setOnPreparedListener{
mMediaPlay.start()
MediaButtonReceiver(applicationContext,this)
}
mMediaPlay.prepareAsync()
} override fun onKeyDown(keyAction: Int) {
when(keyAction){
MediaButtonReceiver.KeyActions.PLAY_ACTION -> Log.d(tag,"播放...")
MediaButtonReceiver.KeyActions.PAUSE_ACTION -> Log.d(tag,"暂停...")
MediaButtonReceiver.KeyActions.PREV_ACTION -> Log.d(tag,"上一首...")
MediaButtonReceiver.KeyActions.NEXT_ACTION -> Log.d(tag,"下一首...")
}
}
}

打印结果

参考:

Android 扫描蓝牙设备并获取设备类型

ボタンイベントの受信と処理

Android蓝牙线控切歌、连接状态监听(无线耳机也适用)的更多相关文章

  1. Android耳机线控具体解释,蓝牙耳机button监听(仿酷狗线控效果)

    转载请注明出处:http://blog.csdn.net/fengyuzhengfan/article/details/46461253 当耳机的媒体按键被单击后.Android系统会发出一个广播.该 ...

  2. MTK Android 耳机线控的实现方法

    android 耳机线控的实现方法 keycodeonkeydownkeyevent 耳机线控的功能 耳机线控是一种很好用,并且能提升用户体验的功能.可以用来实现一些常用和基本的功能.比如:实现音乐播 ...

  3. ZT android -- 蓝牙 bluetooth (五)接电话与听音乐

    android -- 蓝牙 bluetooth (五)接电话与听音乐 分类: Android的原生应用分析 2013-07-13 20:53 2165人阅读 评论(9) 收藏 举报 蓝牙android ...

  4. Android USB大容量存储时SD卡状态监听(转)

    对SD卡状态监听,到现在为止我知道的有两种方式: 1.注册StorageEventListener来监听sd卡状态 StorageEventListener中有onStorageStateChange ...

  5. 背水一战 Windows 10 (66) - 控件(WebView): 监听和处理 WebView 的事件

    [源码下载] 背水一战 Windows 10 (66) - 控件(WebView): 监听和处理 WebView 的事件 作者:webabcd 介绍背水一战 Windows 10 之 控件(WebVi ...

  6. Android开发之手势滑动(滑动手势监听)详解

    Android开发之手势滑动(滑动手势监听)详解 在Android应用中,经常需要手势滑动操作,比如上下滑动,或左右方向滑动,处理手势滑动通常有两种方法:一种是单独实现setOnTouchListen ...

  7. Android软键盘的隐藏显示、事件监听的代码

    把开发过程中重要的一些内容片段做个珍藏,如下资料是关于Android软键盘的隐藏显示.事件监听的内容,应该是对小伙伴们有所用途. public class ResizeLayout extends L ...

  8. 截屏状态监听 - iOS

    既接到电话状态监听的需求之后再次添加了截屏状态的监听,当使用 App 时若用户执行截屏操作需要对当前状态进行监听操作,下面有两种方法,其中可以替换截屏的图片内容(Plan A),也可以弹出提示框(Pl ...

  9. Android TV开发中所有的遥控器按键监听及注意事项,新增home键监听

    原文:Android TV开发中所有的遥控器按键监听及注意事项,新增home键监听 简单记录下android 盒子开发遥控器的监听 ,希望能帮到新入门的朋友们 不多说,直接贴代码 public cla ...

随机推荐

  1. 记一次学习kibaba踩过的坑(Windows环境)

    下载地址 ElasticSearch:https://www.elastic.co/cn/downloads/elasticsearchLogstash:https://www.elastic.co/ ...

  2. 如何从0开发一个Vue组件库并发布到npm

    1.新建文件夹在终端打开执行 npm init -y 生成package.json如下,注意如果要发布到npm,name不能有下划线,大写字母等 { "name": "v ...

  3. 【摸鱼神器】UI库秒变低代码工具——表单篇(一)设计

    前面说了列表的低代码化的方法,本篇介绍一下表单的低代码化. 内容摘要 需求分析. 定义 interface. 定义表单控件的 props. 定义 json 文件. 基于 el-form 封装,实现依赖 ...

  4. JSON: JavaScript Object Notation

    JSON是JavaScript Object Notation 的缩写,是JS提供的一种数据交换格式.1) JSON对象本质上就是一个JS对象,但是这个对象比较特殊,它可以直接转换为字符串,在不同语言 ...

  5. 记录一下第一次在CodeForces供题的事(未完待续)

    3月11日 因为想出题而开始打比赛上分 (Rating 1727) (期间最低掉到 1669) 6月4日凌晨 上分,有了权限 (Rating 2141) 6月4-6日 出了七道题 6月8-12日 又出 ...

  6. OpenCV视频防抖技术解析

    视频防抖有很多种技术,各有优劣,主流的目前分为三种:EIS电子防抖EIS电子防抖是通过软件算法实现防抖的.其技术运作原理是通过加速度传感器和陀螺仪模块侦测手机抖动的幅度,从而来动态调节整ISO.快门以 ...

  7. while练习题_1到100之间的偶数和

    依然是while循环四步骤 初始化变量 条件判断 条件执行体 最后就是输出答案就可以了 点击查看笔者代码 a = 1 sum = 0 while a <= 100: if (a+1)%2:#if ...

  8. 论文解读(soft-mask GNN)《Soft-mask: Adaptive Substructure Extractions for Graph Neural Networks》

    论文信息 论文标题:Soft-mask: Adaptive Substructure Extractions for Graph Neural Networks论文作者:Mingqi Yang, Ya ...

  9. 使用.NET简单实现一个Redis的高性能克隆版(七-完结)

    译者注 该原文是Ayende Rahien大佬业余自己在使用C# 和 .NET构建一个简单.高性能兼容Redis协议的数据库的经历. 首先这个"Redis"是非常简单的实现,但是他 ...

  10. Apache DolphinScheduler 1.3.6 功能发布说明

    参与人员 @chengshiwen.@hailin0.@wanghong1314.@ruanwenjun.@xxjingcd.@zhangguohao.@zhuangchong.@syb8535531 ...