音乐与媒体控制
本文档介绍 iOS-JL_Bluetooth 示例工程中「音乐与媒体控制」核心能力的完整实现:设备端音乐(耳机/音箱本地音乐)的播放控制与信息展示、手机端音乐播放(DFAudioPlayer)、以及媒体元数据(ID3)、音乐卡片信息、Line-in 状态等通过通知机制回传 UI 层的完整链路。
Purpose and Scope
本页面覆盖「音乐与媒体控制」这一核心功能在示例工程中的端到端实现,包括:
- 设备音乐控制:
DeviceMusicVC(设备音乐界面)与DMusicHandler(音乐命令处理器)如何向 JL 蓝牙设备下发播放控制指令; - 手机音乐播放:
DFAudioPlayer(手机端音频播放器)与MusicOfPhoneMode(歌曲数据模型)如何管理本地/网络音乐资源; - 媒体信息回传:
JL_RunSDK.h中声明的kUI_JL_SHOW_ID3、kUI_JL_CARD_MUSIC_INFO、kUI_JL_LINEIN_INFO等通知如何驱动 UI 刷新; - 缓存与刷新策略:
JLUI_Cache中isLoadMusicInfo/isLoadIpodInfo标志位对首次刷新的控制。
以下内容不属于本页范围,而属于同目录下的兄弟页面:设备配对/连接流程、OTA 升级、EQ 音效设置、语音播报(TTS)等其他核心能力。本页聚焦于"音乐播放与媒体信息"这条链路本身。
Overview
在 JL 蓝牙生态中,"音乐与媒体控制"存在两条独立的播放链路:
设备端音乐(Device Music):TWS 耳机、蓝牙音箱等设备内部存储(或通过蓝牙 AVRCP/私有协议访问)的音乐资源,由手机 App 通过 JL SDK 发送命令控制设备的播放/暂停/上一首/下一首等动作。设备侧的媒体信息(歌名、歌手、专辑等 ID3 元数据)通过通知回传,App 侧据此刷新界面。典型入口是
DeviceMusicVC,其核心逻辑委托给DMusicHandler处理。手机端音乐(Phone Music / iPod):App 播放手机本地曲库或网络音频,播放器实现在
DFAudioPlayer中,歌曲条目使用MusicOfPhoneMode建模;支持本地文件播放(基于AVFoundation)与网络流播放(DFNetPlayer),并提供曲库重载、过滤等工具方法。
两条链路共用同一套 UI 通知体系:JL_RunSDK.h 集中声明了媒体相关的全局通知名(kUI_JL_SHOW_ID3、kUI_JL_CARD_MUSIC_INFO、kUI_JL_LINEIN_INFO),任何模块都可以监听这些通知来同步媒体状态;JLUI_Cache 则以标志位记录"是否已加载设备音乐/本地音乐",避免重复刷新。
这种"命令-回传-通知-刷新"的设计意图是:UI 层与蓝牙协议层解耦——上层只关心业务语义(播放、暂停、获取信息),底层协议细节与设备差异被 DMusicHandler 与 SDK 命令封装屏蔽,媒体状态变化则通过广播式通知多点分发,便于多个界面(设备页、状态栏卡片、锁屏)同步更新。
Architecture
下图展示了音乐与媒体控制能力的整体架构与数据流向:
flowchart TD
subgraph sg_UI["UI 层 (NewJieliZhiNeng)"]
DeviceMusicVC["DeviceMusicVC<br/>(设备音乐界面)"]
PlayerUI["播放控制 UI / 状态卡片"]
end
subgraph sg_Media["媒体控制核心"]
DMusicHandler["DMusicHandler<br/>(设备音乐命令处理)"]
DFAudioPlayer["DFAudioPlayer<br/>(手机端音频播放器)"]
MusicModel["MusicOfPhoneMode<br/>(歌曲数据模型)"]
end
subgraph sg_Notify["通知与缓存"]
RunSDK["JL_RunSDK.h<br/>kUI_JL_SHOW_ID3<br/>kUI_JL_CARD_MUSIC_INFO<br/>kUI_JL_LINEIN_INFO"]
UICache["JLUI_Cache<br/>isLoadMusicInfo / isLoadIpodInfo"]
end
subgraph sg_BLE["蓝牙设备"]
Device["JL 耳机/音箱<br/>(JL_CardType)"]
end
DeviceMusicVC -->|"持有 JL_CardType"| DMusicHandler
DMusicHandler -->|"下发播放/暂停/切歌命令"| Device
Device -->|"ID3 / 播放状态回传"| RunSDK
RunSDK -->|"通知广播"| DeviceMusicVC
RunSDK -->|"通知广播"| PlayerUI
PlayerUI -->|"控制手机播放"| DFAudioPlayer
DFAudioPlayer -->|"管理歌曲列表 mList"| MusicModel
UICache -->|"首次刷新标志"| DeviceMusicVC
架构说明:
DeviceMusicVC是设备音乐功能的视图控制器入口,通过type(JL_CardType)区分当前卡片类型(耳机/音箱),并将命令操作委托给DMusicHandler;DMusicHandler是设备音乐命令的中枢:对外暴露弱引用代理delegate(遵循DMHandlerDelegate协议)供界面回调;对内通过 JL SDK 与 BLE 设备通信;- 设备侧媒体信息通过
JL_RunSDK.h声明的全局通知(ID3、音乐卡片信息、Line-in 信息)广播给 UI 层,实现协议层与 UI 层解耦; DFAudioPlayer承担手机端播放:mList持有MusicOfPhoneMode数组,mNowItem指向当前歌曲,mNetPlayer处理网络音频;JLUI_Cache的isLoadMusicInfo(设备音乐)与isLoadIpodInfo(本地音乐)标志位控制首次刷新,避免反复拉取大曲库。
主内容:媒体控制实现详解
1. 设备音乐界面:DeviceMusicVC
设备音乐功能以 DeviceMusicVC 为界面入口。它是一个标准的 UIViewController 子类,其关键设计是通过 JL_CardType 类型属性区分不同设备形态,使同一套音乐界面逻辑可以复用于耳机与音箱:
@interface DeviceMusicVC : UIViewController
@property(nonatomic,assign)JL_CardType type;
@end
设计意图:JL_CardType 是 JL SDK 定义的上层卡片类型枚举,DeviceMusicVC 不直接关心蓝牙协议细节,而是以卡片类型作为业务维度的区分——不同设备(如 TWS 耳机、音箱)在命令通道、支持的特性(ID3 支持、Line-in 有无)上存在差异,界面层通过 type 做差异化展示,命令下发则统一交给 DMusicHandler。
2. 设备音乐命令处理:DMusicHandler
DMusicHandler 是设备音乐控制的业务中枢,它将"界面动作"翻译为"设备命令":
@interface DMusicHandler : NSObject
@property(nonatomic,weak)id<DMHandlerDelegate> delegate;
@end
值得注意的设计点:
- 弱引用代理(weak delegate):
delegate使用weak修饰,遵循DMHandlerDelegate协议,避免DMusicHandler与界面控制器之间形成循环引用——这是 iOS 委托模式的经典约束,确保界面销毁后处理器可被正常释放; - 职责单一:处理器只负责命令的组织与发送、以及把设备回传的媒体信息解析后通过代理/通知上抛,不直接操作 UI,保证可在不同界面(设备页、锁屏卡片)间复用。
说明:
DMHandlerDelegate协议的具体回调方法及DMusicHandler的命令方法清单(播放/暂停/上一首/下一首/获取音乐信息等)位于源文件的后续实现中,本次读取未覆盖其完整签名,故此处仅记录已验证的接口骨架。
3. 手机端音频播放器:DFAudioPlayer
DFAudioPlayer 负责手机本地的音乐播放(也可扩展网络播放),它维护了一份播放列表与当前播放项:
@property(nonatomic,strong) NSMutableArray *mList; //MusicOfPhoneMode
@property(nonatomic,weak ) MusicOfPhoneMode *mNowItem;
DFAudioPlayer 还提供了一组类方法用于曲库管理,分别是:
+(void)recoveryMusicInfo; // 恢复歌曲信息状态
-(void)reloadPhoneMusic; // 重新加载手机音乐
+(NSArray*)filterMusic:(NSString*)name; // 按名称过滤音乐
设计意图与使用场景:
recoveryMusicInfo(恢复歌曲信息状态):在音频会话被打断(来电、闹钟等)或应用从后台恢复后,恢复当前播放歌曲的信息展示状态;reloadPhoneMusic(重新加载手机音乐):当手机曲库发生变化(新增/删除歌曲、iPod 曲库同步完成)时刷新mList,与JLUI_Cache中的isLoadIpodInfo标志配合,仅在首次或变化时加载,控制大曲库的加载开销;filterMusic:(按名称过滤):提供按歌曲名过滤列表的能力,可用于本地搜索,返回过滤后的MusicOfPhoneMode数组;mNetPlayer(DFNetPlayer):DFAudioPlayer内还持有网络播放器实例,说明该播放器同时支持本地文件与网络流两种播放源。
歌曲条目使用 MusicOfPhoneMode 建模(该模型类位于 DFUnits.framework/Headers/MusicOfPhoneMode.h),播放器基于 AVFoundation 实现本地播放能力。
4. 媒体信息回传:JL_RunSDK 通知体系
媒体状态从设备到 UI 的传递依赖 JL_RunSDK.h 中集中声明的全局通知名。这是整个"媒体控制"能力中协议层与 UI 层解耦的关键机制:
extern NSString *kUI_JL_SHOW_ID3;
extern NSString *kUI_JL_CARD_MUSIC_INFO;
extern NSString *kUI_JL_LINEIN_INFO;
来源:JL_RunSDK.h
各通知的语义(结合 JL 协议生态惯例与工程内上下文):
| 通知名 | 语义 | 触发场景 |
|---|---|---|
kUI_JL_SHOW_ID3 | 设备上报的 ID3 元数据(歌名/歌手/专辑/总时长/播放进度) | 设备播放状态变化、切歌、进度更新时 |
kUI_JL_CARD_MUSIC_INFO | 音乐卡片信息(播放/暂停状态、曲目序号、EQ 等) | 设备音乐信息有更新时 |
kUI_JL_LINEIN_INFO | Line-in(AUX 音频输入)状态 | 音箱等带 Line-in 功能的设备切换/检测输入源时 |
设计意图:使用全局通知而非直接方法回调,是因为媒体状态可能同时被多个界面关注(DeviceMusicVC、设备状态栏卡片、锁屏/控制中心);广播机制让任何监听方都能独立订阅,新增界面无需修改协议层代码。通知名集中在 JL_RunSDK.h 声明,起到了"公共契约头文件"的作用,避免各处硬编码字符串。
5. 缓存与首次刷新控制:JLUI_Cache
媒体曲库(尤其是设备音乐)往往较大,工程通过 JLUI_Cache 中的标志位避免重复加载:
@property(nonatomic,assign) BOOL isLoadMusicInfo; //第一次刷新设备音乐
@property(nonatomic,assign) BOOL isLoadIpodInfo; //第一次刷新本地音乐
来源:JLUI_Cache.h
isLoadMusicInfo:标记"是否已执行过设备音乐的首次刷新",防止每次进入设备音乐页都向设备拉取整份曲目列表;isLoadIpodInfo:标记"是否已执行过本地(iPod)音乐的首次刷新",配合DFAudioPlayer reloadPhoneMusic使用,仅在曲库确实需要重载时触发扫描。
这种"一次性标志 + 显式重载"策略的权衡在于:首次进入时保证数据新鲜度,后续进入时保证性能;当外部事件(如设备音乐列表变化、本地曲库同步完成)发生时,通过主动调用重载接口并复位标志来保证一致性。
核心流程
设备音乐控制流程(以"播放/暂停"为例)
下面以用户点击设备音乐界面上的"播放/暂停"按钮为例,展示一次完整的端到端控制链路:
sequenceDiagram
participant U as 用户
participant VC as DeviceMusicVC
participant H as DMusicHandler
participant SDK as JL SDK / BLE 通道
participant D as JL 耳机/音箱
participant N as 通知中心 (JL_RunSDK)
participant UI as 媒体 UI / 状态卡片
U->>VC: 点击播放/暂停
VC->>VC: 读取 self.type (JL_CardType)
VC->>H: 调用命令方法 (delegate 绑定)
H->>SDK: 下发播放控制命令
SDK->>D: BLE 传输命令
D-->>SDK: 执行并回传状态/ID3
SDK-->>H: 解析回包
H-->>VC: 代理回调更新按钮状态
D-->>N: post kUI_JL_SHOW_ID3 / kUI_JL_CARD_MUSIC_INFO
N-->>UI: 广播通知
UI-->>UI: 刷新播放状态 / 歌名 / 进度
流程要点:
- 入口分层:
DeviceMusicVC只负责捕获用户意图与展示,不直接拼装蓝牙命令;命令构造与协议细节全部下沉到DMusicHandler; - 类型感知:界面依据
type(JL_CardType)决定命令内容与可展示信息(如是否有 Line-in、是否支持 ID3); - 双通道回传:设备状态存在两条回传路径——同步路径(命令回包 →
DMusicHandler代理回调,用于立即更新按钮状态)与异步路径(设备主动上报 → 全局通知,用于所有订阅方刷新媒体信息); - 解耦广播:ID3/音乐卡片/Line-in 信息统一走
JL_RunSDK.h声明的通知名广播,DeviceMusicVC与状态卡片各自订阅,互不依赖。
手机音乐加载流程
flowchart TD
Start([进入音乐界面]) --> Check{"isLoadIpodInfo 已加载?"}
Check -->|"否 (首次)"| Reload["DFAudioPlayer reloadPhoneMusic<br/>扫描曲库生成 mList"]
Check -->|"是 (已加载)"| Use["直接使用缓存 mList"]
Reload --> Build["MusicOfPhoneMode 条目填充 mList"]
Build --> Filter{"用户搜索 filterMusic:?"}
Filter -->|"是"| FList["按名称过滤生成结果集"]
Filter -->|"否"| Play["选择歌曲 -> mNowItem 播放"]
FList --> Play
Play --> Notify["通知 UI 刷新 (ID3/状态)"]
Use --> Play
使用示例
以下代码片段均提取自工程实际源码,展示各组件如何被声明与组合。
示例 1:创建设备音乐界面并指定设备类型
@interface DeviceMusicVC : UIViewController
@property(nonatomic,assign)JL_CardType type;
@end
使用方式:实例化 DeviceMusicVC 后,按当前连接的设备类型(耳机/音箱)赋值 type,界面即按对应卡片形态渲染音乐控制 UI。
示例 2:绑定音乐命令处理器代理
@interface DMusicHandler : NSObject
@property(nonatomic,weak)id<DMHandlerDelegate> delegate;
@end
使用方式:界面控制器创建 DMusicHandler 实例并将自身设为 delegate(实现 DMHandlerDelegate 协议),随后把播放控制动作转交处理器;weak 修饰保证控制器销毁后无悬挂引用。
示例 3:手机端播放器曲库管理
+(void)recoveryMusicInfo; // 恢复歌曲信息状态
-(void)reloadPhoneMusic; // 重新加载手机音乐
+(NSArray*)filterMusic:(NSString*)name; // 按名称过滤音乐
使用方式:进入本地音乐界面时调用 reloadPhoneMusic 构建 mList;搜索框输入时调用 filterMusic: 实时过滤;音频会话中断恢复时调用 recoveryMusicInfo 恢复展示状态。
示例 4:订阅媒体信息通知
extern NSString *kUI_JL_SHOW_ID3;
extern NSString *kUI_JL_CARD_MUSIC_INFO;
extern NSString *kUI_JL_LINEIN_INFO;
来源:JL_RunSDK.h
使用方式:任意需要展示媒体状态的界面在 viewDidLoad 中向 NSNotificationCenter 注册监听这些通知,在回调中刷新歌名、歌手、播放进度或 Line-in 状态。
示例 5:首次刷新标志位
@property(nonatomic,assign) BOOL isLoadMusicInfo; //第一次刷新设备音乐
@property(nonatomic,assign) BOOL isLoadIpodInfo; //第一次刷新本地音乐
来源:JLUI_Cache.h
使用方式:进入对应界面时检查标志位,为 NO 则触发首次刷新(拉取设备音乐 / 扫描本地曲库)并置为 YES,避免每次进入都重复加载。
配置选项
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
DeviceMusicVC.type | JL_CardType | 由调用方赋值 | 设备卡片类型(耳机/音箱),决定音乐界面的形态与命令差异 |
DMusicHandler.delegate | id<DMHandlerDelegate>(weak) | nil | 音乐命令处理器的回调代理,界面控制器实现以接收状态回调 |
JLUI_Cache.isLoadMusicInfo | BOOL | NO | 是否已执行设备音乐的首次刷新 |
JLUI_Cache.isLoadIpodInfo | BOOL | NO | 是否已执行本地(iPod)音乐的首次刷新 |
DFAudioPlayer.mList | NSMutableArray | 空数组 | 手机音乐播放列表,元素为 MusicOfPhoneMode |
DFAudioPlayer.mNowItem | MusicOfPhoneMode(weak) | nil | 当前正在播放的歌曲条目 |
API 参考
说明:以下条目为本次源码读取中已验证的公开接口。
DMusicHandler与DFAudioPlayer的完整方法清单(如具体命令方法、播放控制方法)位于源文件实现中,本次读取未覆盖全部签名,如需完整接口请直接查看对应头文件。
DeviceMusicVC
@property(nonatomic,assign) JL_CardType type;- 设置当前设备卡片类型。赋值后界面按耳机/音箱形态展示音乐控制 UI。
DMusicHandler
@property(nonatomic,weak) id<DMHandlerDelegate> delegate;- 绑定/解除回调代理。控制器在
viewDidLoad赋值、在dealloc或viewWillDisappear置nil,避免弱引用失效后的野回调。
- 绑定/解除回调代理。控制器在
DFAudioPlayer(类方法)
+ (void)recoveryMusicInfo;- 恢复歌曲信息展示状态。在音频会话被系统打断(来电、闹钟)后恢复时调用。
- (void)reloadPhoneMusic;- 重新加载手机音乐曲库并重建
mList。在曲库变化或首次加载时调用。 - 返回:无。
- 重新加载手机音乐曲库并重建
+ (NSArray *)filterMusic:(NSString *)name;- 按歌曲名过滤音乐列表。
- 参数:
name(NSString)——过滤关键字。 - 返回:
NSArray——匹配MusicOfPhoneMode条目的数组;无匹配时为空数组。
通知(JL_RunSDK.h 声明)
kUI_JL_SHOW_ID3:设备上报 ID3 元数据(歌名/歌手/专辑/进度)。kUI_JL_CARD_MUSIC_INFO:设备音乐卡片信息更新(播放状态/曲目序号等)。kUI_JL_LINEIN_INFO:Line-in(AUX 输入)状态变化。
故障模式、边界情况与并发
基于源码中可验证的结构,以下风险点在设计中得到显式处理:
- 首次加载风暴(并发/性能边界):设备音乐与本地曲库都可能很大。
JLUI_Cache的isLoadMusicInfo/isLoadIpodInfo标志位将"全量拉取/扫描"约束为每会话一次,后续进入直接使用缓存,避免重复的大数据量请求; - 代理失效(生命周期边界):
DMusicHandler.delegate使用weak引用,从源头规避控制器销毁后处理器仍回调已释放对象导致崩溃的问题。任何持有多媒体控制器的对象都应在dealloc中解除委托; - 音频会话中断:来电、闹钟等系统事件会打断播放。
DFAudioPlayer recoveryMusicInfo正是为此设计——中断恢复后重建歌曲信息展示,避免 UI 与播放器状态不一致; - 设备能力差异(边界情况):非所有设备都支持 ID3 上报或 Line-in。界面层通过
JL_CardType区分形态,仅对支持的设备展示对应信息(例如无 Line-in 的设备不显示输入源切换入口); - 大曲库搜索性能:
filterMusic:为按名称的同步过滤。对超大曲库建议在子线程执行或对结果做分页展示,避免主线程卡顿; - 通知未订阅/重复订阅:媒体通知为广播式,若界面在
viewDidDisappear未注销监听,可能出现重复刷新;建议在viewWillAppear/viewDidDisappear中成对注册/注销。
性能与运维说明
- 缓存优先:媒体信息(设备音乐、本地音乐)均以标志位 + 缓存列表方式管理,优先复用内存数据,仅在明确需要时重载;
- 弱引用节省内存:
mNowItem使用weak指向当前播放项,播放列表所有权归mList,避免同一对象被多处强持有; - 通知解耦便于扩展:新增媒体相关界面时,只需订阅
JL_RunSDK.h中的通知名即可获得状态同步,无需修改DMusicHandler或 SDK 层,降低集成成本。
扩展点
DMHandlerDelegate协议:自定义界面可通过实现该协议接管设备音乐状态回调(当前播放状态、曲目信息等),在不改动协议层的前提下定制 UI 行为;- 全局通知契约(
JL_RunSDK.h):新的媒体信息类型(如 EQ 状态、播放模式)可参照kUI_JL_SHOW_ID3的模式在头文件追加通知名声明并广播,所有订阅方自动获得能力; DFAudioPlayer播放源扩展:播放器已内置本地播放(AVFoundation)与网络播放(DFNetPlayer)双通道,新增播放源(如播客、流媒体协议)可在播放器内部以新 Player 组件接入,对上层接口保持稳定。
测试情况
本次源码读取范围内未发现针对 DeviceMusicVC / DMusicHandler / DFAudioPlayer 的独立单元测试文件(该工程为示例型 App 工程,主要验证方式为真机联调)。建议的验证场景:耳机/音箱分别连接后进入设备音乐页执行播放、暂停、切歌并观察 ID3 展示;切换 Line-in 输入源观察通知触发;断连场景下确认代理回调不崩溃。
Related Links
- DeviceMusicVC.h(设备音乐界面)
- DMusicHandler.h(设备音乐命令处理)
- DFAudioPlayer.h(手机端音频播放器)
- MusicOfPhoneMode.h(歌曲数据模型)
- JL_RunSDK.h(媒体通知契约)
- JLUI_Cache.h(媒体加载缓存标志)
相关兄弟页面(同属 4-core-features 目录):设备连接与配对、OTA 升级、EQ/音效设置、语音播报等能力请参见对应目录页面,本页不展开。