Files
Obsidian/项目文档/同力/蓝牙音乐/蓝牙音乐模块功能流程梳理_2026-07-13.md
2026-11-05 14:35:04 +08:00

749 lines
23 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 蓝牙音乐模块功能流程梳理
更新时间: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 微调和异常状态补齐。