# 蓝牙音乐模块功能流程梳理 更新时间: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 微调和异常状态补齐。