杰理 SDK 文档中心
首页
首页
  • 概述与快速开始

    • 项目简介与功能总览
    • 运行环境与快速开始
    • 工程结构与文档布局
  • 架构与核心机制

    • 插件架构与原生平台桥接
    • 基类管理器与常量体系
    • 事件流与接收通知机制
  • 蓝牙连接与设备管理

    • 蓝牙连接与状态管理
    • 设备信息、配置与按键设置
    • 双设备连接与多链路管理
    • 数据传输与自定义命令
  • 音乐与媒体控制

    • 设备音乐与手机音乐播放控制
    • 音量与音频输出管理
  • 音效与音频模式

    • 均衡器与音效调节
    • 音频模式与降噪(ANC)设置
    • Auracast 音频广播
  • 设备功能控制

    • 闹钟管理
    • FM 收音机控制
    • 灯光控制
    • 充电仓与彩屏仓管理
  • 示例应用:杰理之家 Demo

    • 应用框架与交互组件
    • 设置、多语言与调试
  • 接口参考与文档中心

    • 发送接口参考
    • 接收接口与事件参考
    • 官方文档与集成指南

音量与音频输出管理

BleVolumeManager / BleVolumeProcessor 与 VolumeInfo / VolumeCtrlInfo 构成了 JieLi Home Flutter SDK 中「音量与音频输出」能力的完整闭环:业务层通过 MethodChannel 向原生 BLE SDK 下发查询/设置指令,原生 SDK 将设备返回的音量状态以事件形式回传,Flutter 侧统一解析为数据模型并暴露为响应式 Stream。

Purpose and Scope

本页聚焦 音量(Volume)与高低音(Treble/Bass,即音频输出控制) 这条完整能力链路,覆盖:

  • 命令下发入口 BleVolumeManager(查询当前音量、设置音量/高音/低音);
  • 事件接收入口 BleVolumeProcessor(音量变化事件、高低音变化事件的 Stream);
  • 数据模型 VolumeInfo 与 VolumeCtrlInfo 及其序列化约定;
  • Flutter 与原生 BLE SDK(Android VolumeManager.kt、iOS VolumeManager.swift)之间的桥接边界;
  • 相关方法常量与事件常量、失败模式、并发与扩展方式。

本页不包含:蓝牙连接/设备扫描、播放控制(播放/暂停/切歌)、EQ 均衡器等其他音乐媒体能力——这些属于 4-music-media 目录下的兄弟页面,本页仅在涉及事件通道时引用公共的 BleBaseManager / BleBaseEventProcessor。

概述

在 JieLi(杰理)蓝牙生态中,音量与音频输出能力横跨三层:

  1. Flutter 应用层:业务页面调用 BleVolumeManager 的静态方法下发指令,订阅 BleVolumeProcessor 暴露的 Stream 获得设备状态变化;
  2. 桥接层:BleBaseManager.invokeMethod 通过 MethodChannel 把指令与参数序列化后交给原生侧;BleBaseEventProcessor 是全局事件总线,负责把原生回调分发到各类型事件的订阅者;
  3. 原生 BLE SDK 层:Android 侧 VolumeManager.kt / VolumeInfo.java / VolumeCtrl.java,iOS 侧 VolumeManager.swift / VolumeEventProcessor.swift(底层为 JL_SystemVolume 协议),负责与蓝牙设备(耳机/音箱)实际通信。

设计上,BleVolumeManager 与 BleVolumeProcessor 均为纯静态类、无内部状态:命令只是一次异步 MethodChannel 调用,事件则是被 BleBaseEventProcessor.filterByType 过滤后的冷流映射。这种「命令-事件」分离的设计让音量能力天然线程安全、可组合,业务方只需关心「发什么指令」和「听什么事件」,无需感知设备协议细节。

架构

flowchart TD
    subgraph sg_UI["Flutter 应用层"]
        Page["业务页面 / Example 页面"]
    end

    subgraph sg_Command["命令下发(静态方法)"]
        Mgr["BleVolumeManager"]
        BaseMgr["BleBaseManager.invokeMethod"]
        MConst["BleMethodConstants"]
    end

    subgraph sg_Event["事件接收(响应式 Stream)"]
        BaseProc["BleBaseEventProcessor(事件总线)"]
        Proc["BleVolumeProcessor"]
        EConst["BleEventConstants"]
    end

    subgraph sg_Model["数据模型"]
        VI["VolumeInfo<br/>maxVol / volume / supportVolumeSync"]
        VCI["VolumeCtrlInfo<br/>high / bass"]
    end

    subgraph sg_Native["原生桥接(MethodChannel)"]
        Android["VolumeManager.kt<br/>VolumeInfo.java / VolumeCtrl.java"]
        IOS["VolumeManager.swift<br/>VolumeEventProcessor.swift / JL_SystemVolume"]
    end

    subgraph sg_Device["蓝牙设备"]
        Dev["耳机 / 音箱(BLE)"]
    end

    Page -->|"getCurrentVolume / setCurrentVolume"| Mgr
    Mgr --> BaseMgr
    Mgr --> MConst
    BaseMgr -->|"MethodChannel 调用"| Android
    BaseMgr -->|"MethodChannel 调用"| IOS
    Android -->|"BLE 指令"| Dev
    IOS -->|"BLE 指令"| Dev
    Dev -->|"音量/高低音状态回包"| Android
    Dev -->|"音量/高低音状态回包"| IOS
    Android -->|"原生事件回调"| BaseProc
    IOS -->|"原生事件回调"| BaseProc
    BaseProc -->|"filterByType 过滤"| Proc
    Proc -->|"VolumeInfo.fromMap"| VI
    Proc -->|"VolumeCtrlInfo.fromMap"| VCI
    VI --> Page
    VCI --> Page

各角色职责

组件职责状态
BleVolumeManager音量指令下发入口,静态方法封装 MethodChannel 调用无状态
BleVolumeProcessor音量/高低音事件订阅入口,把事件总线消息映射为模型 Stream无状态
VolumeInfo音量快照:最大音量、当前音量、是否支持音量同步纯数据
VolumeCtrlInfo音频输出控制快照:高音(treble)、低音(bass)纯数据
BleBaseManager / BleBaseEventProcessor公共桥接基座:MethodChannel 调用与全局事件分发(属于 SDK 公共层,详见连接管理页面)全局单例/静态

设计意图:把「命令」与「事件」拆成两个类,是因为两者生命周期完全不同——指令是一次性 fire-and-forget 的异步调用,事件则可能是设备在任意时刻主动上报(例如用户在耳机上按键调音量)。若合并为一个类,静态无状态的设计会被破坏,订阅方与调用方也会耦合。

核心组件详解

1. 命令下发:BleVolumeManager

BleVolumeManager 是音量能力唯一的指令入口,全部方法为 static,内部只做一件事:把参数打包成 Map,转交 BleBaseManager.invokeMethod 走 MethodChannel 下发到原生 SDK。

/// Volume Manager
class BleVolumeManager {
  static Future<void> getCurrentVolume() async {
    await BleBaseManager.invokeMethod(
      BleMethodConstants.methodGetCurrentVolume,
    );
  }

  static Future<void> setCurrentVolume({
    required int currentVolume,
    required int treble,
    required int bass,
  }) async {
    await BleBaseManager.invokeMethod(
      BleMethodConstants.methodSetVolume,
      arguments: {
        BleMethodConstants.argCurrentVol: currentVolume,
        BleMethodConstants.argTreble: treble,
        BleMethodConstants.argBass: bass
      },
    );
  }
}

Source: ble_volume_manager.dart

要点分析:

  • getCurrentVolume():无参查询,方法名取自 BleMethodConstants.methodGetCurrentVolume。查询结果不通过返回值返回,而是由设备随后上报 typeVolumeChanged 事件——这是典型的异步「指令-事件」配对模式:查询指令发出后,订阅方等待对应事件即可。
  • setCurrentVolume({currentVolume, treble, bass}):一次调用同时设置当前音量、高音、低音三个参数,说明设备侧的音量指令是一个复合指令(音量与音频输出控制共用一条指令通道)。三个参数均为 required,Dart 编译器层面强制调用方显式传入,避免遗漏某个频段导致设备端状态不一致。
  • 方法返回 Future<void> 且 await 了 invokeMethod,即只等待「指令已送达原生层」,并不等待设备确认——设备确认仍以事件形式异步返回。这也是为什么该 API 无需透传错误码。

2. 事件接收:BleVolumeProcessor

BleVolumeProcessor 是音量事件的唯一订阅入口。它复用公共事件总线 BleBaseEventProcessor.filterByType,按事件类型过滤后把原始 Map 映射为强类型模型,再以 Stream 暴露给业务方。

/// Volume event processor
class BleVolumeProcessor {
  static Stream<VolumeInfo> get volumeInfoStream {
    return BleBaseEventProcessor.filterByType(
      BleEventConstants.typeVolumeChanged,
    ).map((event) {
      final data = BleBaseEventProcessor.getValueFromEvent(event);

      return VolumeInfo.fromMap({
        BleEventConstants.keyMaxVol:
            data[BleEventConstants.keyMaxVol] as int? ?? 0,
        BleEventConstants.keyCurrentVol:
            data[BleEventConstants.keyCurrentVol] as int? ?? 0,
        BleEventConstants.keySupportVolumeSync:
            data[BleEventConstants.keySupportVolumeSync] as bool? ?? false,
      });
    });
  }

  static Stream<VolumeCtrlInfo> get volumeCtrlStream {
    return BleBaseEventProcessor.filterByType(
      BleEventConstants.typeHeightBassChanged,
    ).map((event) {
      final data = BleBaseEventProcessor.getValueFromEvent(event);

      return VolumeCtrlInfo.fromMap({
        BleEventConstants.keyTreble:
        data[BleEventConstants.keyTreble] as int? ?? 0,
        BleEventConstants.keyBass:
        data[BleEventConstants.keyBass] as int? ?? 0
      });
    });
  }
}

Source: ble_volume_processor.dart

要点分析:

  • 两个事件类型对应两条 Stream:typeVolumeChanged → volumeInfoStream(音量状态);typeHeightBassChanged → volumeCtrlStream(高低音状态)。设备端把「音量」与「高低音」作为两个独立事件上报,因此消费方可以分别订阅,互不阻塞。
  • 解析防御:as int? ?? 0、as bool? ?? false 表明原生侧字段可能缺失或类型不符,解析层兜底为默认值,保证 Stream 永不因单字段异常而中断——这是跨语言桥接中常见的容错设计。
  • 映射职责独立:processor 只负责「取数据 + 转模型」,不持有任何 UI 状态,因此多个页面可以同时订阅同一 Stream 而互不干扰(filterByType 返回的流可被多路监听)。

3. 数据模型

3.1 VolumeInfo —— 音量快照

/// Volume info model
class VolumeInfo {
  final int maxVol; // 最大音量
  final int volume; // 当前音量
  final bool supportVolumeSync; // 是否支持音量同步

  VolumeInfo({
    required this.maxVol,
    required this.volume,
    required this.supportVolumeSync,
  });

  factory VolumeInfo.fromMap(Map<String, dynamic> map) {
    return VolumeInfo(
      maxVol: map[BleEventConstants.keyMaxVol] ?? 0,
      volume: map[BleEventConstants.keyCurrentVol] ?? 0,
      supportVolumeSync: map[BleEventConstants.keySupportVolumeSync] ?? false,
    );
  }

  Map<String, dynamic> toMap() {
    return {
      BleEventConstants.keyMaxVol: maxVol,
      BleEventConstants.keyCurrentVol: volume,
      BleEventConstants.keySupportVolumeSync: supportVolumeSync,
    };
  }
}

Source: volume_info.dart

supportVolumeSync 是音量同步能力的标志位:部分设备支持「手机调音量 ↔ 设备实际音量」双向同步,业务方可据此决定是否在 UI 上显示联动提示或禁用本地滑杆。fromMap 与 toMap 成对出现,保证同一模型既能从原生事件反序列化,也能被序列化回传(例如用于状态持久化或日志)。

3.2 VolumeCtrlInfo —— 音频输出控制快照

/// Volume ctrl model
class VolumeCtrlInfo {
  final int high; // 高音
  final int bass; // 低音

  VolumeCtrlInfo({
    required this.high,
    required this.bass
  });

  factory VolumeCtrlInfo.fromMap(Map<String, dynamic> map) {
    return VolumeCtrlInfo(
      high: map[BleEventConstants.keyTreble] ?? 0,
      bass: map[BleEventConstants.keyBass] ?? 0
    );
  }

  Map<String, dynamic> toMap() {
    return {
      BleEventConstants.keyTreble: high,
      BleEventConstants.keyBass: bass
    };
  }
}

Source: volume_ctrl_info.dart

注意 VolumeCtrlInfo.high 在序列化时使用的 key 是 keyTreble(高音),即模型字段名与协议字段名并不一一对应:Dart 侧用更直观的 high,协议层用 treble。这种命名隔离说明模型是面向业务语义设计的,协议细节被封装在常量层。

4. 常量约定

方法名、参数 key、事件类型、事件字段 key 全部集中在 BleMethodConstants(ble_method_constants.dart)与 BleEventConstants(ble_event_constants.dart)两个常量类中,本页涉及的常量如下(名称取自上述源码中的实际引用):

常量值语义所属常量类使用位置
methodGetCurrentVolume查询当前音量指令BleMethodConstantsgetCurrentVolume()
methodSetVolume设置音量/高音/低音指令BleMethodConstantssetCurrentVolume()
argCurrentVol指令参数:当前音量BleMethodConstantssetCurrentVolume()
argTreble指令参数:高音BleMethodConstantssetCurrentVolume()
argBass指令参数:低音BleMethodConstantssetCurrentVolume()
typeVolumeChanged音量变化事件类型BleEventConstantsvolumeInfoStream 过滤
typeHeightBassChanged高低音变化事件类型BleEventConstantsvolumeCtrlStream 过滤
keyMaxVol事件字段:最大音量BleEventConstantsVolumeInfo.fromMap
keyCurrentVol事件字段:当前音量BleEventConstantsVolumeInfo.fromMap
keySupportVolumeSync事件字段:是否支持音量同步BleEventConstantsVolumeInfo.fromMap
keyTreble事件字段:高音BleEventConstantsVolumeCtrlInfo.fromMap
keyBass事件字段:低音BleEventConstantsVolumeCtrlInfo.fromMap

统一常量层的价值在于:原生与 Flutter 之间以字符串协议通信,任何一处硬编码字符串都可能导致「指令发了但事件收不到」的静默故障;集中定义后,IDE 重构与编译期检查可以覆盖全部引用点。

核心流程

指令-事件闭环(设置音量)

sequenceDiagram
    participant UI as 业务页面
    participant Mgr as BleVolumeManager
    participant Base as BleBaseManager<br/>(MethodChannel)
    participant Native as 原生 BLE SDK<br/>(Android / iOS)
    participant Dev as 蓝牙设备
    participant Bus as BleBaseEventProcessor<br/>(事件总线)
    participant Proc as BleVolumeProcessor
    participant Model as VolumeInfo / VolumeCtrlInfo

    UI->>Mgr: setCurrentVolume(currentVolume, treble, bass)
    activate Mgr
    Mgr->>Base: invokeMethod(methodSetVolume,<br/>{argCurrentVol, argTreble, argBass})
    Base->>Native: MethodChannel 调用
    Native->>Dev: 下发 BLE 音量设置指令
    Dev-->>Native: 音量状态回包
    Native-->>Bus: 原生事件回调<br/>(volumeChanged / heightBassChanged)
    deactivate Mgr
    Bus->>Proc: filterByType 过滤事件
    Proc->>Model: fromMap 解析
    Model-->>UI: Stream 推送<br/>(volumeInfoStream / volumeCtrlStream)
    UI->>UI: 更新音量滑杆 / 高低音显示

流程关键点

  1. 指令方向是「调用链」:UI → BleVolumeManager → BleBaseManager.invokeMethod → MethodChannel → 原生 SDK → 设备。每一步都是同步 await 的异步调用,但不等待设备回包。
  2. 事件方向是「事件总线」:设备回包 → 原生 SDK 回调 → BleBaseEventProcessor 统一入总线 → BleVolumeProcessor 按 filterByType 过滤 → .map 转模型 → Stream 推送 UI。两条方向完全解耦,这也是为什么查询音量时 getCurrentVolume() 的返回值是 Future<void>——查询结果永远通过事件流回来。
  3. 查询与设置的配对:业务方执行「查询 → 订阅流 → 等首个事件」是标准用法;若设备支持音量同步(supportVolumeSync == true),后续用户在耳机侧调节音量也会触发 typeVolumeChanged,UI 无需主动轮询。

使用示例

基本用法:查询并监听音量状态

// 1. 先订阅事件流(防止错过设备回包)
BleVolumeProcessor.volumeInfoStream.listen((info) {
  // info.maxVol 最大音量 / info.volume 当前音量 / info.supportVolumeSync 是否支持同步
  print('max=${info.maxVol}, current=${info.volume}, sync=${info.supportVolumeSync}');
});

// 2. 再下发查询指令
await BleVolumeManager.getCurrentVolume();

Source: ble_volume_processor.dart 与 ble_volume_manager.dart

高级用法:设置音量与高低音、订阅音频输出控制

// 订阅高低音变化事件
BleVolumeProcessor.volumeCtrlStream.listen((ctrl) {
  // ctrl.high 高音 / ctrl.bass 低音
  print('high=${ctrl.high}, bass=${ctrl.bass}');
});

// 一次调用同时设置当前音量、高音、低音
await BleVolumeManager.setCurrentVolume(
  currentVolume: 80,
  treble: 10,
  bass: 6,
);

Source: ble_volume_manager.dart 与 ble_volume_processor.dart

提示:仓库中的示例工程 code/JieLi_Home_Demo/example/lib/pages/volume_page.dart 提供了完整的音量设置页面 Demo(滑杆 + 高低音调节),可直接作为业务接入参考。

API 参考

BleVolumeManager

方法签名说明参数返回异常
static Future<void> getCurrentVolume()下发查询当前音量指令;结果通过 volumeInfoStream 事件返回无仅表示指令已送达原生层由 BleBaseManager.invokeMethod 抛出的平台通道异常
static Future<void> setCurrentVolume({required int currentVolume, required int treble, required int bass})同时设置当前音量、高音、低音currentVolume:当前音量(0~maxVol);treble:高音;bass:低音仅表示指令已送达原生层同上;参数为 required,缺失会在编译期报错

BleVolumeProcessor

属性类型说明
static Stream<VolumeInfo> get volumeInfoStreamStream<VolumeInfo>音量变化事件流;每次设备上报音量状态推出一条 VolumeInfo
static Stream<VolumeCtrlInfo> get volumeCtrlStreamStream<VolumeCtrlInfo>高低音变化事件流;每次设备上报推出一条 VolumeCtrlInfo

数据模型

类构造关键方法字段
VolumeInfoVolumeInfo({required maxVol, required volume, required supportVolumeSync})factory fromMap(Map) / Map toMap()int maxVol、int volume、bool supportVolumeSync
VolumeCtrlInfoVolumeCtrlInfo({required high, required bass})factory fromMap(Map) / Map toMap()int high(协议 key 为 treble)、int bass

失败模式与边界情况

  • 字段缺失/类型不符兜底:VolumeInfo.fromMap 与 VolumeCtrlInfo.fromMap 对每个字段使用 ?? 0 / ?? false 兜底,BleVolumeProcessor 内还额外做了 as int? / as bool? 强转。含义是:即使原生侧事件数据不完整,Stream 仍会推送一条「默认值模型」,业务侧不应假设模型字段必然有效,尤其是 volume == 0 并不代表设备静音,需结合 maxVol 判断。
  • 先订阅后下发:由于查询结果只通过事件流返回,若业务方先调 getCurrentVolume() 再订阅 volumeInfoStream,可能错过回包事件导致 UI 不更新。正确顺序是先 listen 再发指令(见使用示例)。
  • 指令 Fire-and-Forget 的语义:setCurrentVolume 只 await 到「指令送达原生层」,设备是否真正生效、参数是否越界(如音量超过 maxVol)均不通过返回值反馈,业务方只能依赖后续事件或设备行为自行校验。
  • 设备侧主动调音量:用户直接在耳机/音箱上按键调音量时,设备会主动上报 typeVolumeChanged。若 supportVolumeSync == false,UI 上应避免用本地滑杆值覆盖设备值或反向猜测设备状态,否则会出现滑杆与真实音量漂移。
  • 断连/未连接场景:本页 API 不校验蓝牙连接状态,指令会直接走 BleBaseManager.invokeMethod;未连接设备时由原生层决定忽略或抛平台异常(具体行为取决于 BleBaseManager 与原生 SDK 的实现)。

并发与性能

  • 静态无状态设计:BleVolumeManager / BleVolumeProcessor 不含可变成员,多个页面并发调用或订阅不会产生共享状态竞争,天然线程安全。
  • 冷流多订阅:filterByType 派生出的 Stream 可被多个监听者同时订阅,每个订阅者独立收到事件;处理器本身不做缓存或重放,晚订阅者不会收到历史事件——需要「最近一次音量状态」的业务方应自行缓存。
  • 事件频率:音量变化事件可能由设备高频上报(如长按音量键连续调节)。processor 的解析逻辑为 O(1) 的 Map 读取,开销可忽略;若 UI 侧需要节流(如滑杆动画),建议在业务层使用 debounce,本页代码未内置该机制。

扩展点

  • 新增音量相关事件:在 BleEventConstants 中新增事件类型常量,然后在 BleVolumeProcessor 中仿照 volumeInfoStream 增加一条 filterByType(新类型).map(...) 的 Stream getter 即可;事件总线与桥接层无需任何改动。
  • 新增指令:在 BleMethodConstants 中新增方法名与参数 key,在 BleVolumeManager 中新增静态方法调用 BleBaseManager.invokeMethod,并同步在 Android/iOS 原生 SDK 的 Volume Manager 中注册对应方法处理。
  • 自定义解析:若业务需要原始 Map 而非强类型模型,可直接订阅 BleBaseEventProcessor.filterByType(...) 拿原始事件数据,processor 的模型映射不是必经之路。
  • 原生侧扩展:Android 侧可参考 code/JieLi_Home_Demo/android/src/main/kotlin/com/jieli/bt/sdk/data/manager/VolumeManager.kt 及模型 VolumeInfo.java / VolumeCtrl.java;iOS 侧可参考 code/JieLi_Home_Demo/ios/Classes/Manager/VolumeManager.swift、VolumeEventProcessor.swift 与 JL_SystemVolume.h。两边桥接类均与本页 Flutter 类同名同职责,扩展时需保持方法名与参数 key 一致。

相关链接

  • 公共桥接基座:BleBaseManager / BleBaseEventProcessor(连接管理、事件总线,见 4-music-media 目录下连接相关页面)
  • 示例页面:volume_page.dart(音量设置完整 Demo)
  • 命令下发:ble_volume_manager.dart
  • 事件处理:ble_volume_processor.dart
  • 数据模型:volume_info.dart、volume_ctrl_info.dart
  • 原生桥接:Android VolumeManager.kt / iOS VolumeManager.swift
Prev
设备音乐与手机音乐播放控制