first commit
This commit is contained in:
commit
669f49b1d6
342 files changed
+320811
No files matched your search
@@ -0,0 +1,748 @@
|
||||
# 蓝牙音乐模块功能流程梳理
|
||||
|
||||
更新时间: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 微调和异常状态补齐。
|
||||
Reference in new issue
Block a user