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

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

首先新建一个广播类 BluetoothStateReceiver

  1. /**
  2. * @author komine
  3. * 监听蓝牙音频设备的连接状态
  4. */
  5. class BluetoothStateReceiver : BroadcastReceiver(){
  6. override fun onReceive(context: Context?, intent: Intent?) {
  7. when(intent?.action){
  8. //蓝牙已连接,如果不需要判断是否为音频设备,下面代码块的判断可以不要
  9. BluetoothDevice.ACTION_ACL_CONNECTED -> {
  10. val device =
  11. intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
  12. ?: return
  13. //如果在Android32 SDK还需要声明 android.permission.BLUETOOTH_CONNECT权限
  14. if (isHeadPhone(device.bluetoothClass)) {
  15. Log.d("BluetoothStateReceiver", "蓝牙音频设备已连接")
  16. }
  17. }
  18. //蓝牙状态发生改变,断开,正在扫描,正在连接
  19. BluetoothAdapter.ACTION_STATE_CHANGED -> {
  20. val blueState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0)
  21. if (blueState == BluetoothAdapter.STATE_OFF) {
  22. Log.d("BluetoothStateReceiver", "蓝牙设备已断开")
  23. }
  24. }
  25. }
  26. }
  27. companion object{
  28. private const val PROFILE_HEADSET = 0
  29. private const val PROFILE_A2DP = 1
  30. private const val PROFILE_OPP = 2
  31. private const val PROFILE_HID = 3
  32. private const val PROFILE_PANU = 4
  33. private const val PROFILE_NAP = 5
  34. private const val PROFILE_A2DP_SINK = 6
  35. }
  36. private fun isHeadPhone(bluetoothClass: BluetoothClass?): Boolean {
  37. if (bluetoothClass == null) {
  38. return false
  39. }
  40. return if (doesClassMatch(bluetoothClass, PROFILE_HEADSET)) true else doesClassMatch(bluetoothClass, PROFILE_A2DP)
  41. }
  42. //系统源码拷贝
  43. //可以参考 http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/bluetooth/BluetoothClass.java
  44. private fun doesClassMatch(bluetoothClass: BluetoothClass, profile: Int): Boolean {
  45. return if (profile == PROFILE_A2DP) {
  46. if (bluetoothClass.hasService(BluetoothClass.Service.RENDER)) {
  47. return true
  48. }
  49. when (bluetoothClass.deviceClass) {
  50. BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO, BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES, BluetoothClass.Device.AUDIO_VIDEO_LOUDSPEAKER, BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO -> true
  51. else -> false
  52. }
  53. } else if (profile == PROFILE_A2DP_SINK) {
  54. if (bluetoothClass.hasService(BluetoothClass.Service.CAPTURE)) {
  55. return true
  56. }
  57. when (bluetoothClass.deviceClass) {
  58. BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO, BluetoothClass.Device.AUDIO_VIDEO_SET_TOP_BOX, BluetoothClass.Device.AUDIO_VIDEO_VCR -> true
  59. else -> false
  60. }
  61. } else if (profile == PROFILE_HEADSET) {
  62. // The render service class is required by the spec for HFP, so is a
  63. // pretty good signal
  64. if (bluetoothClass.hasService(BluetoothClass.Service.RENDER)) {
  65. return true
  66. }
  67. when (bluetoothClass.deviceClass) {
  68. BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE, BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET, BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO -> true
  69. else -> false
  70. }
  71. } else if (profile == PROFILE_OPP) {
  72. if (bluetoothClass.hasService(BluetoothClass.Service.OBJECT_TRANSFER)) {
  73. return true
  74. }
  75. when (bluetoothClass.deviceClass) {
  76. 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
  77. else -> false
  78. }
  79. } else if (profile == PROFILE_HID) {
  80. bluetoothClass.deviceClass and BluetoothClass.Device.Major.PERIPHERAL == BluetoothClass.Device.Major.PERIPHERAL
  81. } else if (profile == PROFILE_PANU || profile == PROFILE_NAP) {
  82. // No good way to distinguish between the two, based on class bits.
  83. if (bluetoothClass.hasService(BluetoothClass.Service.NETWORKING)) {
  84. true
  85. } else bluetoothClass.deviceClass and BluetoothClass.Device.Major.NETWORKING == BluetoothClass.Device.Major.NETWORKING
  86. } else {
  87. false
  88. }
  89. }
  90. }

然后在 MainActivity中动态注册广播

  1. class MainActivity : AppCompatActivity() {
  2. private val mBluetoothStateReceiver:BluetoothStateReceiver = BluetoothStateReceiver()
  3. override fun onCreate(savedInstanceState: Bundle?) {
  4. super.onCreate(savedInstanceState)
  5. setContentView(R.layout.activity_main)
  6. registerBluetoothReceiver()
  7. }
  8. private fun registerBluetoothReceiver(){
  9. val intentFilter = IntentFilter()
  10. intentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED)
  11. intentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED)
  12. intentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED)
  13. intentFilter.addAction("android.bluetooth.BluetoothAdapter.STATE_OFF")
  14. intentFilter.addAction("android.bluetooth.BluetoothAdapter.STATE_ON")
  15. registerReceiver(mBluetoothStateReceiver,intentFilter)
  16. }
  17. }

最后在 AndroidManifest.xml声明权限

  1. <uses-permission android:name="android.permission.BLUETOOTH"/>
  2. <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类

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

然后在 MainActivity添加播放的代码

  1. class MainActivity : AppCompatActivity(),MediaButtonReceiver.IKeyDownListener {
  2. private val tag = javaClass.simpleName
  3. private val mMediaPlay:MediaPlayer = MediaPlayer()
  4. override fun onCreate(savedInstanceState: Bundle?) {
  5. super.onCreate(savedInstanceState)
  6. setContentView(R.layout.activity_main)
  7. mMediaPlay.reset()
  8. val audioAttributesBuilder = AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
  9. mMediaPlay.setAudioAttributes(audioAttributesBuilder.build())
  10. //注意文件读写权限
  11. mMediaPlay.setDataSource(Environment.getExternalStorageDirectory().toString() + File.separator + "1.ape")
  12. mMediaPlay.setOnPreparedListener{
  13. mMediaPlay.start()
  14. MediaButtonReceiver(applicationContext,this)
  15. }
  16. mMediaPlay.prepareAsync()
  17. }
  18. override fun onKeyDown(keyAction: Int) {
  19. when(keyAction){
  20. MediaButtonReceiver.KeyActions.PLAY_ACTION -> Log.d(tag,"播放...")
  21. MediaButtonReceiver.KeyActions.PAUSE_ACTION -> Log.d(tag,"暂停...")
  22. MediaButtonReceiver.KeyActions.PREV_ACTION -> Log.d(tag,"上一首...")
  23. MediaButtonReceiver.KeyActions.NEXT_ACTION -> Log.d(tag,"下一首...")
  24. }
  25. }
  26. }

打印结果

参考:

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. 二、shell 脚本条件测试

    目录 一.条件测试 test 格式 文件测试 文件测试常见选项 整数值比较 字符串比较 浮点数的运算 逻辑测试 二.if语句 1单分支 2双分支结构 3多分支结构 三元运算符 三.case 一.条件测 ...

  2. 细说GaussDB(DWS)复杂多样的资源负载管理手段

    摘要:对于如此多的管控功能,管控起来实际的效果到底如何,本篇文章就基于当前最新版本,进行效果实测,并进行一定的分析说明. 本文分享自华为云社区<GaussDB(DWS) 资源负载管理:并发管控以 ...

  3. 理解 Python 的 for 循环

    在本篇博客中,我们将讨论 Python 中 for 循环的原理. 我们将从一组基本例子和它的语法开始,还将讨论与 for 循环关联的 else 代码块的用处. 然后我们将介绍迭代对象.迭代器和迭代器协 ...

  4. Nacos开机自启

    1.添加nacos.service文件 vi /lib/systemd/system/nacos.service 2.将以下内容写到nacos.service文件中 ps:我的nacos路径是/usr ...

  5. NC17400 gpa

    NC17400 gpa 题目 题目描述 Kanade selected n courses in the university. The academic credit of the i-th cou ...

  6. CentOS查看操作系统安装时间信息:

    CentOS查看系统安装时间信息: 方法1:[root@logserver ~]#  ll /boot/|egrep -i "(grub|lost\+found)" 方法2:[ro ...

  7. 综合案例_文件搜索和FileFilter过滤器的原理和使用

    文件搜索 需求 : 遍历D:\aaa文件夹,及 aaa 文件夹的子文件夹并且只要.java结尾的文件 分析: 1.目录搜索,无法判断多少级目录,所以使用递归,遍历所有目录 2.遍历目录时,获取的子文件 ...

  8. MySQL--创建计算字段

    存储在数据库表中的数据一般不是应用程序所需要的格式.下面举几个例子.  如果想在一个字段中既显示公司名,又显示公司的地址,但这两个信息一般包含在不同的表列中.  城市.州和邮政编码存储在不同的列中 ...

  9. Java开发学习(十三)----基于注解开发定义第三方bean及注解开发总结

    在前面的博客中定义bean的时候都是在自己开发的类上面写个注解就完成了,但如果是第三方的类,这些类都是在jar包中,我们没有办法在类上面添加注解,这个时候该怎么办? 遇到上述问题,我们就需要有一种更加 ...

  10. Python网页解析库:用requests-html爬取网页

    Python网页解析库:用requests-html爬取网页 1. 开始 Python 中可以进行网页解析的库有很多,常见的有 BeautifulSoup 和 lxml 等.在网上玩爬虫的文章通常都是 ...