Browse Source

修复 iOS Swift 编译错误

三类问题:
1. JieliEventListener 是 @objc protocol 但参数类型 [String: Any?] 无法
   桥接到 Obj-C → 改成纯 Swift protocol(去掉 @objc),同 module 内
   InternalCleanupListener 仍能正常实现
2. 内部类型(EventDispatcher / TranslationCoordinator / TranslationAudio
   Bridge / EventChannelAudioBridge / 各 Feature 类)声明了 public 但同
   时引用了 internal 类型 → 把 plugin 内部所有 public 全部降为 internal
   (Flutter entry JielihomePlugin / DevicePluginInterfacePlugin 仍保留
   public 因为 Flutter 注册需要)
3. JL_BLEMultiple.otaFunc(...) 的 Result 参数是 nullable closure
   (JL_OTA_RT __nullable),Swift 不接受给 nullable closure 用 trailing
   closure 语法 → 改成显式 closure 字面量赋给 cb 变量再传入

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
weicu
liwei1dao 5 months ago
parent
commit
5af13c0872
  1. 17
      local_plugins/device_jieli/ios/Classes/Bridge/EventDispatcher.swift
  2. 14
      local_plugins/device_jieli/ios/Classes/Bridge/MethodRouter.swift
  3. 34
      local_plugins/device_jieli/ios/Classes/Core/JieliHomeServer.swift
  4. 10
      local_plugins/device_jieli/ios/Classes/Feature/AssistantBridge.swift
  5. 2
      local_plugins/device_jieli/ios/Classes/Feature/ConnectFeature.swift
  6. 4
      local_plugins/device_jieli/ios/Classes/Feature/CustomCmdFeature.swift
  7. 6
      local_plugins/device_jieli/ios/Classes/Feature/DeviceInfoFeature.swift
  8. 8
      local_plugins/device_jieli/ios/Classes/Feature/DeviceRecordFeature.swift
  9. 17
      local_plugins/device_jieli/ios/Classes/Feature/OtaFeature.swift
  10. 18
      local_plugins/device_jieli/ios/Classes/Feature/ScanFeature.swift
  11. 28
      local_plugins/device_jieli/ios/Classes/Feature/SpeechFeature.swift
  12. 16
      local_plugins/device_jieli/ios/Classes/Feature/Translation/TranslationAudioBridge.swift
  13. 24
      local_plugins/device_jieli/ios/Classes/Feature/TranslationFeature.swift
  14. 6
      local_plugins/device_jieli/ios/Classes/Integration/JieliNativeDevicePlugin.swift

17
local_plugins/device_jieli/ios/Classes/Bridge/EventDispatcher.swift

@ -4,7 +4,10 @@ import Foundation
/// Native-side event subscriber. Mirrors Android `JieliEventListener`.
/// Implementers receive the same `payload` dictionary that gets pushed to
/// the Flutter EventChannel, so native and Dart subscribers stay symmetric.
@objc public protocol JieliEventListener: AnyObject {
///
/// Pure-Swift protocol (not `@objc`) because the payload's `[String: Any?]`
/// cannot be bridged to Obj-C (Obj-C dictionary values can't be nullable).
protocol JieliEventListener: AnyObject {
func onEvent(payload: [String: Any?])
}
@ -14,7 +17,7 @@ import Foundation
/// originating queue — fits high-frequency audio frames).
///
/// Mirrors Android `bridge/EventDispatcher.kt`.
public class EventDispatcher: NSObject, FlutterStreamHandler {
class EventDispatcher: NSObject, FlutterStreamHandler {
private let lock = NSLock()
private var sink: FlutterEventSink?
@ -22,7 +25,7 @@ public class EventDispatcher: NSObject, FlutterStreamHandler {
// MARK: - FlutterStreamHandler
public func onListen(
func onListen(
withArguments arguments: Any?,
eventSink events: @escaping FlutterEventSink
) -> FlutterError? {
@ -31,7 +34,7 @@ public class EventDispatcher: NSObject, FlutterStreamHandler {
return nil
}
public func onCancel(withArguments arguments: Any?) -> FlutterError? {
func onCancel(withArguments arguments: Any?) -> FlutterError? {
lock.lock(); defer { lock.unlock() }
sink = nil
return nil
@ -39,14 +42,14 @@ public class EventDispatcher: NSObject, FlutterStreamHandler {
// MARK: - Native listeners
public func addNativeListener(_ listener: JieliEventListener) {
func addNativeListener(_ listener: JieliEventListener) {
lock.lock(); defer { lock.unlock() }
if !listeners.contains(where: { $0 === listener }) {
listeners.append(listener)
}
}
public func removeNativeListener(_ listener: JieliEventListener) {
func removeNativeListener(_ listener: JieliEventListener) {
lock.lock(); defer { lock.unlock() }
listeners.removeAll { $0 === listener }
}
@ -57,7 +60,7 @@ public class EventDispatcher: NSObject, FlutterStreamHandler {
///
/// Payload schema: identical to Android. `type` is the discriminator key
/// that the Dart `Jielihome._parseEvent(...)` switches on.
public func send(_ payload: [String: Any?]) {
func send(_ payload: [String: Any?]) {
let snapshot: [JieliEventListener]
let sinkRef: FlutterEventSink?
lock.lock()

14
local_plugins/device_jieli/ios/Classes/Bridge/MethodRouter.swift

@ -8,15 +8,15 @@ import UIKit
/// argument keys. Where the iOS JieLi SDK does not yet have a wired
/// implementation, the handler returns a `NOT_IMPLEMENTED_IOS` error so the
/// Dart layer can downgrade gracefully (instead of crashing).
public class MethodRouter {
class MethodRouter {
private let server: JieliHomeServer
public init(server: JieliHomeServer) {
init(server: JieliHomeServer) {
self.server = server
}
public func handle(call: FlutterMethodCall, result: @escaping FlutterResult) {
func handle(call: FlutterMethodCall, result: @escaping FlutterResult) {
let args = call.arguments as? [String: Any] ?? [:]
do {
switch call.method {
@ -317,10 +317,10 @@ public class MethodRouter {
/// Local plugin error wrapping that maps cleanly onto FlutterError. Used by
/// feature classes when they need to surface a typed error code without
/// throwing an opaque NSError.
public struct PluginError: Error {
public let code: String
public let message: String
public init(_ code: String, _ message: String) {
struct PluginError: Error {
let code: String
let message: String
init(_ code: String, _ message: String) {
self.code = code
self.message = message
}

34
local_plugins/device_jieli/ios/Classes/Core/JieliHomeServer.swift

@ -13,14 +13,14 @@ import JL_BLEKit
///
/// Initialization is idempotent — calling [initialize] twice returns
/// immediately on the second call.
public class JieliHomeServer {
class JieliHomeServer {
public static let shared = JieliHomeServer()
static let shared = JieliHomeServer()
private(set) public var initialized: Bool = false
/// Event bus shared by Flutter EventChannel + native listeners.
public let dispatcher: EventDispatcher = EventDispatcher()
let dispatcher: EventDispatcher = EventDispatcher()
// MARK: - Underlying SDK references
// The JieLi iOS SDK exposes BLE plumbing via `JL_BLEMultiple` and per-device
@ -34,36 +34,36 @@ public class JieliHomeServer {
/// `JLTranslationManager`. Holds the active translation sink (call
/// translation / assistant / device record); modules switch sinks as they
/// enter/exit modes — only one mode at a time.
public private(set) lazy var translationCoordinator: TranslationCoordinator = {
private(set) lazy var translationCoordinator: TranslationCoordinator = {
TranslationCoordinator(server: self)
}()
// MARK: - Features (lazy-attached after initialize)
public private(set) lazy var scanFeature: ScanFeature = {
private(set) lazy var scanFeature: ScanFeature = {
ScanFeature(dispatcher: dispatcher)
}()
public private(set) lazy var connectFeature: ConnectFeature = {
private(set) lazy var connectFeature: ConnectFeature = {
ConnectFeature(server: self)
}()
public private(set) lazy var deviceInfoFeature: DeviceInfoFeature = {
private(set) lazy var deviceInfoFeature: DeviceInfoFeature = {
DeviceInfoFeature(server: self)
}()
public private(set) lazy var customCmdFeature: CustomCmdFeature = {
private(set) lazy var customCmdFeature: CustomCmdFeature = {
CustomCmdFeature(server: self)
}()
public private(set) lazy var translationFeature: TranslationFeature = {
private(set) lazy var translationFeature: TranslationFeature = {
TranslationFeature(server: self)
}()
public private(set) lazy var speechFeature: SpeechFeature = {
private(set) lazy var speechFeature: SpeechFeature = {
SpeechFeature(server: self)
}()
public private(set) lazy var otaFeature: OtaFeature = {
private(set) lazy var otaFeature: OtaFeature = {
OtaFeature(server: self)
}()
public private(set) lazy var deviceRecordFeature: DeviceRecordFeature = {
private(set) lazy var deviceRecordFeature: DeviceRecordFeature = {
DeviceRecordFeature(server: self)
}()
public private(set) lazy var assistantBridge: AssistantBridge = {
private(set) lazy var assistantBridge: AssistantBridge = {
AssistantBridge(server: self)
}()
@ -71,7 +71,7 @@ public class JieliHomeServer {
// MARK: - Lifecycle
public func initialize(
func initialize(
multiDevice: Bool,
skipNoNameDev: Bool,
enableLog: Bool
@ -103,7 +103,7 @@ public class JieliHomeServer {
])
}
public func shutdown() {
func shutdown() {
if !initialized { return }
otaFeature.cancel()
deviceRecordFeature.stop()
@ -116,11 +116,11 @@ public class JieliHomeServer {
// MARK: - Native event subscription helpers
public func addEventListener(_ listener: JieliEventListener) {
func addEventListener(_ listener: JieliEventListener) {
dispatcher.addNativeListener(listener)
}
public func removeEventListener(_ listener: JieliEventListener) {
func removeEventListener(_ listener: JieliEventListener) {
dispatcher.removeNativeListener(listener)
}

10
local_plugins/device_jieli/ios/Classes/Feature/AssistantBridge.swift

@ -24,7 +24,7 @@ import JL_BLEKit
/// re-routes through the connected A2DP earphone. iOS equivalent is
/// `AVAudioEngine`/`AudioQueue`; the orchestrator can drive this directly
/// or call back through `assistantPlayback` (TODO once needed).
public class AssistantBridge {
class AssistantBridge {
weak var server: JieliHomeServer?
init(server: JieliHomeServer) { self.server = server }
@ -36,9 +36,9 @@ public class AssistantBridge {
private var sinkWrapper: AssistantSinkWrapper?
#endif
public func isRunning() -> Bool { running }
func isRunning() -> Bool { running }
public func start() -> Bool {
func start() -> Bool {
if running { return true }
#if canImport(JL_BLEKit)
guard let server = server else { return false }
@ -118,7 +118,7 @@ public class AssistantBridge {
#endif
}
public func stop() {
func stop() {
if !running { return }
running = false
cleanup()
@ -128,7 +128,7 @@ public class AssistantBridge {
])
}
public func shutdown() { stop() }
func shutdown() { stop() }
// MARK: - Internal

2
local_plugins/device_jieli/ios/Classes/Feature/ConnectFeature.swift

@ -12,7 +12,7 @@ import JL_BLEKit
/// (0=disconnect, 1=ok, 2=connecting). RCSP-init success surfaces as a
/// separate `rcspInit` event when the JieLi SDK confirms `cmdTargetFeatureResult`
/// returns successfully — same flow Android publishes.
public class ConnectFeature: NSObject {
class ConnectFeature: NSObject {
weak var server: JieliHomeServer?
init(server: JieliHomeServer) { self.server = server }

4
local_plugins/device_jieli/ios/Classes/Feature/CustomCmdFeature.swift

@ -12,12 +12,12 @@ import JL_BLEKit
/// argument, so we prepend the opCode byte to the payload and call that.
/// The reply comes back through the result block. This matches the shape
/// the Dart caller expects: a `[Int]` response.
public class CustomCmdFeature {
class CustomCmdFeature {
weak var server: JieliHomeServer?
init(server: JieliHomeServer) { self.server = server }
public func send(
func send(
address: String,
opCode: UInt8,
payload: Data,

6
local_plugins/device_jieli/ios/Classes/Feature/DeviceInfoFeature.swift

@ -10,12 +10,12 @@ import JL_BLEKit
/// caller. The Android schema (`name`, `firmwareVersion`, `battery`, ...) is
/// preserved; iOS-specific extras (`bleAddr`, `btAddr`, `pidvid`) are added
/// for completeness — Dart consumers ignore unknown keys.
public class DeviceInfoFeature {
class DeviceInfoFeature {
weak var server: JieliHomeServer?
init(server: JieliHomeServer) { self.server = server }
public func snapshot(address: String) -> [String: Any?]? {
func snapshot(address: String) -> [String: Any?]? {
#if canImport(JL_BLEKit)
guard let entity = server?.connectFeature.currentEntity() as? JL_EntityM,
entity.mUUID == address else {
@ -39,7 +39,7 @@ public class DeviceInfoFeature {
#endif
}
public func queryTargetInfo(
func queryTargetInfo(
address: String,
mask: Int,
completion: @escaping ([String: Any?]?, Error?) -> Void

8
local_plugins/device_jieli/ios/Classes/Feature/DeviceRecordFeature.swift

@ -21,7 +21,7 @@ import JL_BLEKit
///
/// Mutually exclusive with TranslationFeature — both use the shared
/// `JLTranslationManager`. The MethodRouter stops the other before starting.
public class DeviceRecordFeature {
class DeviceRecordFeature {
weak var server: JieliHomeServer?
init(server: JieliHomeServer) { self.server = server }
@ -36,9 +36,9 @@ public class DeviceRecordFeature {
private var sinkWrapper: RecordSinkWrapper?
#endif
public func isRecording() -> Bool { working }
func isRecording() -> Bool { working }
public func start(args: [String: Any]) throws {
func start(args: [String: Any]) throws {
if working { throw PluginError("DEVICE_RECORD_ERR", "already recording") }
#if canImport(JL_BLEKit)
guard let server = server else {
@ -101,7 +101,7 @@ public class DeviceRecordFeature {
#endif
}
public func stop() {
func stop() {
if !working { return }
working = false
let addr = deviceAddress

17
local_plugins/device_jieli/ios/Classes/Feature/OtaFeature.swift

@ -15,16 +15,16 @@ import JL_OTALib
/// progress callback into the same `otaState` / `otaError` event shapes
/// the Android side publishes — Dart `_parseOtaState(...)` keeps working
/// unchanged.
public class OtaFeature {
class OtaFeature {
weak var server: JieliHomeServer?
init(server: JieliHomeServer) { self.server = server }
private var running: Bool = false
public func isRunning() -> Bool { running }
func isRunning() -> Bool { running }
public func start(
func start(
address: String?,
firmwareFilePath: String,
blockSize: Int,
@ -47,9 +47,16 @@ public class OtaFeature {
"state": "INQUIRING",
"sent": 0, "total": 0, "percent": -1, "tsMs": Self.tsMs(),
])
server.bleMultiple.otaFunc(withEntityM: entity, withFilePath: firmwareFilePath) { [weak self] result, progress in
// JL_OTA_RT 是 nullable closure (`JL_OTA_RT __nullable`),Swift 不接受
// trailing closure 给 nullable closure 参数 —— 显式拼出 closure 字面量。
let cb: JL_OTA_RT = { [weak self] result, progress in
self?.onOtaCallback(result: result, progress: progress)
}
server.bleMultiple.otaFunc(
withEntityM: entity,
withFilePath: firmwareFilePath,
result: cb
)
#else
server?.dispatcher.send([
"type": "otaError",
@ -59,7 +66,7 @@ public class OtaFeature {
#endif
}
public func cancel() {
func cancel() {
if !running { return }
running = false
server?.dispatcher.send([

18
local_plugins/device_jieli/ios/Classes/Feature/ScanFeature.swift

@ -25,7 +25,7 @@ import CoreBluetooth
/// exact entry point is wired we leave them null and rely on the connection
/// path's BLE-only fallback (matches the Android "fallback when SDK can't
/// parse" behavior).
public class ScanFeature: NSObject, CBCentralManagerDelegate {
class ScanFeature: NSObject, CBCentralManagerDelegate {
private weak var server: JieliHomeServer?
private weak var dispatcher: EventDispatcher?
@ -44,26 +44,26 @@ public class ScanFeature: NSObject, CBCentralManagerDelegate {
// Track which addresses have been published this scan to dedupe.
private var publishedIds: Set<String> = []
public init(dispatcher: EventDispatcher) {
init(dispatcher: EventDispatcher) {
self.dispatcher = dispatcher
super.init()
}
public func attach(server: JieliHomeServer) {
func attach(server: JieliHomeServer) {
self.server = server
if centralManager == nil {
centralManager = CBCentralManager(delegate: self, queue: .main)
}
}
public func detach() {
func detach() {
stopScan()
centralManager = nil
}
// MARK: - Public API (mirrors Android signatures)
public func startScan(
func startScan(
timeoutMs: Int,
nameList: [String],
uuidList: [String],
@ -103,7 +103,7 @@ public class ScanFeature: NSObject, CBCentralManagerDelegate {
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(timeoutMs), execute: work)
}
public func stopScan() {
func stopScan() {
if let work = stopWorkItem { work.cancel(); stopWorkItem = nil }
if let central = centralManager, central.state == .poweredOn {
central.stopScan()
@ -120,11 +120,11 @@ public class ScanFeature: NSObject, CBCentralManagerDelegate {
serviceFilters = []
}
public func isScanning() -> Bool { scanning }
func isScanning() -> Bool { scanning }
// MARK: - CBCentralManagerDelegate
public func centralManagerDidUpdateState(_ central: CBCentralManager) {
func centralManagerDidUpdateState(_ central: CBCentralManager) {
let on = central.state == .poweredOn
dispatcher?.send([
"type": "adapterStatus",
@ -134,7 +134,7 @@ public class ScanFeature: NSObject, CBCentralManagerDelegate {
if !on && scanning { stopScan() }
}
public func centralManager(
func centralManager(
_ central: CBCentralManager,
didDiscover peripheral: CBPeripheral,
advertisementData: [String: Any],

28
local_plugins/device_jieli/ios/Classes/Feature/SpeechFeature.swift

@ -14,27 +14,27 @@ import Foundation
///
/// The MethodChannel surface is preserved so the Dart layer (which already
/// expects errors here on missing devices) sees a clean `SPEECH_START_ERR`.
public class SpeechFeature {
class SpeechFeature {
public static let voiceTypePcm = 0
public static let voiceTypeSpeex = 1
public static let voiceTypeOpus = 2
static let voiceTypePcm = 0
static let voiceTypeSpeex = 1
static let voiceTypeOpus = 2
public static let sampleRate8k = 8
public static let sampleRate16k = 16
static let sampleRate8k = 8
static let sampleRate16k = 16
public static let vadWayDevice = 0
public static let vadWaySdk = 1
static let vadWayDevice = 0
static let vadWaySdk = 1
public static let reasonNormal = 0
public static let reasonStop = 1
static let reasonNormal = 0
static let reasonStop = 1
weak var server: JieliHomeServer?
init(server: JieliHomeServer) { self.server = server }
public func isRecording(address: String?) -> Bool { false }
func isRecording(address: String?) -> Bool { false }
public func start(
func start(
address: String?,
voiceType: Int,
sampleRate: Int,
@ -45,7 +45,7 @@ public class SpeechFeature {
completion(false, "speech.start not yet implemented on iOS")
}
public func stop(
func stop(
address: String?,
reason: Int,
completion: @escaping (Bool, String?) -> Void
@ -53,7 +53,7 @@ public class SpeechFeature {
completion(false, "speech.stop not yet implemented on iOS")
}
public func detach() {
func detach() {
// No resources held while stubbed.
}
}

16
local_plugins/device_jieli/ios/Classes/Feature/Translation/TranslationAudioBridge.swift

@ -3,7 +3,7 @@ import Foundation
/// Bridge between a TranslationModeHandler and the outside world (Flutter
/// EventChannel by default; can be replaced by host code for native-direct
/// orchestration). Mirrors Android `feature/translation/TranslationAudioBridge.kt`.
public protocol TranslationAudioBridge: AnyObject {
protocol TranslationAudioBridge: AnyObject {
/// Plugin → outside: one captured PCM frame from the device.
func emitAudioFrame(
@ -47,14 +47,14 @@ public protocol TranslationAudioBridge: AnyObject {
/// Default bridge backed by the EventDispatcher → Flutter EventChannel
/// path. Mirrors Android `feature/translation/EventChannelAudioBridge.kt`.
public final class EventChannelAudioBridge: TranslationAudioBridge {
final class EventChannelAudioBridge: TranslationAudioBridge {
private weak var dispatcher: EventDispatcher?
/// Closure invoked when feedTranslatedAudio is called. The default impl
/// routes back into TranslationFeature — same as Android's `injector`.
private let injector: (Int, String, Data, TranslationAudioFormat, Bool) -> Bool
public init(
init(
dispatcher: EventDispatcher,
injector: @escaping (Int, String, Data, TranslationAudioFormat, Bool) -> Bool
) {
@ -62,7 +62,7 @@ public final class EventChannelAudioBridge: TranslationAudioBridge {
self.injector = injector
}
public func emitAudioFrame(
func emitAudioFrame(
modeId: Int,
streamId: String,
pcm: Data,
@ -85,7 +85,7 @@ public final class EventChannelAudioBridge: TranslationAudioBridge {
])
}
public func emitTranslationResult(
func emitTranslationResult(
modeId: Int,
srcLang: String?,
srcText: String?,
@ -104,7 +104,7 @@ public final class EventChannelAudioBridge: TranslationAudioBridge {
])
}
public func emitLog(modeId: Int, content: String) {
func emitLog(modeId: Int, content: String) {
dispatcher?.send([
"type": "translationLog",
"modeId": modeId,
@ -112,7 +112,7 @@ public final class EventChannelAudioBridge: TranslationAudioBridge {
])
}
public func emitError(modeId: Int, code: Int, message: String?) {
func emitError(modeId: Int, code: Int, message: String?) {
dispatcher?.send([
"type": "translationError",
"modeId": modeId,
@ -122,7 +122,7 @@ public final class EventChannelAudioBridge: TranslationAudioBridge {
}
@discardableResult
public func feedTranslatedAudio(
func feedTranslatedAudio(
modeId: Int,
outputStreamId: String,
pcm: Data,

24
local_plugins/device_jieli/ios/Classes/Feature/TranslationFeature.swift

@ -9,7 +9,7 @@ import Foundation
///
/// The `feedTranslatedAudio` / `feedTranslationResult` paths route to the
/// active handler / bridge, exactly like Android.
public class TranslationFeature: NSObject {
class TranslationFeature: NSObject {
weak var server: JieliHomeServer?
@ -38,7 +38,7 @@ public class TranslationFeature: NSObject {
/// Replace the active bridge — used by host code that wants to keep
/// audio inside native (mirrors Android `setTranslationBridge`).
public func setBridge(_ bridge: TranslationAudioBridge) {
func setBridge(_ bridge: TranslationAudioBridge) {
self.bridge = bridge
guard let server = server else { return }
handlers = [
@ -53,19 +53,19 @@ public class TranslationFeature: NSObject {
// MARK: - Public API
public func isWorking() -> Bool { current?.isWorking ?? false }
public func currentModeId() -> Int? {
func isWorking() -> Bool { current?.isWorking ?? false }
func currentModeId() -> Int? {
guard let h = current, h.isWorking else { return nil }
return h.modeId
}
public func currentInputStreams() -> [String] {
func currentInputStreams() -> [String] {
current?.isWorking == true ? (current?.inputStreams ?? []) : []
}
public func currentOutputStreams() -> [String] {
func currentOutputStreams() -> [String] {
current?.isWorking == true ? (current?.outputStreams ?? []) : []
}
public func start(modeId: Int, args: [String: Any]) throws {
func start(modeId: Int, args: [String: Any]) throws {
// Match Android logic — auto-upgrade mode=3 + OPUS to mode=6 stereo when
// the device supports it (and caller did not opt out).
let bypassStereo = (args["bypassStereoUpgrade"] as? Bool) ?? false
@ -91,13 +91,13 @@ public class TranslationFeature: NSObject {
current = handler
}
public func stop() {
func stop() {
current?.stop()
current = nil
}
@discardableResult
public func feedTranslatedAudio(
func feedTranslatedAudio(
streamId: String,
pcm: Data,
sampleRate: Int,
@ -119,7 +119,7 @@ public class TranslationFeature: NSObject {
)
}
public func feedTranslationResult(
func feedTranslationResult(
srcLang: String?, srcText: String?,
destLang: String?, destText: String?,
requestId: String?
@ -139,7 +139,7 @@ public class TranslationFeature: NSObject {
/// `JLDeviceConfigTws.isSupportOpusStereo` flag when available. As a
/// pragmatic default we just return `trIsPlayWithA2dp()`, matching the
/// demo's `setupModeForCall` heuristic.
public func isSupportCallTranslationWithStereo(address: String?) -> Bool {
func isSupportCallTranslationWithStereo(address: String?) -> Bool {
#if canImport(JL_BLEKit)
guard let server = server else { return false }
return server.translationCoordinator.manager?.trIsPlayWithA2dp() ?? false
@ -152,5 +152,5 @@ public class TranslationFeature: NSObject {
/// dedicated handler maps to MODE_AUDIO_TRANSLATION which the demo drives
/// by starting recording-and-play. Returns `false` until implemented to
/// keep the Dart contract intact.
public func feedAudioFilePcm(pcm: Data, sampleRate: Int) -> Bool { false }
func feedAudioFilePcm(pcm: Data, sampleRate: Int) -> Bool { false }
}

6
local_plugins/device_jieli/ios/Classes/Integration/JieliNativeDevicePlugin.swift

@ -19,11 +19,11 @@ import Foundation
/// 3. Hand both to the registry.
///
/// Until then `register()` is a no-op so the plugin still loads cleanly.
public enum JieliNativeDevicePlugin {
enum JieliNativeDevicePlugin {
public static let kVendor = "jieli"
static let kVendor = "jieli"
public static func register() {
static func register() {
// TODO: when device_plugin_interface iOS gains a NativeDevicePluginRegistry,
// wire the JieLi factory here, mirroring Android's
// `JieliNativeDevicePlugin.register(context)`.

Loading…
Cancel
Save