seilon_voice_plugin 0.1.6 copy "seilon_voice_plugin: ^0.1.6" to clipboard
seilon_voice_plugin: ^0.1.6 copied to clipboard

Flutter bridge for the Seilon BLE recorder SDK on Android and iOS.

English | 简体中文

Seilon Voice Plugin (English) #

A Flutter 3.44+ plugin for Seilon BLE SDK 1.0.4. It supports Android API 24+ and iOS 15+, providing device discovery and connection, three-step binding, recorder controls, independent Wi-Fi/TCP networking, file transfer, runtime logs, and raw OPUS frames.

Installation #

dependencies:
  seilon_voice_plugin: ^0.1.6
import 'package:flutter/foundation.dart';
import 'package:seilon_voice_plugin/seilon_voice_plugin.dart';

Quick start #

final plugin = SeilonVoicePlugin();

await plugin.initialize(
  config: const SeilonSdkConfig(
    commandTimeout: Duration(seconds: 8),
  ),
);

final subscription = plugin.events.listen((event) {
  switch (event) {
    case SeilonDeviceFoundEvent(:final device):
      print('Device found: ${device.name} (${device.id})');
    case SeilonDisconnectedEvent(:final reason):
      print('Device disconnected: $reason');
    case SeilonAudioFrameEvent(:final opus):
      print('OPUS frame: ${opus.length} bytes');
    case SeilonLogEvent(:final entry):
      print('[${entry.level.name}] ${entry.source}: ${entry.message}');
    default:
      break;
  }
});

if (defaultTargetPlatform == TargetPlatform.android) {
  final report = await plugin.requestPermissions(
    SeilonPermissionOperation.scan,
  );
  if (!report.granted) {
    throw StateError('Scan requirements are not satisfied.');
  }
}

await plugin.startScan(duration: const Duration(seconds: 30));

// Select a device ID from SeilonDeviceFoundEvent.
final session = await plugin.connect(deviceId);

await session.beginBind();
await session.handshake();
await session.completeBind();

final device = await session.queryDeviceInfo();
print('${device.model} / ${device.firmwareVersion}');

await session.disconnect();
await subscription.cancel();
await plugin.close();

Any omitted SeilonSdkConfig field continues to use the native SDK default. Reinitializing an already initialized SDK with the same configuration is idempotent. Call close() before changing the configuration.

SeilonVoicePlugin #

API Description
initialize(config: ...) Initializes the singleton SDK
startScan(duration: ...) / stopScan() Scans for recorders; the vendor-advertised MAC is preferred as the device ID
connect(deviceId) Connects to a device and returns a SeilonRecorderSession
network Returns a SeilonNetworkClient that does not require a BLE session
disconnect() Actively disconnects the current device
events Broadcast stream for scan, connection, disconnection, log, and OPUS events
checkPermissions(operation) Checks Android permissions and system capabilities
requestPermissions(operation) Requests Android runtime permissions and checks again
clearLogs() Clears current SDK logs
close() Releases the SDK and current session

The session returned by connect() carries a native session token. After reconnecting, an old SeilonRecorderSession is rejected with SeilonErrorCode.notConnected, preventing it from operating on the new session.

SeilonRecorderSession #

Binding and device controls #

API Description
beginBind() Starts binding
handshake() Performs the handshake
completeBind() Completes binding
queryDeviceInfo() Returns a complete SeilonDevice
startRecording() / stopRecording() Starts or stops device recording
powerOff() Powers off the device
setUsbMode(enabled) Changes USB mode
setScreen(enabled:, seconds:, brightness:) Changes screen state, duration, and brightness
setWifiEnabled(enabled) Enables or disables the device hotspot
setWifiCountryCode(code) / queryWifiCountryCode() Writes or reads the two-letter Wi-Fi country code

Wi-Fi, TCP, and channel selection #

// These device commands require the BLE recorder session.
await session.setWifiEnabled(true);
final endpoint = await session.queryTcpEndpoint();

// SDK 1.0.4 platform networking does not require plugin.connect().
final network = plugin.network;
await network.connectWifi(
  ssid: deviceHotspotSsid,
  password: deviceHotspotPassword,
);
await network.connectTcp(host: endpoint.host, port: endpoint.port);

// Select the device command channel on the BLE session when needed.
await session.setChannel(SeilonChannel.tcp);
API Description
plugin.network.connectWifi(ssid:, password:) / disconnectWifi() Connects to or disconnects from the device hotspot without requiring a BLE session
plugin.network.connectTcp(host:, port:) / disconnectTcp() Connects directly to the supplied TCP endpoint without requiring a BLE session
plugin.network.connectTcp() Connects using the endpoint previously passed to network.configureTcp() or the native default
plugin.network.queryDeviceInfo() and control/file methods Uses the connected TCP transport for typed device operations
queryTcpEndpoint() Reads the TCP endpoint from the device
configureTcp(host:, port:) Configures the endpoint that the next TCP connection will actually use
connectTcp() / disconnectTcp() Connects or disconnects TCP
setChannel(SeilonChannel.ble/tcp) Selects the SDK command channel

The session Wi-Fi/TCP methods remain available for source compatibility. New code should use plugin.network for platform Wi-Fi and TCP setup. setWifiCountryCode() accepts a two-letter ISO 3166-1 alpha-2 code such as CN or US; lowercase input is normalized to uppercase.

After enabling the device hotspot, its SSID and password can be parsed from the corresponding log packet:

if (event case SeilonLogEvent(:final entry)) {
  final credentials = SeilonWifiCredentials.tryParsePacketHex(
    entry.rawPacketHex,
  );
  if (credentials != null) {
    print('${credentials.ssid} / ${credentials.password}');
  }
}

File operations #

API Description
queryFileCount() Queries the file count, page count, and per-request length
queryFiles(pageNumber) Queries one file-list page; page numbering starts at 1
downloadFile(fileName) Downloads a file and returns its local path
cancelDownload() Cancels only the local download wait; it does not send a stop command to the recorder
stopFileDownload() Sends the SDK stop-transfer command over the channel that started the download
resumeFileDownload(fileName) Resumes from the received byte count and returns the local path
deleteFile(fileName) Deletes a device file by name
final summary = await session.queryFileCount();

for (var pageNumber = 1; pageNumber <= summary.pageCount; pageNumber++) {
  final page = await session.queryFiles(pageNumber);
  for (final file in page.files) {
    print('${file.fileName}: ${file.fileSizeBytes} bytes');
  }
}

final localPath = await session.downloadFile(fileName);
print('Saved to: $localPath');

Download progress is provided through log events:

plugin.events.listen((event) {
  if (event case SeilonLogEvent()) {
    final progress = event.transferProgress;
    if (progress != null) {
      print('${progress.percent}% '
          '(${progress.receivedBytes}/${progress.totalBytes})');
    }
  }
});

File transfer rules #

  1. Call queryFileCount() first. Its maxContentLength is the length of a single retrieval request.
  2. Call queryFiles(pageNumber) to obtain file names and sizes.
  3. downloadFile(fileName) sends the initial retrieval command once and then waits for actual TCP/BLE data.
  4. The SDK writes chunks continuously in arrival order. It does not reorder by chunk sequence or repeatedly request the next chunk.
  5. By default, the transfer fails after 8 seconds without data. Only an explicit resumeFileDownload(fileName) requests data again from the received byte count.
  6. cancelDownload() only cancels the current local wait. Received data is retained for an explicit resume.
  7. stopFileDownload() asks the recorder to stop sending file data and uses the BLE/TCP channel that started the transfer.

State and events #

Dart event Data
SeilonScanStartedEvent Scan started
SeilonScanStoppedEvent Scan stopped
SeilonDeviceFoundEvent SeilonDevice
SeilonConnectedEvent Connected SeilonDevice
SeilonDisconnectedEvent Device ID, expected-disconnect flag, and reason
SeilonScanFailedEvent SeilonSdkException
SeilonLogEvent SeilonLogEntry, including optional transferProgress
SeilonAudioFrameEvent Raw device OPUS data as Uint8List

The Flutter API does not expose Android Flow or the native SeilonSessionState. Read state through typed method results and events. The SDK does not perform ASR, playback, or OPUS/WAV conversion.

SeilonDevice fields #

After a successful queryDeviceInfo(), the following fields are available:

Field Description
id / name / rssi / connected Plugin device ID, name, signal strength, and connection state
mac / model / firmwareVersion / serialNumber Device MAC, model, firmware version, and serial number
batteryPercent Battery percentage, from 0 to 100
recording / recordingMode / recordingStatus Recording flag, mode, and raw device status
usbModeEnabled / wifiEnabled USB and device Wi-Fi state
storageUsedMb / storageTotalMb Used and total EMMC capacity in MB
recordFormat Current recording format; existing devices return Opus
capacityAlertValue Low-capacity alert threshold, from 0 to 20
charging Whether the device is charging
recordingFileName Current recording file name, empty when not recording
headsetPrimaryMac Primary headset MAC, empty when not transmitted
systemDurationSeconds System duration in seconds
screenBrightness Screen brightness, from 0 to 100

Errors and timeouts #

Failed calls consistently throw SeilonSdkException:

try {
  await session.queryFiles(1);
} on SeilonSdkException catch (error) {
  print('${error.code.name}: ${error.message}');
}

Stable error codes include busy, invalidArgument, notConnected, permissionDenied, bluetoothUnavailable, wifiUnavailable, sendFailed, timeout, parseFailed, disconnected, fileTransferFailed, cancelled, unsupportedFeature, and internal.

  • All device replies ignore seq.
  • Normal commands match only by source channel and category + command.
  • A session permits only one pending normal operation at a time. Concurrent operations return busy.
  • Default timeouts are 8 seconds for normal commands, 45 seconds for enabling Wi-Fi, 60 seconds for platform Wi-Fi/TCP connections, and 8 seconds for file-transfer inactivity.
  • Override these values with commandTimeout, wifiCommandTimeout, networkTimeout, and transferIdleTimeout in SeilonSdkConfig.

Platform setup #

Android #

  • Minimum Flutter version: 3.44 (Dart 3.12).
  • Minimum Android API level: 24.
  • Android uses the Flutter 3.44 AGP 9 migration layout without directly applying KGP in the plugin or app module.
  • The AAR manifest declares the required Bluetooth, location, Wi-Fi, network, and TCP permissions.
  • Use checkPermissions / requestPermissions for runtime permissions. The plugin does not automatically enable Bluetooth or Wi-Fi.

iOS #

  • Minimum iOS version: 15. CocoaPods and Swift Package Manager are supported.
  • The host Info.plist must include Bluetooth and local-network usage descriptions:
<key>NSBluetoothAlwaysUsageDescription</key>
<string>Used to discover and connect to Seilon recording devices.</string>
<key>NSLocalNetworkUsageDescription</key>
<string>Used to connect to the local TCP service provided by Seilon devices.</string>
  • Enable the Hotspot Configuration capability for the App ID and target when connecting to the device hotspot.

Lifecycle and limitations #

  • One plugin instance manages one native SDK, one device session, and one independent network client.
  • The plugin does not bind, handshake, reconnect, or change BLE/TCP channels automatically.
  • Cancel the event subscription and call close() when leaving the owning page or application container.
  • The SDK does not include OTA, DNR, ASR, sharing, OPUS playback, or OPUS/WAV conversion.
  • See example/ for the complete test panel and controlled delete/power-off flows.

English | 简体中文

Seilon Voice Plugin (Simplified Chinese) #

Seilon BLE SDK 1.0.4 的 Flutter 3.44+ 插件,支持 Android API 24+ 与 iOS 15+。插件提供设备扫描与连接、三步绑定、录音设备控制、独立 Wi-Fi/TCP 网络客户端、文件传输、运行日志和原始 OPUS 帧。

安装 #

dependencies:
  seilon_voice_plugin: ^0.1.6
import 'package:flutter/foundation.dart';
import 'package:seilon_voice_plugin/seilon_voice_plugin.dart';

基本使用 #

final plugin = SeilonVoicePlugin();

await plugin.initialize(
  config: const SeilonSdkConfig(
    commandTimeout: Duration(seconds: 8),
  ),
);

final subscription = plugin.events.listen((event) {
  switch (event) {
    case SeilonDeviceFoundEvent(:final device):
      print('发现设备:${device.name} (${device.id})');
    case SeilonDisconnectedEvent(:final reason):
      print('设备断开:$reason');
    case SeilonAudioFrameEvent(:final opus):
      print('收到 OPUS:${opus.length} bytes');
    case SeilonLogEvent(:final entry):
      print('[${entry.level.name}] ${entry.source}: ${entry.message}');
    default:
      break;
  }
});

if (defaultTargetPlatform == TargetPlatform.android) {
  final report = await plugin.requestPermissions(
    SeilonPermissionOperation.scan,
  );
  if (!report.granted) {
    throw StateError('扫描权限未满足');
  }
}

await plugin.startScan(duration: const Duration(seconds: 30));

// 从 SeilonDeviceFoundEvent 中选择设备 ID。
final session = await plugin.connect(deviceId);

await session.beginBind();
await session.handshake();
await session.completeBind();

final device = await session.queryDeviceInfo();
print('${device.model} / ${device.firmwareVersion}');

await session.disconnect();
await subscription.cancel();
await plugin.close();

未传入的 SeilonSdkConfig 字段继续使用原生 SDK 默认值。SDK 已初始化时,使用相同配置重复初始化为幂等操作;如需更换配置,请先调用 close()

SeilonVoicePlugin #

API 说明
initialize(config: ...) 初始化单例 SDK
startScan(duration: ...) / stopScan() 扫描录音设备;设备 ID 优先使用厂商广播 MAC
connect(deviceId) 连接指定设备并返回 SeilonRecorderSession
network 获取不依赖 BLE 会话的 SeilonNetworkClient
disconnect() 主动断开当前设备
events 广播扫描、连接、断连、日志和 OPUS 帧事件
checkPermissions(operation) Android 权限和系统能力检查
requestPermissions(operation) Android 运行时权限申请并重新检查
clearLogs() 清空当前 SDK 日志
close() 释放 SDK 和当前会话

connect() 返回的会话带有原生会话令牌。设备重连后,旧 SeilonRecorderSession 会以 SeilonErrorCode.notConnected 拒绝操作,不会误操作新会话。

SeilonRecorderSession #

绑定与设备控制 #

API 说明
beginBind() 开始绑定
handshake() 执行握手
completeBind() 完成绑定
queryDeviceInfo() 查询并返回完整 SeilonDevice
startRecording() / stopRecording() 开始或停止设备录音
powerOff() 关闭设备
setUsbMode(enabled) 设置 USB 模式
setScreen(enabled:, seconds:, brightness:) 设置屏幕开关、持续时间和亮度
setWifiEnabled(enabled) 开启或关闭设备热点
setWifiCountryCode(code) / queryWifiCountryCode() 写入或读取两位 Wi-Fi 国家码

Wi-Fi、TCP 与通道 #

// 以下设备指令需要 BLE 会话。
await session.setWifiEnabled(true);
final endpoint = await session.queryTcpEndpoint();

// SDK 1.0.4 的平台网络入口不要求先调用 plugin.connect()。
final network = plugin.network;
await network.connectWifi(
  ssid: deviceHotspotSsid,
  password: deviceHotspotPassword,
);
await network.connectTcp(host: endpoint.host, port: endpoint.port);

// 需要时再通过 BLE 会话切换设备指令通道。
await session.setChannel(SeilonChannel.tcp);
API 说明
plugin.network.connectWifi(ssid:, password:) / disconnectWifi() 无需 BLE 会话即可连接或断开设备热点
plugin.network.connectTcp(host:, port:) / disconnectTcp() 无需 BLE 会话,直接连接传入的 TCP 地址
plugin.network.connectTcp() 使用 network.configureTcp() 预先设置的地址或原生默认值连接
plugin.network.queryDeviceInfo() 及控制/文件方法 TCP 连接后通过 TCP 执行类型化设备操作
queryTcpEndpoint() 从设备读取 TCP 地址
configureTcp(host:, port:) 配置后续 TCP 连接实际使用的地址
connectTcp() / disconnectTcp() 连接或断开 TCP
setChannel(SeilonChannel.ble/tcp) 切换 SDK 指令通道

为兼容旧代码,会话对象上的 Wi-Fi/TCP 方法仍然保留。新代码应使用 plugin.network 建立平台 Wi-Fi 和 TCP 连接。setWifiCountryCode() 接收 CNUS 这类 ISO 3166-1 alpha-2 两位国家码,小写输入会自动转换为大写。

设备开启热点后,热点账号和密码可从对应日志包中解析:

if (event case SeilonLogEvent(:final entry)) {
  final credentials = SeilonWifiCredentials.tryParsePacketHex(
    entry.rawPacketHex,
  );
  if (credentials != null) {
    print('${credentials.ssid} / ${credentials.password}');
  }
}

文件操作 #

API 说明
queryFileCount() 查询文件总数、页数和单次请求长度
queryFiles(pageNumber) 查询指定页文件列表,页码从 1 开始
downloadFile(fileName) 下载文件并返回本地路径
cancelDownload() 只取消本地下载等待,不向设备发送停止指令
stopFileDownload() 通过启动下载时使用的通道发送 SDK 停止传输指令
resumeFileDownload(fileName) 从已接收字节处继续下载并返回本地路径
deleteFile(fileName) 按文件名删除设备文件
final summary = await session.queryFileCount();

for (var pageNumber = 1; pageNumber <= summary.pageCount; pageNumber++) {
  final page = await session.queryFiles(pageNumber);
  for (final file in page.files) {
    print('${file.fileName}: ${file.fileSizeBytes} bytes');
  }
}

final localPath = await session.downloadFile(fileName);
print('文件已保存:$localPath');

下载进度通过日志事件提供:

plugin.events.listen((event) {
  if (event case SeilonLogEvent()) {
    final progress = event.transferProgress;
    if (progress != null) {
      print('${progress.percent}% '
          '(${progress.receivedBytes}/${progress.totalBytes})');
    }
  }
});

文件传输规则 #

  1. 先调用 queryFileCount();返回值的 maxContentLength 是单次获取请求长度。
  2. 调用 queryFiles(pageNumber) 获取文件名与文件大小。
  3. downloadFile(fileName) 只发送一次首次获取命令,之后等待 TCP/BLE 实际数据回流。
  4. SDK 按到达顺序连续写入,不依据分片序号重排,也不会为下一片重复发送获取命令。
  5. 默认连续 8 秒无数据时失败。只有显式调用 resumeFileDownload(fileName) 才会从已接收字节处重新请求。
  6. cancelDownload() 只取消当前本地等待;已接收数据会保留用于显式断点续传。
  7. stopFileDownload() 会要求设备停止发送文件数据,并沿用启动传输时的 BLE/TCP 通道。

状态与事件 #

Dart 事件 数据
SeilonScanStartedEvent 扫描已开始
SeilonScanStoppedEvent 扫描已停止
SeilonDeviceFoundEvent SeilonDevice
SeilonConnectedEvent 已连接的 SeilonDevice
SeilonDisconnectedEvent 设备 ID、是否主动断开和原因
SeilonScanFailedEvent SeilonSdkException
SeilonLogEvent SeilonLogEntry,并可读取 transferProgress
SeilonAudioFrameEvent 设备原始 OPUS Uint8List

Flutter 公共 API 不暴露 Android Flow 或原生 SeilonSessionState。状态通过类型化方法结果和 events 获取。SDK 不做 ASR、播放或 OPUS/WAV 转换。

SeilonDevice 字段 #

调用 queryDeviceInfo() 成功后可读取以下字段:

字段 说明
id / name / rssi / connected 插件设备标识、名称、信号和连接状态
mac / model / firmwareVersion / serialNumber 设备 MAC、型号、版本号和 SN
batteryPercent 电量,范围 0~100
recording / recordingMode / recordingStatus 录音状态、模式和设备原始状态值
usbModeEnabled / wifiEnabled USB 和设备 Wi-Fi 状态
storageUsedMb / storageTotalMb EMMC 已用/总容量,单位 MB
recordFormat 当前录音格式,现有设备返回 Opus
capacityAlertValue 容量不足提醒阈值,范围 0~20
charging 是否正在充电
recordingFileName 当前录音文件名,未录音时为空
headsetPrimaryMac 耳机主耳 MAC,未传输时为空
systemDurationSeconds 系统持续时间,单位秒
screenBrightness 屏幕亮度,范围 0~100

错误与超时 #

失败方法统一抛出 SeilonSdkException

try {
  await session.queryFiles(1);
} on SeilonSdkException catch (error) {
  print('${error.code.name}: ${error.message}');
}

稳定错误码包括 busyinvalidArgumentnotConnectedpermissionDeniedbluetoothUnavailablewifiUnavailablesendFailedtimeoutparseFaileddisconnectedfileTransferFailedcancelledunsupportedFeatureinternal

  • 所有设备回复忽略 seq
  • 普通指令只按来源通道和 category + command 匹配。
  • 同一会话同一时间只允许一个普通等待操作;并发操作会返回 busy
  • 默认普通指令超时 8 秒、开启 Wi-Fi 指令 45 秒、平台 Wi-Fi/TCP 连接 60 秒、文件空闲 8 秒。
  • 超时值可通过 SeilonSdkConfigcommandTimeoutwifiCommandTimeoutnetworkTimeouttransferIdleTimeout 覆盖。

平台配置 #

Android #

  • 最低 Flutter 3.44(Dart 3.12)。
  • 最低 Android API 24。
  • Android 使用 Flutter 3.44 的 AGP 9 迁移结构,插件与 App 模块不再直接应用 KGP。
  • AAR Manifest 已声明蓝牙、定位、Wi-Fi、网络和 TCP 所需权限。
  • 使用 checkPermissions / requestPermissions 处理运行时权限。插件不会自动打开已关闭的蓝牙或 Wi-Fi。

iOS #

  • 最低 iOS 15,支持 CocoaPods 与 Swift Package Manager。
  • 宿主 Info.plist 必须包含蓝牙和本地网络用途说明:
<key>NSBluetoothAlwaysUsageDescription</key>
<string>用于扫描并连接 Seilon 录音设备</string>
<key>NSLocalNetworkUsageDescription</key>
<string>用于连接 Seilon 录音设备提供的本地 TCP 服务</string>
  • 连接设备热点时,需要为 App ID 和 target 启用 Hotspot Configuration capability。

生命周期与限制 #

  • 一个插件实例只管理一个原生 SDK、一个设备会话和一个独立网络客户端。
  • 插件不会自动绑定、握手、重连或切换 BLE/TCP 通道。
  • 页面或业务容器退出时,请取消事件订阅并调用 close()
  • SDK 不包含 OTA、DNR、ASR、分享、OPUS 播放或 OPUS/WAV 转换。
  • 完整功能入口和受控删除/关机测试流程见 example/
0
likes
150
points
266
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Flutter bridge for the Seilon BLE recorder SDK on Android and iOS.

Homepage

Topics

#bluetooth #ble #recorder #audio #seilon

License

BSD-3-Clause (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on seilon_voice_plugin

Packages that implement seilon_voice_plugin