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

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

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

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

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

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

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

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

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

Auracast 音频广播

Auracast(LE Audio 音频广播)是蓝牙 5.2+ 规范中的广播音频功能,本页面介绍 JL SDK(jl_home)中 Auracast 从 Flutter 侧到原生 BLE 层的完整实现:发送接口 BleAuraCastManager、接收事件流 BleEventStream、示例应用 AuraCastReceiverManager 以及 Android/iOS 原生管理器。

Purpose and Scope

本页面覆盖 Auracast 音频广播能力在 JieLi_Home_Demo 仓库中的完整链路:

  • Flutter SDK 层:BleAuraCastManager(命令发送)与 BleEventStream(状态/数据接收)的公开 API;
  • 示例应用层:AuraCastReceiverManager(ChangeNotifier 状态管理)、aura_cast_receiver_page.dart 页面与 aura_cast_animated_icon.dart 动画组件;
  • 原生桥接层:Android 侧 AuraCastManager / AuraCastEventSender / AuraCastObserverManager,以及 iOS JL_BLEKit.framework 中的 JLAuracastManager 系列头文件;
  • 数据模型与枚举:广播列表、记录状态、连接失败原因等。

以下内容不在此页面展开,由兄弟页面承载:蓝牙基础连接/配对(见"设备连接"相关页面)、EQ 音效(见"音频效果"目录下的其他页面)、TWS 等音频分发能力。

Overview

Auracast 允许音频源设备(如手机、电视)通过 LE Audio 广播音频流,任意"接收者"(耳机/音箱)可在附近扫描并收听广播,无需配对。本仓库将其落地为一条完整的 SDK 能力链:

  1. 扫描与发现:原生层启动 BLE 广播扫描,将附近 Auracast 广播列表上报到 Flutter;
  2. 订阅与同步:用户点击某个广播(可带密码),原生层建立广播同步(BIS 同步),期间持续上报同步状态;
  3. 记录管理:已同步过的广播会进入"我的设备"记录列表,支持再次点击加入、离开、移除;
  4. 失败反馈:密码错误、超时等失败原因通过事件流回传,UI 层据此弹出对应提示。

SDK 层通过 BleBaseManager.invokeMethod 走 MethodChannel 与原生通信;Flutter 侧职责被拆分为"发送命令"(BleAuraCastManager)与"接收事件"(BleEventStream)两个通道,这正是本仓库 SDK 的统一设计模式——命令与方法一一对应,事件以 Stream 形式订阅。

Architecture

flowchart TD
    subgraph sg_UI["示例应用层 (example)"]
        Page["aura_cast_receiver_page.dart"]
        Manager["AuraCastReceiverManager<br/>(ChangeNotifier)"]
        Icon["aura_cast_animated_icon.dart"]
        Toast["ToastUtils / LoadingDialogWidget"]
    end

    subgraph sg_SDK["jl_home SDK (Flutter)"]
        SendMgr["BleAuraCastManager<br/>(发送接口)"]
        EventStream["BleEventStream<br/>(接收接口)"]
        Proc["BleAuraCastProcessor"]
        BaseMgr["BleBaseManager.invokeMethod"]
        Models["AuraCastBroadcastModel /<br/>AuraCastRecordStateModel"]
    end

    subgraph sg_Native["原生桥接层"]
        Android["Android: AuraCastManager /<br/>AuraCastEventSender /<br/>AuraCastObserverManager"]
        iOS["iOS: JLAuracastManager /<br/>JLAuracastLancerManager"]
    end

    subgraph sg_BLE["BLE 协议层"]
        LE["LE Audio 广播/同步<br/>(BIS/PA)"]
    end

    Page --> Manager
    Manager --> SendMgr
    Manager --> EventStream
    Manager --> Toast
    Page --> Icon
    SendMgr --> BaseMgr
    EventStream --> Proc
    BaseMgr -->|"MethodChannel"| Android
    BaseMgr -->|"MethodChannel"| iOS
    Android --> LE
    iOS --> LE
    Proc --> Models

架构说明:

  • UI 层(AuraCastReceiverManager)是示例应用唯一与 SDK 交互的入口:它向 BleAuraCastManager 发命令,向 BleEventStream 订阅 5 条事件流,并把结果归一化为 myDevices / nearbyDevices 两个列表供页面渲染。
  • SDK 层遵循"命令/事件分离":BleAuraCastManager 的方法名与 BleMethodConstants 中的方法常量一一对应(如 methodAuraCastBroadClickIndex);BleEventStream 则把 BleAuraCastProcessor 的底层流原样暴露给上层。
  • 原生层负责真正的 BLE 操作:Android 的 AuraCastManager 承载扫描/同步逻辑,AuraCastEventSender 把事件回传 Flutter,AuraCastObserverManager 管理观察者注册;iOS 侧由 JL_BLEKit.framework 的 JLAuracastManager 等类实现同等能力。

SDK 层实现详解

发送接口:BleAuraCastManager

BleAuraCastManager 位于 libs/Send Interface/ble_aura_cast_manager.dart,是 Auracast 的全部命令入口。所有方法都是 static,内部统一委托给 BleBaseManager.invokeMethod,以方法名常量 + 参数 Map 的形式调用原生层:

import '../ble_base_manager.dart';
import '../constant/ble_method_constants.dart';

/// AuraCast Manager
class BleAuraCastManager {
  static Future<void> auraCastGetRecordList() async {
    await BleBaseManager.invokeMethod(
      BleMethodConstants.methodAuraCastGetRecordList,
    );
  }

  static Future<bool> sendAuraCastSwitchState(bool state) async {
    return await BleBaseManager.invokeMethod(
      BleMethodConstants.methodAuraCastSwitchState,
      arguments: {BleMethodConstants.argAuraCastSwitchState: state},
    );
  }

  static Future<void> clickAuraCastRecordBroadcast(int index) async {
    await BleBaseManager.invokeMethod(
      BleMethodConstants.methodAuraCastBroadRecordClickIndex,
      arguments: {
        BleMethodConstants.argAuraCastBroadRecordClickIndex: index
      },
    );
  }

  static Future<void> clickAuraCastRecordLeaveBroadcast(int index) async {
    await BleBaseManager.invokeMethod(
      BleMethodConstants.methodAuraCastBroadRecordLeaveClickIndex,
      arguments: {
        BleMethodConstants.argAuraCastBroadRecordLeaveClickIndex: index
      },
    );
  }

  static Future<void> clickAuraCastRecordRemoveBroadcast(int index) async {
    await BleBaseManager.invokeMethod(
      BleMethodConstants.methodAuraCastBroadRecordRemoveClickIndex,
      arguments: {
        BleMethodConstants.argAuraCastBroadRecordRemoveClickIndex: index
      },
    );
  }

  static Future<void> clickAuraCastBroadcast(int index, String password) async {
    await BleBaseManager.invokeMethod(
      BleMethodConstants.methodAuraCastBroadClickIndex,
      arguments: {
        BleMethodConstants.argAuraCastBroadClickIndex: index,
        BleMethodConstants.argAuraCastBroadPassword: password
      },
    );
  }
}

Source: ble_aura_cast_manager.dart

设计意图:方法被拆成三组语义——查询(auraCastGetRecordList)、开关(sendAuraCastSwitchState,返回 bool 表示原生是否成功切换扫描状态)、点击操作(连接/离开/移除,均以记录列表中的 index 定位目标)。点击类方法不返回结果,成败完全依赖事件流回传,因此 UI 层必须同时订阅状态流,这是"命令-事件"异步模型的核心约定。

接收接口:BleEventStream 的 Auracast 事件流

BleEventStream(libs/Receive Interface/ble_event_stream.dart)把 BleAuraCastProcessor 的底层流统一封装为 5 条静态 Stream,覆盖 Auracast 全部上行事件:

// Get auraCast scan state
static Stream<bool> get auraScanStateStream =>
    BleAuraCastProcessor.auraScanStateStream;

// Get auraCast broadcast list
static Stream<List<AuraCastBroadcastModel>> get auraBroadcastListStream =>
    BleAuraCastProcessor.auraBroadcastListStream;

// Get auraCast broadcast state
static Stream<int> get auraSyncStateStream =>
    BleAuraCastProcessor.auraSyncStateStream;

// Get auraCast broadcast connect fail
static Stream<(String broadcastName, int errorCode)> get auraConnectFailedStream =>
    BleAuraCastProcessor.auraConnectFailedStream;

// Get auraCast broadcast record list
static Stream<List<AuraCastRecordStateModel>> get auraBroadcastRecordListStream =>
    BleAuraCastProcessor.auraBroadcastRecordListStream;

Source: ble_event_stream.dart

各流语义如下:

事件流类型触发时机
auraScanStateStreamStream<bool>原生扫描开关状态变化(true = 扫描中)
auraBroadcastListStreamStream<List<AuraCastBroadcastModel>>附近广播列表刷新
auraSyncStateStreamStream<int>广播同步状态变化(对应 SyncState 枚举下标)
auraConnectFailedStreamStream<(String, int)>连接/同步失败,携带广播名与错误码
auraBroadcastRecordListStreamStream<List<AuraCastRecordStateModel>>"我的设备"记录列表更新

ble_event_stream.dart 顶部还引入了 model/auracast_broadcast_model.dart 与 model/auracast_reecord_state_model.dart 两个数据模型,说明 SDK 层为 Auracast 专门定义了广播模型与记录状态模型,事件流的泛型即由这两个模型承载。

设计意图:把 5 条流直接暴露而非封装成回调,使 UI 层可以按需订阅、用 StreamSubscription 精确管理生命周期;auraConnectFailedStream 使用 Dart 3 的 record 类型 (String, int) 打包"广播名 + 错误码",避免为单次失败事件新建 DTO。

数据模型与枚举

示例应用层定义了与 SDK 事件流对齐的枚举(见 aura_cast_receiver_manager.dart):

enum SyncState {
  syncing,
  idle,
  syncOk,
}

enum ConnectFailReason {
  badPassword,
  timeout,
  other;

  static ConnectFailReason fromInt(int value) {
    switch (value) {
      case 0:
        return ConnectFailReason.badPassword;
      case 1:
        return ConnectFailReason.timeout;
      default:
        return ConnectFailReason.other;
    }
  }
}

enum StateCode {
  idle,
  syncing,
  syncOk,
}

enum AuraCastDeviceState {
  normal,
  leave,
}

Source: aura_cast_receiver_manager.dart

错误码映射规则(ConnectFailReason.fromInt):0 = 密码错误(badPassword),1 = 超时(timeout),其余一律归为 other。该映射由原生层错误码定义驱动,UI 层据此选择多语言提示文案。

示例应用实现详解:AuraCastReceiverManager

AuraCastReceiverManager 继承 ChangeNotifier,是示例应用 Auracast 页面的状态中枢(位于 code/JieLi_Home_Demo/example/lib/manager/aura_cast_receiver_manager.dart)。它把 SDK 的 5 条事件流与 6 个命令方法归一化为页面可直接绑定的两个列表:myDevices(我的设备/记录)与 nearbyDevices(附近广播)。

初始化与生命周期

void init(BuildContext ctx) {
  context = ctx;
  BleAuraCastManager.auraCastGetRecordList();
  _listenAllStreams();
}

/// 统一订阅所有流
void _listenAllStreams() {
  _listenToScanState();
  _listenToBroadcastList();
  _listenToBroadCastState();
  _listenToConnectFailed();
  _listenToBroadcastRecordList();
}

void disposeSubscriptions() {
  _scanStateSubscription?.cancel();
  _broadcastListSubscription?.cancel();
  _auraCastStateSubscription?.cancel();
  _connectFailedSubscription?.cancel();
  _recordListSubscription?.cancel();
  myDevices.clear();
  nearbyDevices.clear();
}

Source: aura_cast_receiver_manager.dart

init 先主动拉取一次记录列表(auraCastGetRecordList),再统一订阅 5 条流;disposeSubscriptions 必须成对调用以取消订阅并清空列表,避免页面销毁后收到原生事件导致内存泄漏或对已卸载 BuildContext 操作崩溃。

事件归一化逻辑

每条流的监听都做了"SDK 模型 → UI 展示数据"的转换,是理解整个页面的关键:

void _listenToBroadcastList() {
  _broadcastListSubscription = BleEventStream.auraBroadcastListStream.listen(
        (broadcastList) {
      nearbyDevices = broadcastList.map((model) {
        return {
          BleEventConstants.keyBroadCastName: model.broadcastName,
          BleEventConstants.keyIsEncrypted: model.isEncrypted,
        };
      }).toList();
      notifyListeners();
    },
  );
}

void _listenToBroadcastRecordList() {
  _recordListSubscription =
      BleEventStream.auraBroadcastRecordListStream.listen((recordList) {
        bool hasSyncing = recordList.any(
            (r) => r.syncState == StateCode.syncing.index);

        if (hasSyncing) {
          _showLoadingDialog();
        } else {
          _dismissLoadingDialog();
        }

        myDevices = recordList.map((record) {
          bool isOk = record.syncState == StateCode.syncOk.index;
          return {
            BleEventConstants.keyBroadCastName:
            record.broadcast?.broadcastName,
            BleEventConstants.keyPlayAnimationState: isOk,
            BleEventConstants.keyIsFoundBroadCast:
            record.isFoundBroadcast,
            BleEventConstants.keyAuraCastDeviceState:
            isOk ? AuraCastDeviceState.normal : AuraCastDeviceState.leave,
          };
        }).toList();
        notifyListeners();
      });
}

Source: aura_cast_receiver_manager.dart

要点:

  • 附近列表只保留 broadcastName(广播名)与 isEncrypted(是否加密)——加密项在 UI 上需要弹出密码输入框;
  • 记录列表把 syncState == StateCode.syncOk 映射为"播放动画状态"与 AuraCastDeviceState.normal;未同步成功则标记为 leave(已离开);
  • 只要记录中存在任一 syncing 项,就弹全局 Loading 对话框(LoadingDialogWidget),全部同步完成才关闭——用"任一同步中"来驱动遮罩,避免多次弹窗竞态。

用户操作入口

Future<bool> sendSwitchState(bool value) async {
  return await BleAuraCastManager.sendAuraCastSwitchState(value);
}

Future<void> clickRecordBroadcast(int index) async {
  notifyListeners();
  await BleAuraCastManager.clickAuraCastRecordBroadcast(index);
}

Future<void> clickNearbyBroadcast(int index, String password) async {
  notifyListeners();
  await BleAuraCastManager.clickAuraCastBroadcast(index, password);
}

Source: aura_cast_receiver_manager.dart

点击类操作在发送前先 notifyListeners() 立即刷新 UI(如切换 loading 状态),再异步等待原生执行——这是"乐观更新"策略,保证交互即时反馈。

连接失败提示

void _listenToConnectFailed() {
  _connectFailedSubscription = BleEventStream.auraConnectFailedStream.listen(
        (failInfo) {
      // 缓存安全 context
      final ctx = context;
      if (!ctx.mounted) return;

      final (name, code) = failInfo;
      final loc = AppLocalizations.of(ctx)!;
      final reason = ConnectFailReason.fromInt(code);
      String message;

      switch (reason) {
        case ConnectFailReason.badPassword:
          message = loc.badPasswordTips(name);
          break;
        case ConnectFailReason.timeout:
          message = loc.syncTimeoutTips(name);
          break;
        case ConnectFailReason.other:
          message = loc.syncFailedTips(name);
          break;
      }

      ToastUtils.show(ctx, message);
    },
  );
}

Source: aura_cast_receiver_manager.dart

这里缓存了 BuildContext 并在使用前检查 ctx.mounted,再通过 AppLocalizations 按错误码选择多语言提示文案(badPasswordTips / syncTimeoutTips / syncFailedTips),最后用 ToastUtils.show 展示。这是异步回调中安全使用 Flutter Context 的标准姿势。

Core Flow

sequenceDiagram
    participant U as 用户
    participant P as AuraCastReceiverPage
    participant M as AuraCastReceiverManager
    participant S as BleAuraCastManager (SDK)
    participant E as BleEventStream (SDK)
    participant N as 原生 AuraCastManager

    U->>P: 打开 Auracast 页面
    P->>M: init(context)
    M->>S: auraCastGetRecordList()
    M->>E: 订阅 5 条事件流

    U->>P: 打开广播开关
    P->>M: sendSwitchState(true)
    M->>S: sendAuraCastSwitchState(true)
    S->>N: methodAuraCastSwitchState
    N-->>E: auraScanStateStream(true)
    N-->>E: auraBroadcastListStream(附近广播列表)
    E-->>M: 更新 nearbyDevices / broadcastSwitch
    M-->>P: notifyListeners() 渲染列表

    U->>P: 点击某个加密广播并输入密码
    P->>M: clickNearbyBroadcast(index, password)
    M->>S: clickAuraCastBroadcast(index, password)
    S->>N: methodAuraCastBroadClickIndex + password
    N-->>E: auraSyncStateStream(syncing)
    E-->>M: _showLoadingDialog()
    alt 同步成功
        N-->>E: auraBroadcastRecordListStream(记录 syncOk)
        E-->>M: myDevices 更新, dismiss dialog
        M-->>P: 列表项显示播放动画
    else 同步失败
        N-->>E: auraConnectFailedStream(name, code)
        E-->>M: ConnectFailReason.fromInt(code)
        M-->>P: ToastUtils 提示 密码错误/超时/失败
    end

流程要点:所有 UI 状态变化都来自事件流而非命令返回值(sendAuraCastSwitchState 除外,它返回 bool)。同步期间由"记录列表中存在 syncing 项"驱动 Loading 遮罩;失败路径由 auraConnectFailedStream 携带的 (broadcastName, errorCode) 决定提示文案。这个"命令只管发、状态全靠流"的模型,保证原生层任意时刻的状态变化都能准确反映到 UI。

原生层桥接

MethodChannel 的另一端是各平台的原生实现。仓库中可确认的原生 Auracast 文件如下:

Android(Kotlin/Java,JL SDK)

code/JieLi_Home_Demo/android/src/main/kotlin/com/jieli/bt/sdk/data/manager/auracast/ 目录下:

  • AuraCastManager.kt:Auracast 核心管理器,承接 Flutter 侧 6 个方法(扫描开关、广播点击、记录管理),维护扫描状态与广播列表;
  • AuraCastEventSender.kt:事件发送器,把扫描状态、广播列表、同步状态、连接失败、记录列表五类事件通过通道回调到 Flutter,是 BleEventStream 各条流的原生源头;
  • AuraCastObserverManager.kt:观察者注册/注销管理,向 SDK 的扫描与同步回调注册监听,再转发给 AuraCastEventSender。

data/model/auracast/ 下还有配套模型:AuraCastAssistantViewModel.java、AuraCastReceiverViewModel.kt(接收者视图模型)、AuraCastRecordOp.java(记录操作)、AuracastRecordState.java(记录状态,对应 Flutter 侧 AuraCastRecordStateModel)。

iOS(JL_BLEKit.framework)

code/JieLi_Home_Demo/example/ios/JL_BLEKit.framework/Headers/ 下的头文件:

  • JLAuracastManager.h:iOS 侧 Auracast 管理器,与 Android AuraCastManager 对应;
  • JLAuracastLancerManager.h 与 JLAuracastLancerSettingMode.h:广播发起者(Lancer)管理与设置模型,对应"作为音频源发起广播"的场景;
  • JLAuracastDevStateModel.h:设备状态模型。

注:Android/iOS 原生文件仅经目录与文件清单确认,其内部实现细节未在本次文档生成过程中逐行阅读;如需精确的原生流程,请直接查阅上述文件。

配置与常量

示例应用通过 BleEventConstants 定义列表 Map 的键名,UI 层与数据归一化共用同一组常量:

常量键含义使用位置
keyBroadCastName广播名称nearbyDevices / myDevices 列表项
keyIsEncrypted是否加密广播附近列表项,决定是否弹密码输入
keyPlayAnimationState是否播放同步成功动画myDevices 列表项,驱动 aura_cast_animated_icon.dart
keyIsFoundBroadCast广播是否仍被发现myDevices 列表项
keyAuraCastDeviceState设备状态(normal/leave)myDevices 列表项,绑定 AuraCastDeviceState

SDK 侧的方法常量(BleMethodConstants)与参数常量(argAuraCast*)在 ble_aura_cast_manager.dart 中被引用,构成了与原生层的协议契约:

方法常量参数常量语义
methodAuraCastGetRecordList—拉取广播记录列表
methodAuraCastSwitchStateargAuraCastSwitchState设置扫描开关
methodAuraCastBroadRecordClickIndexargAuraCastBroadRecordClickIndex点击记录中的广播(重新加入)
methodAuraCastBroadRecordLeaveClickIndexargAuraCastBroadRecordLeaveClickIndex离开记录中的广播
methodAuraCastBroadRecordRemoveClickIndexargAuraCastBroadRecordRemoveClickIndex移除记录
methodAuraCastBroadClickIndexargAuraCastBroadClickIndex、argAuraCastBroadPassword点击附近广播并携带密码

API Reference

BleAuraCastManager(发送接口)

方法签名参数返回说明
auraCastGetRecordList()无Future<void>请求原生返回"我的设备"记录列表,结果经 auraBroadcastRecordListStream 回传
sendAuraCastSwitchState(bool state)state:目标开关状态Future<bool>设置广播扫描开关;返回值为原生执行结果
clickAuraCastRecordBroadcast(int index)index:记录列表下标Future<void>重新加入历史记录中的广播
clickAuraCastRecordLeaveBroadcast(int index)index:记录列表下标Future<void>离开某个已同步广播
clickAuraCastRecordRemoveBroadcast(int index)index:记录列表下标Future<void>从记录中删除广播
clickAuraCastBroadcast(int index, String password)index:附近列表下标;password:广播密码(无密码传空串)Future<void>点击附近广播发起同步

BleEventStream(接收接口,Auracast 部分)

属性类型说明
auraScanStateStreamStream<bool>扫描开关状态;true 表示扫描中
auraBroadcastListStreamStream<List<AuraCastBroadcastModel>>附近广播列表,元素含 broadcastName、isEncrypted
auraSyncStateStreamStream<int>同步状态,取值对应 SyncState:syncing=0、idle=1、syncOk=2
auraConnectFailedStreamStream<(String, int)>连接失败事件;record 第一项为广播名,第二项为错误码(0=密码错误,1=超时)
auraBroadcastRecordListStreamStream<List<AuraCastRecordStateModel>>记录列表,元素含 broadcast、syncState、isFoundBroadcast

失败模式、边界情况与并发

连接/同步失败分类

auraConnectFailedStream 是唯一携带错误码的事件通道,原生错误码被收敛为三种原因(见 ConnectFailReason):

错误码原因UI 提示(i18n key)应对
0密码错误(badPassword)badPasswordTips(name)提示用户重新输入密码,再次调用 clickAuraCastBroadcast
1同步超时(timeout)syncTimeoutTips(name)提示超时,建议重试或检查信号
其他未知失败(other)syncFailedTips(name)通用失败提示

生命周期边界

  • Context 失效:_listenToConnectFailed 在异步回调中缓存 context 并检查 ctx.mounted,未挂载直接返回,防止对已销毁页面操作;
  • 订阅泄漏:页面销毁必须调用 disposeSubscriptions() 取消全部 5 条 StreamSubscription 并清空列表,否则原生事件会继续驱动已不可见的 ChangeNotifier;
  • Loading 竞态:_showLoadingDialog 通过 LoadingDialogWidget.isShowing 判重,并用 addPostFrameCallback 延后到下一帧再弹窗,避免快速连续事件导致重复弹窗;
  • 记录状态转换:记录项只有 syncState == syncOk 才进入 AuraCastDeviceState.normal,否则一律 leave——"同步中"的记录会先显示 Loading 遮罩,待结果返回后一次性收敛为成功或失败,避免中间态闪烁。

并发与一致性

  • 点击类命令(连接/离开/移除)都以 index 定位列表项,UI 层在发送前 notifyListeners() 做乐观更新;若原生快速回推列表刷新事件,nearbyDevices / myDevices 会整体重建,因此页面渲染必须容忍列表项按 index 的重排;
  • 多条流相互独立:扫描状态、广播列表、同步状态可能在同一时刻到达,管理器按流分别处理再统一 notifyListeners(),保证单帧内状态一致;
  • 同步中记录的检测使用 any() 聚合("任一同步中即弹遮罩"),多广播并发同步时遮罩的关闭时机以最后一条记录状态更新为准。

性能与运维注意

  • 事件流是高频通道:广播扫描期间 auraBroadcastListStream 可能频繁推送,UI 层每次全量重建 nearbyDevices Map 列表。若广播数量大,建议在页面层做节流或差分渲染(当前示例应用未做该优化);
  • Loading 遮罩开销:isShowing 全局判重避免重复弹窗,但 addPostFrameCallback 的延迟弹窗在极端快速的成功/失败切换下可能产生一次多余闪烁;
  • 多语言文案:所有提示文案经 AppLocalizations 按错误码映射,新增语言只需补充 badPasswordTips / syncTimeoutTips / syncFailedTips 对应翻译;
  • 原生层责任:扫描启动/停止、BIS 同步、记录持久化均在原生 AuraCastManager(Android)/ JLAuracastManager(iOS)内完成,Flutter 侧只做透传与展示,性能敏感路径应优先在原生排查。

扩展点

  1. 自定义 Auracast 页面:可复制 aura_cast_receiver_page.dart 与 aura_cast_animated_icon.dart 的组合,复用 AuraCastReceiverManager 的状态模型与全部 SDK API,仅替换 UI 布局;
  2. 新增广播类型/操作:在 BleAuraCastManager 增加静态方法 + BleMethodConstants 新增方法常量,并在原生两侧(AuraCastManager.kt / JLAuracastManager.h 实现)同步注册通道处理;
  3. 失败提示定制:ConnectFailReason.fromInt 是 UI 层映射,可按业务需要扩展错误码分支或替换提示组件(当前使用 ToastUtils);
  4. 广播发起(Lancer)场景:iOS 侧已存在 JLAuracastLancerManager / JLAuracastLancerSettingMode,仓库为"作为音频源发起广播"预留了原生能力;Flutter SDK 层是否已封装对应方法需以 BleMethodConstants 与 BleAuraCastManager 最新代码为准。

测试情况说明

本次文档生成受源码读取预算限制,未定位到 Auracast 专属的 Flutter 单元测试/集成测试文件。仓库中该能力的正确性主要依赖:原生层 SDK 回调协议(AuraCastEventSender 五类事件)与示例应用 UI 的联调验证。建议在使用或扩展前,先以真机跑通"扫描 → 加入 → 同步成功/失败 → 记录管理"全链路。

Related Links

  • BleAuraCastManager(发送接口)
  • BleEventStream(接收接口事件流)
  • AuraCastReceiverManager(示例应用状态管理)
  • AuraCastReceiverPage(示例应用页面)
  • AuraCastAnimatedIcon(同步动画组件)
  • Android AuraCastManager
  • iOS JLAuracastManager
  • 相邻能力页:EQ 音效(音频效果目录)、设备连接与配对(连接管理目录)
Prev
音频模式与降噪(ANC)设置