【AVRCP】规范精讲[32]:正在播放的播放器被移除?整套信令流程与避坑指南

在车载蓝牙、TWS耳机和智能音箱的AVRCP开发中,你一定遇到过这种棘手的场景:用户正在手机上用音乐App听歌,车载端正在播放,突然用户把App从后台划掉、卸载或者系统把进程杀了。这时候,车载端如果处理不好,就会出现播放卡顿、控制按钮失效、UI卡死甚至蓝牙断连的问题。本文就围绕正在播放的播放器被移除这一高频异常场景,把协议规定的交互流程、核心机制和避坑要点一次性讲透,把这个场景的兼容性和稳定性拉满。


目录

[一、什么是Remove Active Player?](#一、什么是Remove Active Player?)

二、核心交互流程

三、关键协议逻辑与原文解析

四、核心知识点与避坑指南

五、测验


一、什么是Remove Active Player?

直白地说,这个场景就是:控制端CT已经把目标端 TG 上的某台播放器设为当前寻址播放器,且这台播放器正在播放或处于激活状态;突然这台播放器被系统或用户终止,TG必须按规范完成一整套善后流程,告诉CT播放器消失了,同时清理所有相关状态。

和上一期讲的"未激活播放器被移除"不同,这次的核心区别在于播放器处于Active状态,也就是它正在被寻址、甚至正在播放,所以对状态清理和通知时序的要求会更严格,稍有不慎就会出现播放流程的混乱。

二、核心交互流程

我们结合时序图,一步步拆解CT和TG的每一步交互,搞懂每个动作背后的协议逻辑。

1. 初始状态: AVRCP 会话建立,寻址完成

  • CT与TG已建立AVRCP连接,控制通道正常

  • CT通过SetAddressedPlayer指定了播放器(SubunitID=2),这台播放器是当前的寻址目标

  • CT注册了TRACK_CHANGED、ADDRESSED_PLAYER_CHANGED等通知,用于接收播放状态变化

这里可以用一个很形象的比喻:就像你( CT )用遥控器控制电视( TG )上的某一个视频App(播放器),你已经把遥控器对准了这个App,还设置好了自动接收换台通知。

2. 触发事件:用户移除正在播放的播放器

用户操作/系统行为导致TG端的播放器进程被终止,比如划掉后台、卸载App、系统杀进程。此时,TG必须第一时间感知到寻址播放器已经消失。

3. 协议交互全流程(按顺序)

(1)CT发送SetAddressedPlayer,完成寻址

CT通过SetAddressedPlayer命令,将SubunitID=2的播放器设为当前寻址目标,后续的播放控制、通知都将绑定到这台播放器上。

伪代码示例:

cpp 复制代码
// 伪代码:设置寻址播放器
avrcp_pkt_t pkt;
avrcp_build_set_addressed_player(&pkt, 0x0002); // SubunitID=2
avrcp_send_pkt(session, &pkt);

(2)CT注册播放状态通知,等待事件

CT向TG发送RegisterNotification,注册TRACK_CHANGED(轨道变更)事件,用于接收切歌通知。TG回复InterimResponse,表示已接收通知请求,等待事件触发。

(3)用户移除播放器, TG 感知到寻址播放器消失

TG内部检测到SubunitID=2的播放器进程已终止,此时必须启动规范规定的善后流程。

(4) TG 回复ADDRESSED_PLAYER_CHANGED通知,告知CT寻址播放器已移除

TG向CT发送RegisterNotificationResponse,携带ADDRESSED_PLAYER_CHANGED事件,明确标记SubunitID=2的状态为Removed。这一步是整个流程的核心,它相当于直接告诉CT:你之前控制的那个播放器没了,不要再往它身上发命令了。

(5) TG 拒绝所有与旧播放器相关的未完成通知

对于之前CT注册的TRACK_CHANGED等事件,TG必须回复RegisterNotificationResponse并标记为REJECTED。这一步非常关键,它的作用是终止CT对无效事件的等待,避免CT因为一直等不到通知而超时、卡死或者重发无效命令。

(6)CT收到通知后,主动刷新可用播放器列表

CT发送GetFolderItem命令,获取TG上ROOT目录下的媒体播放器列表。这一步是规范的强制要求,CT不能依赖旧的播放器缓存,必须重新拉取最新的可用播放器清单,才能知道哪些播放器还活着。

(7)CT根据新列表,设置新的寻址播放器

CT收到GetFolderItemRsp返回的ListMediaPlayers后,选择一个有效的播放器(比如SubunitID=3),再次发送SetAddressedPlayer,完成新的寻址,后续的播放控制和通知都将绑定到这台新播放器上。

三、关键协议逻辑与原文解析

整个流程的英文描述虽然简洁,但每一句话都对应着开发中必须遵守的硬性规则。

Remove media player which is currently active (remove addressable player)

这句话直接点明了场景的核心:移除的是当前Active状态的可寻址播放器。很多开发同学容易在这里踩坑,认为只有未播放的播放器移除流程简单,但实际上,Active状态的移除流程对通知时序的要求更严格,因为它直接影响当前的播放会话。

This is a recommended sequence of responses

协议明确标注了这是推荐的响应序列,也就是说,TG的实现必须遵循这个顺序:先发送ADDRESSED_PLAYER_CHANGED通知,再拒绝旧的未完成通知,否则就可能出现CT处理逻辑混乱的问题。

四、核心知识点与避坑指南

1. 两个关键通知事件的作用

|--------------------------|------------------|--------------|
| 事件 | 作用 | 触发时机 |
| ADDRESSED_PLAYER_CHANGED | 告知CT当前寻址播放器的状态变化 | 播放器被移除、切换、新增 |
| TRACK_CHANGED | 通知CT当前播放的轨道变更 | 切歌、播放新文件 |

2. 开发中最容易踩的3个坑

  • 坑1:只发ADDRESSED_PLAYER_CHANGED通知,不拒绝旧的未完成通知

很多TG实现只发了寻址变更通知,却忘了处理之前注册的TRACK_CHANGED等事件,导致CT一直等待这些通知,最终超时,出现UI卡死的问题。

  • 坑2:CT收到通知后,不重新拉取播放器列表,直接复用旧缓存

规范强制要求CT必须调用GetFolderItem刷新列表,因为播放器列表可能随时变化,旧缓存里的播放器ID可能已经失效,直接寻址会导致SetAddressedPlayer命令报错。

  • 坑3:TG在播放器移除后,直接销毁资源,不按流程发通知

有些实现会直接把播放器对象删掉,没有发送任何通知,导致CT一直以为旧播放器还在运行,后续的播放控制命令全部发给无效的ID,造成播放失效。

3. 为什么Active状态的移除流程必须严格遵守时序?

因为Active状态的播放器是当前会话的核心,它的移除会直接影响CT的控制逻辑和播放状态。如果时序混乱,比如先拒绝旧通知再发寻址变更通知,CT可能会误判为普通的通知错误,而不是播放器移除事件,从而无法正确切换到新的播放器。

五、测验

**问:**当AVRCP中当前正在寻址的Active播放器被移除时,TG首先必须发送什么通知?(车载蓝牙开发面试真题)

答:

必须首先发送ADDRESSED_PLAYER_CHANGED通知,携带被移除的播放器ID,明确告知CT寻址目标已失效。

**问:**播放器移除后,TG如何处理CT之前注册的TRACK_CHANGED等未完成通知?(蓝牙协议栈开发面试题)

答:

TG必须以REJECTED状态回复这些通知,终止CT的等待流程,避免超时或状态异常。

:CT收到播放器移除通知后,为什么必须调用GetFolderItem刷新播放器列表?(嵌入式蓝牙开发面试题)

规范强制要求CT获取最新的可用播放器列表,避免使用失效的播放器ID,确保后续的SetAddressedPlayer命令能发送给有效的目标。