杰理 SDK 文档中心
首页
首页
  • SDK 框架库

    • JL_BLEKit 蓝牙通信核心
    • JL_AdvParse 广播包解析
    • JL_HashPair 加密配对
    • JL_OTALib 固件升级
    • JLDialUnit 彩屏仓与表盘控制
    • JLBmpConvertKit 位图转换
    • JLPackageResKit 资源包处理
    • JLLogHelper 日志工具
  • 核心功能模块

    • 音乐与媒体控制
    • 音效调节与均衡器
    • 设备发现、连接与设置
    • Auracast 广播接收与发射
    • 文件浏览、闹钟、FM 与灯光控制
    • ANC、按键设置与查找设备
    • AI 翻译与自定义命令
  • 应用架构与工程支撑

    • 杰理之家 App 架构与导航
    • 数据存储与缓存
    • Swift 工具与扩展层
    • JLAudioUnitKit 示例工程
    • SDKTestHelper 测试工具
  • 开发文档与资源

    • 文档中心与 JL_OTALib API 说明
    • 自定义蓝牙接入方式
    • 调试技巧与问题排查
    • 版本历史与社区支持

音乐与媒体控制

本文档介绍 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 蓝牙生态中,"音乐与媒体控制"存在两条独立的播放链路:

  1. 设备端音乐(Device Music):TWS 耳机、蓝牙音箱等设备内部存储(或通过蓝牙 AVRCP/私有协议访问)的音乐资源,由手机 App 通过 JL SDK 发送命令控制设备的播放/暂停/上一首/下一首等动作。设备侧的媒体信息(歌名、歌手、专辑等 ID3 元数据)通过通知回传,App 侧据此刷新界面。典型入口是 DeviceMusicVC,其核心逻辑委托给 DMusicHandler 处理。

  2. 手机端音乐(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

来源:DeviceMusicVC.h

设计意图:JL_CardType 是 JL SDK 定义的上层卡片类型枚举,DeviceMusicVC 不直接关心蓝牙协议细节,而是以卡片类型作为业务维度的区分——不同设备(如 TWS 耳机、音箱)在命令通道、支持的特性(ID3 支持、Line-in 有无)上存在差异,界面层通过 type 做差异化展示,命令下发则统一交给 DMusicHandler。

2. 设备音乐命令处理:DMusicHandler

DMusicHandler 是设备音乐控制的业务中枢,它将"界面动作"翻译为"设备命令":

@interface DMusicHandler : NSObject
@property(nonatomic,weak)id<DMHandlerDelegate> delegate;
@end

来源:DMusicHandler.h

值得注意的设计点:

  • 弱引用代理(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.h

DFAudioPlayer 还提供了一组类方法用于曲库管理,分别是:

+(void)recoveryMusicInfo;          // 恢复歌曲信息状态
-(void)reloadPhoneMusic;           // 重新加载手机音乐
+(NSArray*)filterMusic:(NSString*)name;  // 按名称过滤音乐

来源:DFAudioPlayer.h

设计意图与使用场景:

  • 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_INFOLine-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: 刷新播放状态 / 歌名 / 进度

流程要点:

  1. 入口分层:DeviceMusicVC 只负责捕获用户意图与展示,不直接拼装蓝牙命令;命令构造与协议细节全部下沉到 DMusicHandler;
  2. 类型感知:界面依据 type(JL_CardType)决定命令内容与可展示信息(如是否有 Line-in、是否支持 ID3);
  3. 双通道回传:设备状态存在两条回传路径——同步路径(命令回包 → DMusicHandler 代理回调,用于立即更新按钮状态)与异步路径(设备主动上报 → 全局通知,用于所有订阅方刷新媒体信息);
  4. 解耦广播: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.h

使用方式:实例化 DeviceMusicVC 后,按当前连接的设备类型(耳机/音箱)赋值 type,界面即按对应卡片形态渲染音乐控制 UI。

示例 2:绑定音乐命令处理器代理

@interface DMusicHandler : NSObject
@property(nonatomic,weak)id<DMHandlerDelegate> delegate;
@end

来源:DMusicHandler.h

使用方式:界面控制器创建 DMusicHandler 实例并将自身设为 delegate(实现 DMHandlerDelegate 协议),随后把播放控制动作转交处理器;weak 修饰保证控制器销毁后无悬挂引用。

示例 3:手机端播放器曲库管理

+(void)recoveryMusicInfo;          // 恢复歌曲信息状态
-(void)reloadPhoneMusic;           // 重新加载手机音乐
+(NSArray*)filterMusic:(NSString*)name;  // 按名称过滤音乐

来源:DFAudioPlayer.h

使用方式:进入本地音乐界面时调用 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.typeJL_CardType由调用方赋值设备卡片类型(耳机/音箱),决定音乐界面的形态与命令差异
DMusicHandler.delegateid<DMHandlerDelegate>(weak)nil音乐命令处理器的回调代理,界面控制器实现以接收状态回调
JLUI_Cache.isLoadMusicInfoBOOLNO是否已执行设备音乐的首次刷新
JLUI_Cache.isLoadIpodInfoBOOLNO是否已执行本地(iPod)音乐的首次刷新
DFAudioPlayer.mListNSMutableArray空数组手机音乐播放列表,元素为 MusicOfPhoneMode
DFAudioPlayer.mNowItemMusicOfPhoneMode(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/音效设置、语音播报等能力请参见对应目录页面,本页不展开。

Next
音效调节与均衡器