跳到正文
Embedded SDK

iOS SDK

iKho Embedded SDK 的 iOS 接口形态设计:扫描并绑定 S1 录音卡、读取设备状态、把录音通过 BLE 或 Wi-Fi 同步到手机。SDK 尚未开放发放。

本页描述的是 Embedded SDK 的接口形态设计,SDK 尚未开放发放。需要「用 iKho 硬件采集并绑定到你的 App」的合作方,请通过联系我们登记需求,我们会在 SDK 开放时通知。

当前已可用的是服务端三组接口:认证文件上传转写。可以先用你自己的音频跑通转写链路,见后端篇教程

开发者平台处于内测阶段,接口契约以联调时提供的正式文档为准。账号由对接工程师开通,详见联系我们

iKho S1 录音卡

以下是 iKho Embedded SDK 的 iOS 接口形态设计。SDK 开放后,你的 iOS 应用可以把 S1 录音卡当作一等公民的采集设备:一次配置用户令牌,即可扫描、绑定、读取设备状态,并把设备上的录音同步到手机。SDK 采用代理(delegate)驱动,设备生命周期与同步进度都通过回调返回。方法名与参数以正式发布为准。

环境要求

SDK 开放后,集成需要满足以下条件,具体版本以正式发布说明为准。

项目要求
iOS 部署目标iOS 15.0 及以上(以正式发布说明为准)
Xcode15 及以上
Swift5.9 及以上
测试设备需真机 iPhone。SDK 计划以 arm64 真机框架分发,模拟器不支持设备联调
硬件一张 iKho S1 录音卡(硬件规格以量产规格为准)

安装

SDK 尚未开放发放,以下是开放后的集成方式。届时以私有 Swift Package / CocoaPods 分发,包名为 IKhoEmbedded;依赖地址、私有源与版本号随开通邮件发放,请勿硬编码到公共仓库,也不要凭本页示例去公共包源安装同名包。

swift
// Package.swift —— 开放后适用,依赖地址与版本以开通邮件为准
dependencies: [
    .package(url: "https://<你的内测 Git 地址>/ikho-embedded-ios.git", from: "0.9.0"),
],
targets: [
    .target(
        name: "YourApp",
        dependencies: [
            .product(name: "IKhoEmbedded", package: "ikho-embedded-ios"),
        ]
    ),
]

初始化

集成后在应用启动时用用户令牌配置一次 SDK。用户令牌由你的后端调用认证 API为每个终端用户签发,密钥仅保存在后端,切勿随包下发。认证 API 已经可用,不依赖 SDK 开放,可以先在服务端跑通签发流程。

AppDelegate.swift
import IKhoEmbedded

// 应用启动时调用一次(如 AppDelegate / App.init)
IKhoEmbedded.configure(userToken: "USER_ACCESS_TOKEN")
userToken String 必填
为当前终端用户签发的访问令牌(JWT),用于设备鉴权与文件同步。设备握手令牌由 SDK 从令牌中自动解析。
region IKhoRegion 默认 .chinaMainland
目标区域。当前只有 .chinaMainland(中国大陆)一个取值,海外区域规划中。所有设备与文件请求均走境内节点,不出境。

刷新用户令牌

用户令牌过期或重新登录后,更新令牌即可,无需重新配置 SDK。

swift
IKhoEmbedded.setUserToken("NEW_USER_ACCESS_TOKEN")

权限声明

SDK 通过蓝牙与 S1 通信,并在应用内提供录制与试听,集成后需在 Info.plist 声明以下条目,否则系统会拒绝相关能力。

Info.plist
<key>NSBluetoothAlwaysUsageDescription</key>
<string>用于连接 iKho 录音卡并同步设备上的录音</string>

<key>NSMicrophoneUsageDescription</key>
<string>用于在应用内录制与试听音频</string>

<!-- 后台保持蓝牙连接,支持后台同步 -->
<key>UIBackgroundModes</key>
<array>
  <string>bluetooth-central</string>
</array>

Wi-Fi 快传另需在应用能力中开启 Hotspot Configuration entitlement,见下方「Wi-Fi 快传」。

扫描与绑定设备

设置代理后开始扫描,扫描结果通过 deviceScanResult 回调返回;选中目标设备调用 bind(device:) 完成加密绑定。

1
设置代理并开始扫描
把实现了 IKhoDeviceDelegate 的对象设为代理,调用 scanDevices()
2
在扫描回调中选中设备
deviceScanResult(_:) 中按序列号或用户选择挑出目标设备,并 stopScan()
3
完成加密绑定
调用 bind(device:),绑定结果通过 deviceBindChanged(_:) 返回。
swift
import IKhoEmbedded

final class DeviceCoordinator: IKhoDeviceDelegate {
    private var lastSerial: String?

    func start() {
        IKhoDeviceManager.shared.delegate = self
        IKhoDeviceManager.shared.scanDevices()
    }

    // 扫描到设备时回调(可能多次触发)
    func deviceScanResult(_ devices: [IKhoDevice]) {
        guard let target = devices.first(where: { $0.serialNumber == lastSerial })
                ?? devices.first else { return }
        IKhoDeviceManager.shared.stopScan()
        IKhoDeviceManager.shared.bind(device: target)
    }

    // 绑定结果
    func deviceBindChanged(_ result: IKhoBindResult) {
        if result.bound { print("已绑定 \(result.serialNumber)") }
    }
}

一台 S1 同一时间只能绑定一个应用(用于文件加密与安全隔离)。绑定与应用安装绑定,用户卸载你的应用前请先解绑,否则设备无法再绑定到其他应用。

设备状态与电量

握手完成后,设备总体状态(电量、充电、剩余存储)通过 deviceStateChanged(_:) 回调返回;电量或充电变化会额外触发 deviceBatteryChanged。也可主动调用 refreshDeviceState() 拉取一次。

swift
func deviceStateChanged(_ state: IKhoDeviceState) {
    print("电量 \(state.batteryLevel)% · 充电中 \(state.isCharging)")
    print("剩余存储 \(state.freeStorage) / \(state.totalStorage) 字节")
}

// 主动刷新一次设备状态
IKhoDeviceManager.shared.refreshDeviceState()

录音列表与同步

调用 fetchRecordings() 读取设备上的录音清单(默认走 BLE 通道),结果通过 recordingListUpdated(_:) 返回。选中某条录音调用 exportAudio 同步到手机,进度与结果通过代理回调。

swift
// 读取设备上的录音列表(默认 BLE 通道)
IKhoDeviceManager.shared.fetchRecordings()

func recordingListUpdated(_ recordings: [IKhoRecording]) {
    for r in recordings {
        print("\(r.id) · \(r.durationSeconds)s · \(r.sizeBytes) bytes")
    }
}

// 把某条录音同步到手机
IKhoDeviceManager.shared.exportAudio(recordingId: recording.id, format: .m4a)

func exportProgress(recordingId: String, progress: Int) { /* 0–100 */ }
func exportCompleted(recordingId: String, outputPath: String) { /* 文件已就绪 */ }
func exportFailed(recordingId: String, error: IKhoError) { /* 处理错误 */ }

大文件同步建议:显示进度并预告耗时;开启后台/断点续传,避免退到后台中断;大批量或大文件优先用 Wi-Fi 快传。

Wi-Fi 快传

BLE 之外,S1 支持 Wi-Fi 快传通道,吞吐显著高于 BLE,适合批量或大文件同步。开启后,后续 exportAudio 会在通道就绪后自动优先走 Wi-Fi。

swift
// 开启 Wi-Fi 快传(设备会开启热点,SDK 自动接入)
IKhoDeviceManager.shared.setWiFiTransfer(enabled: true)

// 通道就绪后再发起同步
func wifiTransferReady() {
    IKhoDeviceManager.shared.exportAudio(recordingId: recording.id, format: .m4a)
}

Wi-Fi 快传注意点:

  • 需在应用能力中开启 Hotspot Configuration entitlement。
  • 快传期间手机会临时接入 S1 的热点,过程中普通网络可能短暂中断。
  • 握手或传输失败会自动回退到 BLE 同步。

导出音频

exportAudio 支持指定导出格式;多条录音可用 exportAudioBatch 批量导出,进度按条汇总。

swift
// 单条导出,指定格式(mp3 / m4a / wav)
IKhoDeviceManager.shared.exportAudio(recordingId: id, format: .mp3)

// 批量导出多条录音
IKhoDeviceManager.shared.exportAudioBatch(recordingIds: selectedIds, format: .wav)

func exportBatchProgress(completed: Int, total: Int) {
    print("已完成 \(completed) / \(total)")
}
格式取值适用
MP3.mp3通用、体积小,便于回放与上传转写
M4A.m4aAAC 封装,画质与体积均衡,iOS 原生友好
WAV.wav无损,用于二次处理或高精度转写

后台运行与 BLE 重连

Info.plist 声明 bluetooth-central 后台模式(见上方「权限声明」)后,应用退到后台时 SDK 保持 BLE 连接,进行中的同步继续执行;系统在后台会降低蓝牙事件频率,大文件同步耗时可能长于前台。未声明该模式时,应用挂起后连接会被系统回收,回到前台需重新连接。

连接意外中断(超出距离、设备关机)时,SDK 默认开启自动重连:向系统登记待连请求,设备回到近场即自动恢复连接,前后台均生效,结果通过 deviceConnectionChanged(_:) 回调返回。未完成的同步在重连后从断点继续。

swift
// 自动重连默认开启,可按需关闭
IKhoDeviceManager.shared.setAutoReconnect(enabled: true)

// 连接状态变化(断开与重连成功都会回调)
func deviceConnectionChanged(_ state: IKhoConnectionState) {
    switch state {
    case .connected:
        print("已连接,恢复同步")
    case .reconnecting:
        print("连接中断,等待设备回到近场")
    case .disconnected:
        print("已断开")
    }
}

审核提示:App Store 审核会核对后台模式声明与实际功能是否一致。声明 bluetooth-central 时,请在应用描述或审核备注中说明用途,例如「与录音配件保持蓝牙连接,在后台继续同步音频」;若你的应用不需要后台同步,可不声明该模式,改为回到前台后重新连接。

线程模型与回调队列

IKhoDeviceDelegate 的全部回调默认在 SDK 内部串行队列触发,保证事件按发生顺序抵达,但不在主线程。更新 UI、写入 @Published 属性或触发界面刷新前,必须切回主线程。

swift
func exportProgress(recordingId: String, progress: Int) {
    // 回调在 SDK 内部队列,更新 UI 前切回主线程
    DispatchQueue.main.async {
        self.progressText = "\(progress)%"
    }
}

也可以在初始化后把回调队列整体指定为主队列。SDK 内部的扫描、传输仍在自有队列执行,仅回调的派发位置改变。

swift
// 让所有代理回调直接派发到主队列
IKhoDeviceManager.shared.callbackQueue = .main

回调队列指定为主队列后,请勿在回调内执行耗时的同步操作(如大文件读写),以免阻塞界面。

存储占用与清理

同步完成的音频写入应用沙盒内 SDK 专属的缓存目录,exportCompleted 返回的 outputPath 即位于其中;缓存随应用卸载一并删除,不占用系统相册或共享空间,且默认排除在 iCloud 备份之外。缓存会随同步量增长,建议在设置页向用户展示占用并提供清理入口。

swift
// 查询当前音频缓存占用(字节)
let usedBytes = IKhoDeviceManager.shared.audioCacheSize()
print("缓存占用 \(usedBytes) 字节")

// 清理指定日期之前的缓存文件
let thirtyDaysAgo = Calendar.current.date(byAdding: .day, value: -30, to: Date())!
IKhoDeviceManager.shared.clearAudioCache(olderThan: thirtyDaysAgo)

// 清空全部音频缓存
IKhoDeviceManager.shared.clearAudioCache()

清理只删除 SDK 缓存中的本地副本,不影响设备上的原始录音,也不影响你已复制到业务目录的文件。清理后再次需要某条录音时,重新发起同步即可。

解绑设备

用户切换应用或卸载前,调用 unbind(clear:) 解除绑定并清除本地绑定态。

swift
// 解绑当前设备并清除本地绑定态
IKhoDeviceManager.shared.unbind(clear: true)
clear Bool 必填
设为 true 时清除全部本地连接与缓存的绑定信息。

错误码

SDK 层错误通过 didEncounterError(_:) 或各操作的失败回调返回,IKhoError 携带以下错误码。

错误码含义建议处理
IKHO_ERR_UNAUTHORIZED用户令牌无效或已过期重新签发用户令牌后调用 setUserToken
IKHO_ERR_BLE_UNAVAILABLE蓝牙未开启或未授权引导用户在系统设置开启蓝牙并授予权限
IKHO_ERR_DEVICE_NOT_FOUND扫描超时未发现设备确认设备已开机且在近场,重试扫描
IKHO_ERR_BIND_OCCUPIED设备已被其他应用绑定提示用户在原应用先解绑,再重试绑定
IKHO_ERR_CONNECTION_LOST与设备的连接中断靠近设备后自动重连,或手动重新扫描
IKHO_ERR_TRANSFER_FAILED音频同步过程中断支持断点续传,重新发起同步
IKHO_ERR_STORAGE_FULL手机本地存储空间不足清理空间后重试导出
IKHO_ERR_WIFI_HANDSHAKEWi-Fi 快传握手失败回退到 BLE 同步或稍后重试

代理回调一览

所有设备事件通过 IKhoDeviceDelegate 返回。其中 deviceStateChanged 为唯一必须实现的回调,其余可按需实现。

分组回调说明
连接deviceScanResult(_:)扫描结果更新
连接deviceConnectionChanged(_:)连接状态变化(已连接 / 断开)
连接deviceBindChanged(_:)绑定状态变化
状态deviceStateChanged(_:)必须实现。握手完成后返回设备总体状态(电量 / 充电 / 存储)
状态deviceBatteryChanged(level:isCharging:)电量或充电状态变化
录音recordingStarted(_:)设备开始录音
录音recordingStopped(_:)设备停止录音
录音recordingListUpdated(_:)fetchRecordings() 结果返回
同步exportProgress(recordingId:progress:)单条同步进度,0–100
同步exportCompleted(recordingId:outputPath:)单条同步完成,文件写入 outputPath
同步exportFailed(recordingId:error:)单条同步失败
同步exportBatchProgress(completed:total:)批量同步进度
Wi-FiwifiTransferReady()Wi-Fi 快传通道就绪,可发起同步
错误didEncounterError(_:)其他 SDK 层错误

回调默认在 SDK 内部队列触发,更新 UI 或已发布状态前请切回主线程。

下一步