Files

748 lines
23 KiB
Markdown
Raw Permalink Normal View History

2026-11-05 14:35:04 +08:00
# 蓝牙音乐模块功能流程梳理
更新时间:2026-07-13
工程路径:`F:\zdCode\tongli`
当前蓝牙音乐提交:`8ec79048 modify(YX_BlueTooth):新增蓝牙音乐功能`
## 1. 一句话结论
蓝牙音乐模块不是本地音乐播放器,而是 **车机作为 A2DP Sink 接收手机音频 + 作为 AVRCP Controller 控制手机播放器**。
也就是说:
- 音频从手机通过蓝牙 A2DP 传到车机。
- 播放、暂停、上一首、下一首通过 AVRCP 控制手机端播放器。
- 歌名、歌手、播放进度、总时长来自手机蓝牙媒体会话回调。
- 当前 UI 按老项目效果固定显示默认蓝牙音乐封面,不加载手机专辑图。
## 2. 模块边界
### 2.1 负责的功能
| 功能 | 当前实现 |
|---|---|
| 蓝牙音乐入口 | 主界面左侧“蓝牙音乐”菜单进入 `BtMusicFragment` |
| 独立音乐页 | `BluetoothMusicActivity` 已注册,但当前主要入口是主界面 Fragment |
| A2DP 状态 | 判断 A2DP 是否连接,未连接时显示连接提示 |
| A2DP 自动连接 | 已有手机设备且 A2DP 未连接时,尝试连接 A2DP |
| 播放控制 | 播放、暂停、上一首、下一首 |
| 歌曲信息 | 歌名、歌手、当前时间、总时长、进度 |
| 音频焦点 | 播放前申请蓝牙音乐焦点,焦点丢失时暂停/静音 |
| UI 对齐 | 已按老项目蓝牙音乐页 UI 对齐 |
### 2.2 不负责的功能
| 不负责项 | 说明 |
|---|---|
| 本地音乐扫描 | 不扫描 U 盘、本地文件或歌单 |
| 本地解码播放 | 不直接解码 mp3/flac 等媒体文件 |
| 手机歌单浏览 | 当前只做基础播放控制,不展示手机媒体库列表 |
| 专辑封面展示 | 当前按老项目固定显示默认蓝牙音乐图 |
| 蓝牙配对搜索完整流程 | 属于蓝牙设置模块 |
## 3. 关键代码入口
| 层级 | 文件 | 作用 |
|---|---|---|
| 主入口 | `YX_BlueTooth/src/main/java/com/yaxon/bluetooth/main/MainActivity.java` | 左侧菜单切换到蓝牙音乐页 |
| Fragment UI | `YX_BlueTooth/src/main/java/com/yaxon/bluetooth/fragment/BtMusicFragment.java` | 主界面内的蓝牙音乐页面 |
| 独立 Activity | `YX_BlueTooth/src/main/java/com/yaxon/bluetooth/main/BluetoothMusicActivity.java` | 独立蓝牙音乐页面 |
| ViewModel | `YX_BlueTooth/src/main/java/com/yaxon/bluetooth/viewmodel/BtMusicViewModel.java` | UI 与 SDK 之间的桥接层 |
| 控制门面 | `YX_BluetoothSDK/src/main/java/com/yaxon/bluetoothsdk/manager/BtMusicControlManager.java` | 对外提供蓝牙音乐控制 API |
| 音乐管理 | `YX_BluetoothSDK/src/main/java/com/yaxon/bluetoothsdk/modules/BtMusicManager.java` | 处理 AVRCP、A2DP、回调分发 |
| AVRCP 客户端 | `YX_BluetoothSDK/src/main/java/com/yaxon/bluetoothsdk/platform/BluetoothAvrcpClient.java` | 连接系统蓝牙媒体服务,发送播放控制 |
| MediaBrowser | `YX_BluetoothSDK/src/main/java/com/yaxon/bluetoothsdk/platform/MediaBrowserClient.java` | 连接 `com.android.bluetooth.avrcpcontroller.BluetoothMediaBrowserService` |
| 音频焦点 | `YX_BluetoothSDK/src/main/java/com/yaxon/bluetoothsdk/audio/BtMusicAudioManager.java` | 申请/释放音频焦点,处理焦点变化 |
| A2DP 设置 | `YX_BluetoothSDK/src/main/java/com/yaxon/bluetoothsdk/manager/BtSettingControlManager.java` | 判断/连接 A2DP |
## 4. 总体架构流程
这一节先看分层,再看两条主链路:**控制链路** 和 **回调链路**。这样比一张横向大图更清楚。
### 4.1 分层架构图
图里只保留层级关系,详细类名放到下方表格,避免图过大。
```mermaid
flowchart TB
A["页面层"]
B["ViewModel"]
C["SDK 管理层"]
D["系统蓝牙服务"]
E["手机端"]
A --> B
B --> C
C --> D
D --> E
```
| 层级 | 关键类 | 作用 |
|---|---|---|
| UI | `BtMusicFragment` / `BluetoothMusicActivity` | 显示界面,接收按钮点击 |
| ViewModel | `BtMusicViewModel` | 连接 UI 和 SDK,分发 LiveData |
| SDK 控制 | `BtMusicControlManager` / `BtSettingControlManager` | 对外提供播放控制、A2DP 连接接口 |
| 业务管理 | `BtMusicManager` | 组织 AVRCP、A2DP、焦点和回调 |
| AVRCP 控制 | `BluetoothAvrcpClient` / `MediaBrowserClient` | 控制手机播放器,接收媒体状态 |
| A2DP 音频 | `BluetoothA2dpSinkClient` | 接收手机蓝牙音频 |
| 音频焦点 | `BtMusicAudioManager` / `AudioManager` | 申请焦点、静音/解除静音、处理焦点丢失 |
| 系统蓝牙 | `BluetoothMediaBrowserService` / A2DP Sink Service | Android 蓝牙系统服务 |
| 手机 | 手机音乐播放器 / 手机蓝牙音频 | 真正播放音乐并输出 A2DP 音频 |
SDK 管理层内部再分三条线:
| 内部链路 | 关键类 | 解决的问题 |
|---|---|---|
| AVRCP 控制 | `BtMusicManager` / `BluetoothAvrcpClient` / `MediaBrowserClient` | 播放、暂停、上一首、下一首 |
| A2DP 音频 | `BtSettingControlManager` / `BluetoothA2dpSinkClient` | 建立手机到车机的音频通道 |
| 音频焦点 | `BtMusicAudioManager` / `AudioManager` | 决定蓝牙音乐能不能出声、是否要暂停或静音 |
### 4.2 播放控制链路
用户点按钮时,命令从上往下走,最后控制手机播放器。
```mermaid
flowchart TB
A["用户操作"]
B["页面处理"]
C["ViewModel 转发"]
D["SDK 控制"]
E["系统媒体控制"]
F["手机播放器"]
A --> B
B --> C
C --> D
D --> E
E --> F
```
| 步骤 | 代码定位 | 说明 |
|---|---|---|
| 用户操作 | `iv_bt_music_state` / `iv_bt_music_pre` / `iv_bt_music_next` | 用户点击播放控制按钮 |
| 页面处理 | `BtMusicFragment.onClick` / `BluetoothMusicActivity.onClick` | 判断操作类型 |
| ViewModel 转发 | `BtMusicViewModel.playBtMusic / pauseBtMusic / preBtMusic / nextBtMusic` | UI 不直接碰 SDK |
| SDK 控制 | `BtMusicControlManager` / `BtMusicManager` | 申请焦点并组织 AVRCP 命令 |
| 系统媒体控制 | `BluetoothAvrcpClient` / `MediaBrowserClient` / `TransportControls` | 调用系统蓝牙媒体控制 |
| 手机播放器 | 手机音乐 App | 最终执行播放、暂停、切歌 |
### 4.3 状态回调链路
手机端播放状态变化后,状态从下往上回到 UI。
```mermaid
flowchart TB
A["手机播放器"]
B["系统媒体服务"]
C["SDK 回调处理"]
D["ViewModel LiveData"]
E["页面刷新"]
A --> B
B --> C
C --> D
D --> E
```
| 步骤 | 代码定位 | 说明 |
|---|---|---|
| 手机播放器 | 手机音乐 App | 歌曲、进度、播放状态变化 |
| 系统媒体服务 | `BluetoothMediaBrowserService` | Android 蓝牙 AVRCP Controller 媒体服务 |
| SDK 回调处理 | `MediaBrowserClient` / `BluetoothAvrcpClient` / `BtMusicManager` | 接收并转换媒体状态 |
| ViewModel LiveData | `BtMusicViewModel.mLiveDataMusicInfo` / `mLiveDataPlayState` | 把状态推给 UI |
| 页面刷新 | `BtMusicFragment` / `BluetoothMusicActivity` | 刷新按钮、歌名、时间、进度 |
### 4.4 A2DP 与音频焦点链路
这条链路决定车机是否能真正出声。
先看 A2DP 音频通道:
```mermaid
flowchart TB
A["进入音乐页"]
B["检查 A2DP"]
C["连接手机 A2DP"]
D["音频通道就绪"]
E["车机扬声器输出"]
A --> B
B --> C
C --> D
D --> E
```
再看音频焦点:
```mermaid
flowchart TB
A["点击播放"]
B["申请音频焦点"]
C["解除 A2DP 静音"]
D["发送播放命令"]
E["手机开始播放"]
A --> B
B --> C
C --> D
D --> E
```
| 链路 | 成功条件 | 失败表现 |
|---|---|---|
| A2DP 音频通道 | 手机 A2DP 已连接或连接成功 | UI 保持未连接,车机无蓝牙音乐声 |
| 音频焦点 | `requestAudioFocus` 成功 | 手机可能在播,但车机可能无声或被其他音源压住 |
| AVRCP 控制 | `TransportControls` 可用 | 播放/暂停/切歌命令可能无效 |
核心理解:
- UI 不直接操作系统蓝牙。
- UI 只调用 `BtMusicViewModel`。
- `BtMusicViewModel` 调 `BtMusicControlManager` 和 `BtSettingControlManager`。
- 真正播放控制走 `BluetoothAvrcpClient -> MediaBrowserClient -> TransportControls`。
- 真正音频通道走 A2DP Sink。
- 真正能不能出声,还取决于 A2DP 是否连接和音频焦点是否申请成功。
## 5. 初始化流程
蓝牙 SDK 初始化在应用启动时完成。
```mermaid
flowchart TD
A["应用启动"]
B["初始化蓝牙 SDK"]
C["创建音乐管理器"]
D["准备控制与音频能力"]
E["注册媒体回调"]
A --> B
B --> C
C --> D
D --> E
```
涉及文件:
- `BluetoothApplication.java`
- `BluetoothSdkApplication.java`
- `BtMusicControlManager.java`
- `BtMusicManager.java`
- `BluetoothAvrcpClient.java`
- `MediaBrowserClient.java`
- `BtMusicAudioManager.java`
初始化细节:
| 步骤 | 代码定位 | 结果 |
|---|---|---|
| 应用启动 | `BluetoothApplication` | 进入蓝牙 SDK 应用基类 |
| 初始化蓝牙 SDK | `BluetoothSdkApplication.setPlatform` | 初始化各蓝牙业务模块 |
| 创建音乐管理器 | `BtMusicControlManager.init` | 持有 `BtMusicManager` |
| 准备控制与音频能力 | `BluetoothAvrcpClient` / `BtMusicAudioManager` / `BluetoothA2dpSinkClient` | 准备 AVRCP、焦点、A2DP |
| 注册媒体回调 | `MediaBrowserClient` | 连接系统媒体服务并监听状态 |
## 6. 入口切换流程
主界面左侧菜单有四个入口:
| 位置 | 入口 |
|---|---|
| 0 | 蓝牙拨号 |
| 1 | 联系人 |
| 2 | 蓝牙音乐 |
| 3 | 蓝牙设置 |
`Constant.TAB_BLUETOOTH_MUSIC = 2`。
```mermaid
flowchart TD
A["点击蓝牙音乐"]
B["切换菜单位置"]
C["更新左侧选中态"]
D["切到音乐 Fragment"]
E["显示音乐页面"]
A --> B
B --> C
C --> D
D --> E
```
关键代码:
- `MainActivity.setPositionFragment`
- `Constant.TAB_BLUETOOTH_MUSIC`
- `BtMusicFragment`
入口代码定位:
| 步骤 | 代码定位 | 说明 |
|---|---|---|
| 点击蓝牙音乐 | `MainActivity.onItemClick` | 左侧菜单点击 |
| 切换菜单位置 | `setPositionFragment(position = 2)` | `2` 对应蓝牙音乐 |
| 更新左侧选中态 | `mMenuAdapter.setSelectedPosition` | 菜单高亮 |
| 切到音乐 Fragment | `toFragment(mBtMusicFragment)` | 页面容器替换 |
| 显示音乐页面 | `fragment_bt_music.xml` | 展示蓝牙音乐 UI |
## 7. 页面 UI 状态流程
蓝牙音乐 UI 由 `isBtConnect` 控制两种状态。
```mermaid
flowchart TD
A["进入音乐页"]
B{"A2DP 已连接"}
C["音乐控制面板"]
D["未连接提示"]
A --> B
B -->|是| C
B -->|否| D
```
UI 文件:
- `fragment_bt_music.xml`
- `activity_bluetooth_music.xml`
主要控件:
| 控件 ID | 作用 |
|---|---|
| `iv_bt_music_album` | 默认蓝牙音乐封面 |
| `tv_bt_music_title` | 歌曲名 |
| `tv_bt_music_artist` | 歌手 |
| `seek_bt_music` | 播放进度展示,不允许拖动 |
| `tv_cur_time` | 当前播放时间 |
| `tv_song_time` | 总时长 |
| `iv_bt_music_pre` | 上一首 |
| `iv_bt_music_state` | 播放 / 暂停 |
| `iv_bt_music_next` | 下一首 |
| `ll_bt_connect_panel` | 未连接提示页 |
| `tv_connect_btn` | 连接蓝牙按钮,目前未实现跳转 |
状态对应关系:
| 状态 | 显示内容 | 触发条件 |
|---|---|---|
| 音乐控制面板 | 默认封面、歌名、歌手、进度条、三个控制按钮 | `isA2dpConnected == true` |
| 未连接提示 | 连接手机提示、连接蓝牙按钮 | `isA2dpConnected == false` |
## 8. A2DP 连接流程
A2DP 是手机音乐音频传到车机的通道。
进入页面后的检查:
```mermaid
flowchart TD
A["进入音乐页"]
B["检查 A2DP"]
C["获取手机设备"]
D["发起 A2DP 连接"]
E["等待连接结果"]
A --> B
B --> C
C --> D
D --> E
```
分支规则:
| 判断点 | 是 | 否 |
|---|---|---|
| A2DP 是否已连接 | 显示音乐控制面板 | 继续获取已连接手机设备 |
| 已连接设备是否为手机 | 检查 A2DP 是否正在连接 | 保持未连接状态 |
| A2DP 是否正在连接 | 等待连接结果 | 调用 `connectA2dp(mac)` |
调用定位:
| 步骤 | 代码定位 |
|---|---|
| 进入音乐页 | `BtMusicFragment.onResume` / `BluetoothMusicActivity.onResume` |
| 检查 A2DP | `BtMusicViewModel.isA2dpConnected` |
| 获取手机设备 | `BtMusicFragment.btMusicInfo` / `BluetoothMusicActivity.btMusicInfo` |
| 发起连接 | `BtMusicViewModel.connectA2dp` |
| 底层连接 | `BtSettingControlManager.connectA2dp` / `BluetoothA2dpSinkClient` |
触发位置:
- `BtMusicFragment.btMusicInfo`
- `BluetoothMusicActivity.btMusicInfo`
- `BtMusicViewModel.connectA2dp`
- `BtSettingControlManager.connectA2dp`
连接结果回调:
```mermaid
flowchart TD
A["A2DP 状态变化"]
B["SDK 连接监听"]
C["音乐管理器处理"]
D["LiveData 通知"]
E["UI 切换状态"]
A --> B
B --> C
C --> D
D --> E
```
| 步骤 | 代码定位 | 说明 |
|---|---|---|
| A2DP 状态变化 | 系统蓝牙服务 | A2DP 连接或断开 |
| SDK 连接监听 | `BluetoothConnectListenerClient` | 接收系统连接状态 |
| 音乐管理器处理 | `BtMusicManager.onA2dpConnect / onA2dpDisconnect` | 清理或恢复蓝牙音乐状态 |
| LiveData 通知 | `BtMusicViewModel.mLiveDataA2dpConnectStatus` | 通知 UI |
| UI 切换状态 | `BtMusicFragment` / `BluetoothMusicActivity` | 显示音乐页或未连接页 |
## 9. 播放控制流程
### 9.1 点击播放 / 暂停
```mermaid
flowchart TD
A["点击按钮"]
B{"当前在播放"}
C["走暂停链路"]
D["走播放链路"]
E["手机播放器响应"]
A --> B
B -->|是| C
B -->|否| D
C --> E
D --> E
```
| 分支 | 调用顺序 | 说明 |
|---|---|---|
| 播放 | `onClick -> isMusicPlaying == false -> playBtMusic -> requestFocus -> AVRCP play` | 先拿焦点,再发送播放 |
| 暂停 | `onClick -> isMusicPlaying == true -> pauseBtMusic -> AVRCP pause` | 暂停不需要重新申请焦点 |
| UI 更新 | `mLiveDataPlayState -> updatePlayState` | 最终以回调状态刷新图标 |
### 9.2 点击上一首 / 下一首
```mermaid
flowchart TD
A["点击切歌"]
B["ViewModel 转发"]
C["SDK 申请焦点"]
D["发送 AVRCP 命令"]
E["手机播放器切歌"]
A --> B
B --> C
C --> D
D --> E
```
底层实际命令:
| 操作 | 页面入口 | ViewModel | 最终调用 |
|---|---|---|---|
| 播放 | `onClick` | `playBtMusic` | `TransportControls.play()` |
| 暂停 | `onClick` | `pauseBtMusic` | `TransportControls.pause()` |
| 下一首 | `onClick` | `nextBtMusic` | `TransportControls.skipToNext()` |
| 上一首 | `onClick` | `preBtMusic` | `TransportControls.skipToPrevious()` |
## 10. 歌曲信息与播放状态刷新流程
手机通过系统蓝牙 MediaBrowser 回传媒体状态。
播放状态回调:
```mermaid
flowchart TD
A["手机播放状态变化"]
B["系统媒体回调"]
C["SDK 转换状态"]
D["LiveData 通知"]
E["刷新按钮和进度"]
A --> B
B --> C
C --> D
D --> E
```
歌曲信息回调:
```mermaid
flowchart TD
A["手机歌曲变化"]
B["系统元数据回调"]
C["SDK 转换歌曲信息"]
D["LiveData 通知"]
E["刷新歌名和时长"]
A --> B
B --> C
C --> D
D --> E
```
代码定位:
| 回调类型 | SDK 入口 | ViewModel 入口 | UI 刷新内容 |
|---|---|---|---|
| 播放状态 | `onPlaybackStateChanged` | `onPlayMusicState / onPlayMusicInfo` | 播放按钮、当前进度、当前时间 |
| 歌曲信息 | `onMetadataChanged` | `onPlayMusicInfo` | 歌名、歌手、总时长 |
字段来源:
| 字段 | 来源 |
|---|---|
| 歌名 | `MediaMetadata.METADATA_KEY_TITLE` |
| 歌手 | `MediaMetadata.METADATA_KEY_ARTIST` |
| 专辑 | `MediaMetadata.METADATA_KEY_ALBUM` |
| 总时长 | `MediaMetadata.METADATA_KEY_DURATION` |
| 当前进度 | `PlaybackStateCompat.getPosition()` |
| 播放状态 | `PlaybackStateCompat.STATE_PLAYING` |
当前 UI 特殊点:
- `albumUrl` 虽然会接收,但 UI 不展示手机封面。
- 默认封面固定使用 `R.mipmap.icon_bluetooth_music`。
## 11. 音频焦点流程
蓝牙音乐播放前需要申请音频焦点,否则可能出现手机在播但车机无声或被其他音源压住。
申请焦点:
```mermaid
flowchart TD
A["准备播放"]
B["确认 A2DP"]
C["请求音频焦点"]
D["解除 A2DP 静音"]
E["允许播放命令"]
A --> B
B --> C
C --> D
D --> E
```
失败规则:
| 失败点 | 结果 |
|---|---|
| A2DP 未连接 | `requestFocus` 返回 `false` |
| 音频焦点申请失败 | 不应认为蓝牙音乐已经有效播放 |
| MediaSession 未就绪 | AVRCP 控制可能无效 |
焦点变化:
```mermaid
flowchart TD
A["焦点变化"]
B["静音或恢复 A2DP"]
C["暂停或恢复播放"]
D["刷新音乐状态"]
A --> B
B --> C
C --> D
```
焦点变化处理:
| 焦点事件 | 当前处理 |
|---|---|
| `AUDIOFOCUS_GAIN` | 解除 A2DP 静音;如果丢焦前在播,则恢复播放 |
| `AUDIOFOCUS_LOSS` | 释放焦点;暂停蓝牙音乐;清空 Launcher 音乐信息 |
| `AUDIOFOCUS_LOSS_TRANSIENT` | A2DP 静音;记录是否正在播放;暂停 |
| `AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK` | 降低 A2DP 音量 |
## 12. 断开与关闭流程
```mermaid
flowchart TD
A["连接断开"]
B["SDK 收到回调"]
C["清空音乐缓存"]
D["通知 UI 未连接"]
E["显示未连接面板"]
A --> B
B --> C
C --> D
D --> E
```
相关回调:
- `onDisconnect`
- `onA2dpDisconnect`
- `onClose`
- `onBtDisconnect`
- `onBtClose`
清理内容:
| 清理项 | 目的 |
|---|---|
| `title / artist / album` | 避免断开后继续显示旧歌曲 |
| `progress / duration` | 避免进度条停在旧状态 |
| 播放状态 | 播放按钮恢复为播放图标 |
| `mLiveDataA2dpConnectStatus = false` | UI 切回未连接面板 |
## 13. 当前实现中的关键状态
| 状态 | 位置 | 含义 |
|---|---|---|
| `mLastPlayState` | `BtMusicFragment` / `BluetoothMusicActivity` | 记录上次播放状态,用于页面恢复时决定是否续播 |
| `musicPlaying` | `BtMusicViewModel` | 音频焦点丢失前是否在播放 |
| `mMusicInfo` | `BtMusicViewModel` | 最近一次音乐信息缓存 |
| `mLiveDataMusicInfo` | `BtMusicViewModel` | 歌曲信息 UI 通知 |
| `mLiveDataPlayState` | `BtMusicViewModel` | 播放状态 UI 通知 |
| `mLiveDataA2dpConnectStatus` | `BtMusicViewModel` | A2DP 连接状态 UI 通知 |
| `isFocus` | `BtMusicAudioManager` | 蓝牙音乐是否持有音频焦点 |
| `isPlayingLossFocus` | `BtMusicAudioManager` | 丢失焦点前是否在播放 |
## 14. 和老项目对齐的现状
已对齐:
- 主界面增加“蓝牙音乐”入口。
- 蓝牙音乐 UI 使用老项目默认封面和圆形控制按钮。
- 进度条资源复用老项目。
- 封面固定使用默认蓝牙音乐图,不使用手机专辑图。
- 歌名、歌手、进度、时间通过现有 SDK 刷新。
- 播放、暂停、上一首、下一首继续走新项目 SDK,不搬旧项目业务代码。
未完全补齐:
- 未连接状态下 `tv_connect_btn` 目前没有跳转蓝牙设置页。
- 没有“连接中”状态,例如 A2DP 正在连接时的进度提示。
- MediaBrowser 未连接时没有明显错误提示。
- 播放控制失败没有 UI 反馈。
- Fragment 与 Activity 两套蓝牙音乐逻辑重复,后续可抽公共逻辑。
## 15. 开发切入点
### 15.1 要改 UI
优先看:
- `fragment_bt_music.xml`
- `activity_bluetooth_music.xml`
- `selector_music_play.xml`
- `selector_music_pause.xml`
- `selector_music_pre.xml`
- `selector_music_next.xml`
- `bt_music_progress_layer.xml`
### 15.2 要改播放控制
优先看:
- `BtMusicFragment.onClick`
- `BluetoothMusicActivity.onClick`
- `BtMusicViewModel.playBtMusic / pauseBtMusic / preBtMusic / nextBtMusic`
- `BtMusicControlManager`
- `BtMusicManager`
- `BluetoothAvrcpClient`
### 15.3 要改连接逻辑
优先看:
- `BtMusicFragment.btMusicInfo`
- `BluetoothMusicActivity.btMusicInfo`
- `BtMusicViewModel.connectA2dp`
- `BtSettingControlManager.connectA2dp`
- `BluetoothA2dpSinkClient`
### 15.4 要改音频焦点
优先看:
- `BtMusicAudioManager.requestAudioFocus`
- `BtMusicAudioManager.releaseAudioFocus`
- `BtMusicAudioManager.mAudioFocusListener`
- `BtMusicManager.requestFocus`
## 16. 建议补齐的功能
| 优先级 | 功能 | 原因 |
|---|---|---|
| P0 | 未连接按钮跳转蓝牙设置页 | 用户当前只有蓝牙音乐入口,通过设置页跳转时需要闭环 |
| P0 | A2DP 连接中状态 | 避免点击后无反馈 |
| P1 | MediaBrowser 未连接提示 | 手机蓝牙媒体服务未就绪时便于定位 |
| P1 | 播放控制失败日志/提示 | AVRCP 命令可能因手机端播放器未启动而无效 |
| P1 | Fragment 和 Activity 逻辑复用 | 降低后续维护成本 |
| P2 | 进度条拖动 seek | 当前只展示,不支持拖动 |
| P2 | 专辑图开关 | 如果后续产品要求展示手机专辑图,可恢复 `albumUrl` 加载 |
## 17. 测试清单
### 17.1 正常流程
- 手机已配对并连接 HFP。
- 进入蓝牙音乐页。
- 自动检查并连接 A2DP。
- 手机打开音乐播放器。
- 点击播放,车机出声。
- 点击暂停,手机播放器暂停。
- 点击上一首/下一首,手机播放器切歌。
- 歌名、歌手、时间、进度刷新。
### 17.2 连接异常
- 未连接手机进入蓝牙音乐页,应显示未连接面板。
- 手机只连 HFP 未连 A2DP,进入音乐页应尝试连接 A2DP。
- A2DP 连接失败,应能从日志看到 `connectA2dp` 相关信息。
- 手机断开蓝牙,UI 应切回未连接状态。
### 17.3 焦点与打断
- 蓝牙音乐播放时来电,音乐应暂停或静音。
- 通话结束后,如果丢焦前在播,应尝试恢复。
- 切到收音机/USB 音乐,蓝牙音乐应释放或失去焦点。
- 重新切回蓝牙音乐,按状态恢复播放。
### 17.4 兼容性
- 不同手机品牌测试 AVRCP 元数据。
- 手机音乐 App 未打开时点击播放。
- 手机音乐 App 开着但无播放队列。
- 蓝牙断开后快速重连。
- 系统重启后首次进入蓝牙音乐。
## 18. 日志关键字
调试时可优先过滤这些关键字:
```text
BtMusicFragment
BluetoothMusicActivity
BtMusicViewModel
BtMusicControlManager
BtMusicManager
BtMusicAudioManager
BluetoothAvrcpClient
MediaBrowserHelper
connectA2dp
onA2dpConnect
onA2dpDisconnect
onPlaybackStateChanged
onMetadataChanged
requestAudioFocus
AUDIOFOCUS_GAIN
AUDIOFOCUS_LOSS
```
## 19. 总结
蓝牙音乐模块可以按四条主线理解:
1. **入口线**:`MainActivity -> BtMusicFragment`。
2. **连接线**:`BtMusicFragment -> BtMusicViewModel -> BtSettingControlManager -> A2DP`。
3. **控制线**:`按钮点击 -> BtMusicViewModel -> BtMusicControlManager -> BtMusicManager -> BluetoothAvrcpClient -> 手机播放器`。
4. **回调线**:`手机媒体状态 -> MediaBrowserClient -> BluetoothAvrcpClient -> BtMusicManager -> BtMusicViewModel LiveData -> UI`。
开发这个模块时,优先保证 A2DP 连接、AVRCP 控制、音频焦点三条链路可验证,再做 UI 微调和异常状态补齐。