Browse Source

修改ble通信

weicu
fdp 1 year ago
parent
commit
eab05419d6
  1. 18
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
  2. 2
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentServicePlugin.kt
  3. 8
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt
  4. 21
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt
  5. 2
      local_plugins/ble_service/android/build.gradle.kts
  6. 2
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleConst.kt
  7. 183
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt
  8. 5
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/ChatAgent.kt
  9. 8
      local_plugins/jieli/android/src/main/kotlin/com/yunqiinnovation/jieli/BleService.kt
  10. 107
      local_plugins/jl_opus/android/src/main/kotlin/com/yunqiinnovation/jl_opus/JlOpusPlugin.kt

18
local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt

@ -50,7 +50,7 @@ object AgentService : CoroutineScope {
private var openaiModel: String = "gpt-3.5-turbo" private var openaiModel: String = "gpt-3.5-turbo"
// Azure服务 // Azure服务
private var azureAsrHelper: AzureAsrHelper? = null var azureAsrHelper: AzureAsrHelper? = null
private var azureTtsHelper: AzureTtsHelper? = null private var azureTtsHelper: AzureTtsHelper? = null
// OpenAI服务 // OpenAI服务
@ -112,7 +112,7 @@ object AgentService : CoroutineScope {
* @param config 配置参数,包含所需的所有API密钥和设置 * @param config 配置参数,包含所需的所有API密钥和设置
* @return 是否初始化成功 * @return 是否初始化成功
*/ */
fun initialize(appContext: Context, config: Map<String, Any>): Boolean { fun initialize(appContext: Context, config: Map<String, Any>,isExternalActive:Boolean): Boolean {
if (isInitialized) return true if (isInitialized) return true
try { try {
@ -149,7 +149,8 @@ object AgentService : CoroutineScope {
initialize( initialize(
subscriptionKey = azureSpeechKey, subscriptionKey = azureSpeechKey,
region = azureSpeechRegion, region = azureSpeechRegion,
audioSourceType = AzureAsrHelper.AudioSourceType.MICROPHONE, audioSourceType = if (isExternalActive) AzureAsrHelper.AudioSourceType.EXTERNAL
else AzureAsrHelper.AudioSourceType.MICROPHONE
) )
} }
@ -277,7 +278,7 @@ object AgentService : CoroutineScope {
/** /**
* 开始语音识别 * 开始语音识别
*/ */
fun startRecognition(): Boolean { fun startRecognition(isExternalActive:Boolean): Boolean {
if (!isInitialized) { if (!isInitialized) {
FileLogger.e(TAG, "服务未初始化") FileLogger.e(TAG, "服务未初始化")
return false return false
@ -296,6 +297,13 @@ object AgentService : CoroutineScope {
hasSpeechDetected = false hasSpeechDetected = false
try { try {
FileLogger.d(TAG, "选择音频源类型: ${isExternalActive}") //
// 选择音频源类型
val audioSourceType = if (isExternalActive) {
AzureAsrHelper.AudioSourceType.EXTERNAL
} else {
AzureAsrHelper.AudioSourceType.MICROPHONE
}
azureAsrHelper?.startContinuousRecognition(object : AzureAsrHelper.ContinuousRecognizeCallback { azureAsrHelper?.startContinuousRecognition(object : AzureAsrHelper.ContinuousRecognizeCallback {
override fun onRecognizing(recognizing: String, detectedLanguage: String) { override fun onRecognizing(recognizing: String, detectedLanguage: String) {
if (recognizing.isNotEmpty()) { if (recognizing.isNotEmpty()) {
@ -370,7 +378,7 @@ object AgentService : CoroutineScope {
"message" to error "message" to error
)) ))
} }
}) },audioSourceType)
return true return true
} catch (e: Exception) { } catch (e: Exception) {
isRecognitionActive = false isRecognitionActive = false

2
local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentServicePlugin.kt

@ -39,7 +39,7 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
"startConversation" -> { "startConversation" -> {
try { try {
val isStarted = AgentService.startRecognition() val isStarted = AgentService.startRecognition(false)
result.success(isStarted) result.success(isStarted)
} catch (e: Exception) { } catch (e: Exception) {
FileLogger.e(TAG, "启动对话失败", e) FileLogger.e(TAG, "启动对话失败", e)

8
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt

@ -254,12 +254,12 @@ class AzureAsrHelper(private val context: Context) {
* *
* @param callback 识别结果回调 * @param callback 识别结果回调
*/ */
fun recognizeOnce(callback: RecognizeCallback) { fun recognizeOnce(callback: RecognizeCallback, audioSourceType: AudioSourceType = AudioSourceType.MICROPHONE) {
if (speechConfig == null) { if (speechConfig == null) {
callback.onError("语音服务未初始化") callback.onError("语音服务未初始化")
return return
} }
this.audioSourceType = audioSourceType
// 确保不在连续识别中 // 确保不在连续识别中
if (isContinuousRecognitionActive) { if (isContinuousRecognitionActive) {
stopContinuousRecognition() stopContinuousRecognition()
@ -299,7 +299,7 @@ class AzureAsrHelper(private val context: Context) {
* @param callback 连续识别结果回调 * @param callback 连续识别结果回调
* @return 是否成功开始识别 * @return 是否成功开始识别
*/ */
fun startContinuousRecognition(callback: ContinuousRecognizeCallback): Boolean { fun startContinuousRecognition(callback: ContinuousRecognizeCallback, audioSourceType: AudioSourceType = AudioSourceType.MICROPHONE): Boolean {
if (speechConfig == null) { if (speechConfig == null) {
callback.onError("语音服务未初始化") callback.onError("语音服务未初始化")
return false return false
@ -308,7 +308,7 @@ class AzureAsrHelper(private val context: Context) {
if (isContinuousRecognitionActive) { if (isContinuousRecognitionActive) {
return true return true
} }
this.audioSourceType = audioSourceType
// 重置识别器 // 重置识别器
if (!setupRecognizer()) { if (!setupRecognizer()) {
callback.onError("重置识别器失败") callback.onError("重置识别器失败")

21
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt

@ -123,6 +123,7 @@ class AzureSpeechPlugin: FlutterPlugin, CoroutineScope {
val useExternalAudio = call.argument<Boolean>("useExternalAudio") ?: false val useExternalAudio = call.argument<Boolean>("useExternalAudio") ?: false
try { try {
FileLogger.d(tag, "选择音频源类型: ${useExternalAudio}") //
// 选择音频源类型 // 选择音频源类型
val audioSourceType = if (useExternalAudio) { val audioSourceType = if (useExternalAudio) {
AzureAsrHelper.AudioSourceType.EXTERNAL AzureAsrHelper.AudioSourceType.EXTERNAL
@ -156,7 +157,15 @@ class AzureSpeechPlugin: FlutterPlugin, CoroutineScope {
} }
} }
"recognizeOnce" -> { "recognizeOnce" -> {
val useExternalAudio = call.argument<Boolean>("useExternalAudio") ?: false
try { try {
FileLogger.d(tag, "选择音频源类型: ${useExternalAudio}") //
// 选择音频源类型
val audioSourceType = if (useExternalAudio) {
AzureAsrHelper.AudioSourceType.EXTERNAL
} else {
AzureAsrHelper.AudioSourceType.MICROPHONE
}
// 直接使用同步方式进行一次性识别 // 直接使用同步方式进行一次性识别
azureAsrHelper.recognizeOnce(object : AzureAsrHelper.RecognizeCallback { azureAsrHelper.recognizeOnce(object : AzureAsrHelper.RecognizeCallback {
override fun onResult(text: String, detectedLanguage: String) { override fun onResult(text: String, detectedLanguage: String) {
@ -170,12 +179,13 @@ class AzureSpeechPlugin: FlutterPlugin, CoroutineScope {
override fun onError(error: String) { override fun onError(error: String) {
result.error("RECOGNITION_ERROR", error, null) result.error("RECOGNITION_ERROR", error, null)
} }
}) },audioSourceType)
} catch (e: Exception) { } catch (e: Exception) {
result.error("RECOGNITION_ERROR", "识别过程异常: ${e.message}", null) result.error("RECOGNITION_ERROR", "识别过程异常: ${e.message}", null)
} }
} }
"startContinuousRecognition" -> { "startContinuousRecognition" -> {
val useExternalAudio = call.argument<Boolean>("useExternalAudio") ?: false
// 确保事件通道已准备好 // 确保事件通道已准备好
if (asrEventSink == null) { if (asrEventSink == null) {
result.error("EVENT_CHANNEL_NOT_READY", "事件通道未准备好,无法开始连续识别", null) result.error("EVENT_CHANNEL_NOT_READY", "事件通道未准备好,无法开始连续识别", null)
@ -183,6 +193,13 @@ class AzureSpeechPlugin: FlutterPlugin, CoroutineScope {
} }
try { try {
FileLogger.d(tag, "选择音频源类型: ${useExternalAudio}") //
// 选择音频源类型
val audioSourceType = if (useExternalAudio) {
AzureAsrHelper.AudioSourceType.EXTERNAL
} else {
AzureAsrHelper.AudioSourceType.MICROPHONE
}
val success = azureAsrHelper.startContinuousRecognition(object : AzureAsrHelper.ContinuousRecognizeCallback { val success = azureAsrHelper.startContinuousRecognition(object : AzureAsrHelper.ContinuousRecognizeCallback {
override fun onResult(text: String, detectedLanguage: String) { override fun onResult(text: String, detectedLanguage: String) {
sendAsrEvent(mapOf( sendAsrEvent(mapOf(
@ -222,7 +239,7 @@ class AzureSpeechPlugin: FlutterPlugin, CoroutineScope {
"message" to error "message" to error
)) ))
} }
}) },audioSourceType)
result.success(success) result.success(success)
} catch (e: Exception) { } catch (e: Exception) {
result.error("START_RECOGNITION_ERROR", e.message, null) result.error("START_RECOGNITION_ERROR", e.message, null)

2
local_plugins/ble_service/android/build.gradle.kts

@ -35,4 +35,6 @@ dependencies {
implementation("androidx.security:security-crypto:1.1.0-alpha03") implementation("androidx.security:security-crypto:1.1.0-alpha03")
implementation(project(":deepvoice_utils")) implementation(project(":deepvoice_utils"))
implementation(project(":agent_service")) implementation(project(":agent_service"))
implementation(project(":azure_speech"))
implementation(project(":jl_opus"))
} }

2
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleConst.kt

@ -23,7 +23,7 @@ object BleConst {
val NOTIFY_CHAR_UUID: UUID = UUID.fromString("0000abc2-0000-1111-2222-123456789abc") val NOTIFY_CHAR_UUID: UUID = UUID.fromString("0000abc2-0000-1111-2222-123456789abc")
/** 客户端特征配置描述符UUID */ /** 客户端特征配置描述符UUID */
val CLIENT_CHAR_CONFIG_UUID: UUID = UUID.fromString("0000fd7b-0000-1000-8000-00805f9b34fb") val CLIENT_CHAR_CONFIG_UUID: UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")
/** BLE扫描Action */ /** BLE扫描Action */
const val ACTION_SCAN_RESULT = "com.yunqiinnovation.ble_service.ACTION_BLE_SCAN" const val ACTION_SCAN_RESULT = "com.yunqiinnovation.ble_service.ACTION_BLE_SCAN"

183
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt

@ -23,6 +23,7 @@ import androidx.core.content.ContextCompat
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import android.content.pm.ServiceInfo import android.content.pm.ServiceInfo
import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.CopyOnWriteArrayList
import com.yunqiinnovation.jl_opus.JlOpusPlugin
/** /**
* BLE服务类:提供蓝牙低功耗设备的扫描、连接和通信功能 * BLE服务类:提供蓝牙低功耗设备的扫描、连接和通信功能
@ -85,6 +86,8 @@ object BleService {
// 回调集合 - 使用线程安全的集合存储所有回调 // 回调集合 - 使用线程安全的集合存储所有回调
private val callbacks = CopyOnWriteArrayList<Callback>() private val callbacks = CopyOnWriteArrayList<Callback>()
// 获取插件实例
private val opusPlugin = JlOpusPlugin()
// 初始化状态 // 初始化状态
private var isInitialized = false private var isInitialized = false
@ -101,7 +104,8 @@ object BleService {
this.context = appContext.applicationContext this.context = appContext.applicationContext
// 初始化蓝牙管理器和适配器 // 初始化蓝牙管理器和适配器
bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager bluetoothManager =
context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
bluetoothAdapter = bluetoothManager.adapter bluetoothAdapter = bluetoothManager.adapter
bluetoothLeScanner = bluetoothAdapter?.bluetoothLeScanner bluetoothLeScanner = bluetoothAdapter?.bluetoothLeScanner
@ -242,7 +246,8 @@ object BleService {
// 直接上报每个扫描结果 // 直接上报每个扫描结果
try { try {
// 将单个扫描结果转换为需要的格式 // 将单个扫描结果转换为需要的格式
val uuids = result.scanRecord?.serviceUuids?.map { it.uuid.toString() } ?: emptyList() val uuids =
result.scanRecord?.serviceUuids?.map { it.uuid.toString() } ?: emptyList()
val deviceMap = mapOf( val deviceMap = mapOf(
"id" to dev.address, "id" to dev.address,
"name" to (dev.name ?: "未知"), "name" to (dev.name ?: "未知"),
@ -273,10 +278,13 @@ object BleService {
private fun printDeviceInfo(result: ScanResult) { private fun printDeviceInfo(result: ScanResult) {
val device = result.device val device = result.device
val scanRecord = result.scanRecord val scanRecord = result.scanRecord
val serviceUuids = scanRecord?.serviceUuids?.joinToString(", ") { it.uuid.toString() } ?: "无" val serviceUuids =
scanRecord?.serviceUuids?.joinToString(", ") { it.uuid.toString() } ?: "无"
FileLogger.d(TAG, "扫描到设备: 名称=${device.name ?: "未知"}, 地址=${device.address}, " + FileLogger.d(
"RSSI=${result.rssi}, 服务UUID=[$serviceUuids]") TAG, "扫描到设备: 名称=${device.name ?: "未知"}, 地址=${device.address}, " +
"RSSI=${result.rssi}, 服务UUID=[$serviceUuids]"
)
// 打印广播数据的详细信息 // 打印广播数据的详细信息
val scanData = scanRecord?.bytes val scanData = scanRecord?.bytes
@ -372,10 +380,12 @@ object BleService {
updateConnectionState(BleConst.STATE_CONNECTED) updateConnectionState(BleConst.STATE_CONNECTED)
g.discoverServices() g.discoverServices()
} }
newState == BluetoothProfile.STATE_DISCONNECTED -> { newState == BluetoothProfile.STATE_DISCONNECTED -> {
updateConnectionState(BleConst.STATE_DISCONNECTED) updateConnectionState(BleConst.STATE_DISCONNECTED)
disconnectGatt() disconnectGatt()
} }
else -> { else -> {
updateConnectionState(BleConst.STATE_ERROR) updateConnectionState(BleConst.STATE_ERROR)
disconnectGatt() disconnectGatt()
@ -391,6 +401,7 @@ object BleService {
// 获取主服务特征 // 获取主服务特征
val primarySvc = g.getService(BleConst.PRIMARY_SERVICE_UUID) val primarySvc = g.getService(BleConst.PRIMARY_SERVICE_UUID)
notifyChar = primarySvc?.getCharacteristic(BleConst.NOTIFY_CHAR_UUID) notifyChar = primarySvc?.getCharacteristic(BleConst.NOTIFY_CHAR_UUID)
writeChar = primarySvc?.getCharacteristic(BleConst.WRITE_CHAR_UUID) writeChar = primarySvc?.getCharacteristic(BleConst.WRITE_CHAR_UUID)
@ -405,10 +416,7 @@ object BleService {
} }
writeChar?.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE writeChar?.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
//要先设置音频服务的通知,否则接收不到
// 设置主服务的通知
setupNotifications(g, notifyChar)
// 设置音频服务的通知(如果存在) // 设置音频服务的通知(如果存在)
if (audioChar != null) { if (audioChar != null) {
setupNotifications(g, audioChar) setupNotifications(g, audioChar)
@ -416,11 +424,20 @@ object BleService {
} else { } else {
FileLogger.w(TAG, "音频服务特征未找到") FileLogger.w(TAG, "音频服务特征未找到")
} }
// 设置主服务的通知
setupNotifications(g, notifyChar)
//开启解码
opusPlugin?.startOpusDecodeStream(false, 1, 16000, 40, object :
JlOpusPlugin.CallbackOpus {
override fun onAudioDataReceivedOpus(pcmData: ByteArray) {
// 回调给上层
notifyAudioDataReceived(pcmData)
}
})
} }
override fun onCharacteristicChanged(g: BluetoothGatt, c: BluetoothGattCharacteristic) { override fun onCharacteristicChanged(g: BluetoothGatt, c: BluetoothGattCharacteristic) {
FileLogger.d(TAG, "收到特征数据: ${c.uuid}, 数据=${c.value?.joinToString(", ") { "0x${(it.toInt() and 0xFF).toString(16)}" }}")
val data = c.value ?: return val data = c.value ?: return
// 根据特征UUID区分处理 // 根据特征UUID区分处理
@ -443,13 +460,23 @@ object BleService {
} }
// 其他未知数据 // 其他未知数据
else -> { else -> {
FileLogger.d(TAG, "收到未知数据: ${data.joinToString(", ") { "0x%02X".format(it) }}") FileLogger.d(
TAG,
"收到未知数据: ${data.joinToString(", ") { "0x%02X".format(it) }}"
)
} }
} }
} }
// 其他未知特征 // 其他未知特征
else -> { else -> {
FileLogger.d(TAG, "收到未知特征数据: ${c.uuid}, 数据=${data.joinToString(", ") { "0x%02X".format(it) }}") FileLogger.d(
TAG,
"收到未知特征数据: ${c.uuid}, 数据=${
data.joinToString(", ") {
"0x%02X".format(it)
}
}"
)
} }
} }
} }
@ -457,7 +484,10 @@ object BleService {
override fun onCharacteristicWrite( override fun onCharacteristicWrite(
g: BluetoothGatt, c: BluetoothGattCharacteristic, status: Int g: BluetoothGatt, c: BluetoothGattCharacteristic, status: Int
) { ) {
FileLogger.i(TAG, "写入 ${if (status == BluetoothGatt.GATT_SUCCESS) "成功" else "失败"}") FileLogger.i(
TAG,
"写入 ${if (status == BluetoothGatt.GATT_SUCCESS) "成功" else "失败"}"
)
} }
override fun onDescriptorWrite( override fun onDescriptorWrite(
@ -489,11 +519,9 @@ object BleService {
*/ */
private fun processAudioData(data: ByteArray) { private fun processAudioData(data: ByteArray) {
try { try {
// 记录日志
FileLogger.d(TAG, "收到音频数据: ${data.size}字节")
// 回调给上层 opusPlugin?.handleDecodeRequest(data)
notifyAudioDataReceived(data)
} catch (e: Exception) { } catch (e: Exception) {
FileLogger.e(TAG, "处理音频数据异常: ${e.message}", e) FileLogger.e(TAG, "处理音频数据异常: ${e.message}", e)
} }
@ -503,7 +531,10 @@ object BleService {
* 处理设备响应数据 - 帧头为0xBB * 处理设备响应数据 - 帧头为0xBB
*/ */
private fun processDeviceResponse(data: ByteArray) { private fun processDeviceResponse(data: ByteArray) {
FileLogger.d(TAG, "收到设备响应: ${data.joinToString(", ") { "0x${(it.toInt() and 0xFF).toString(16)}" }}") FileLogger.d(
TAG,
"收到设备响应: ${data.joinToString(", ") { "0x${(it.toInt() and 0xFF).toString(16)}" }}"
)
if (data.size < 3) { if (data.size < 3) {
FileLogger.e(TAG, "设备响应数据格式错误:数据长度过短") FileLogger.e(TAG, "设备响应数据格式错误:数据长度过短")
return return
@ -540,7 +571,10 @@ object BleService {
val receivedCrc = data[data.size - 1].toInt() and 0xFF val receivedCrc = data[data.size - 1].toInt() and 0xFF
if (calculatedCrc.toInt() and 0xFF != receivedCrc) { if (calculatedCrc.toInt() and 0xFF != receivedCrc) {
FileLogger.e(TAG, "设备响应CRC校验失败: 计算=${calculatedCrc.toInt() and 0xFF}, 接收=${receivedCrc}") FileLogger.e(
TAG,
"设备响应CRC校验失败: 计算=${calculatedCrc.toInt() and 0xFF}, 接收=${receivedCrc}"
)
return return
} }
@ -550,7 +584,16 @@ object BleService {
System.arraycopy(data, 3, responseData, 0, length) System.arraycopy(data, 3, responseData, 0, length)
} }
FileLogger.d(TAG, "收到设备响应: CMD=0x${cmdType.toString(16)}, 数据=${responseData.joinToString(", ") { "0x${(it.toInt() and 0xFF).toString(16)}" }}") FileLogger.d(
TAG,
"收到设备响应: CMD=0x${cmdType.toString(16)}, 数据=${
responseData.joinToString(", ") {
"0x${
(it.toInt() and 0xFF).toString(16)
}"
}
}"
)
// 根据命令类型处理不同的响应 // 根据命令类型处理不同的响应
when (cmdType) { when (cmdType) {
@ -563,13 +606,15 @@ object BleService {
if (responseData.isNotEmpty()) { if (responseData.isNotEmpty()) {
// 转换为字符串前,确认是否是有效的ASCII字符 // 转换为字符串前,确认是否是有效的ASCII字符
val version = responseData.joinToString(".") { (it.toInt() and 0xFF).toString() } val version =
responseData.joinToString(".") { (it.toInt() and 0xFF).toString() }
resultMap["version"] = version resultMap["version"] = version
FileLogger.i(TAG, "接收到目标版本号: $version") FileLogger.i(TAG, "接收到目标版本号: $version")
} }
notifyDeviceInfoReceived(cmdType, resultMap) notifyDeviceInfoReceived(cmdType, resultMap)
} }
BleConst.CMD_GET_PRODUCT_ID -> { BleConst.CMD_GET_PRODUCT_ID -> {
val resultMap = mutableMapOf<String, Any>( val resultMap = mutableMapOf<String, Any>(
"success" to true, "success" to true,
@ -578,13 +623,16 @@ object BleService {
) )
if (responseData.isNotEmpty() && responseData.size >= 2) { if (responseData.isNotEmpty() && responseData.size >= 2) {
val productId = "0x${(responseData[0].toInt() and 0xFF).toString(16)}${(responseData[1].toInt() and 0xFF).toString(16)}" val productId = "0x${(responseData[0].toInt() and 0xFF).toString(16)}${
(responseData[1].toInt() and 0xFF).toString(16)
}"
resultMap["productId"] = productId resultMap["productId"] = productId
FileLogger.i(TAG, "接收到产品ID: $productId") FileLogger.i(TAG, "接收到产品ID: $productId")
} }
notifyDeviceInfoReceived(cmdType, resultMap) notifyDeviceInfoReceived(cmdType, resultMap)
} }
BleConst.CMD_GET_COLOR_ID -> { BleConst.CMD_GET_COLOR_ID -> {
val resultMap = mutableMapOf<String, Any>( val resultMap = mutableMapOf<String, Any>(
"success" to true, "success" to true,
@ -600,6 +648,7 @@ object BleService {
notifyDeviceInfoReceived(cmdType, resultMap) notifyDeviceInfoReceived(cmdType, resultMap)
} }
BleConst.CMD_GET_BATTERY_INFO -> { BleConst.CMD_GET_BATTERY_INFO -> {
val resultMap = mutableMapOf<String, Any>( val resultMap = mutableMapOf<String, Any>(
"success" to true, "success" to true,
@ -612,11 +661,15 @@ object BleService {
val leftEarBattery = responseData[0].toInt() and 0x7F val leftEarBattery = responseData[0].toInt() and 0x7F
val leftEarCharging = (responseData[0].toInt() and 0x80) != 0 val leftEarCharging = (responseData[0].toInt() and 0x80) != 0
val rightEarBattery = if (responseData.size > 1) responseData[1].toInt() and 0x7F else 0 val rightEarBattery =
val rightEarCharging = if (responseData.size > 1) (responseData[1].toInt() and 0x80) != 0 else false if (responseData.size > 1) responseData[1].toInt() and 0x7F else 0
val rightEarCharging =
if (responseData.size > 1) (responseData[1].toInt() and 0x80) != 0 else false
val caseBattery = if (responseData.size > 2) responseData[2].toInt() and 0x7F else 0 val caseBattery =
val caseCharging = if (responseData.size > 2) (responseData[2].toInt() and 0x80) != 0 else false if (responseData.size > 2) responseData[2].toInt() and 0x7F else 0
val caseCharging =
if (responseData.size > 2) (responseData[2].toInt() and 0x80) != 0 else false
// 添加到结果Map // 添加到结果Map
resultMap["leftEarBattery"] = leftEarBattery resultMap["leftEarBattery"] = leftEarBattery
@ -627,14 +680,17 @@ object BleService {
resultMap["caseCharging"] = caseCharging resultMap["caseCharging"] = caseCharging
resultMap["batteryLevel"] = leftEarBattery // 整体电量用左耳表示 resultMap["batteryLevel"] = leftEarBattery // 整体电量用左耳表示
FileLogger.i(TAG, "接收到电量信息: 左耳=${leftEarBattery}% " + FileLogger.i(
TAG, "接收到电量信息: 左耳=${leftEarBattery}% " +
"(${if (leftEarCharging) "充电中" else "未充电"}), 右耳=${rightEarBattery}% " + "(${if (leftEarCharging) "充电中" else "未充电"}), 右耳=${rightEarBattery}% " +
"(${if (rightEarCharging) "充电中" else "未充电"}), 充电仓=${caseBattery}% " + "(${if (rightEarCharging) "充电中" else "未充电"}), 充电仓=${caseBattery}% " +
"(${if(caseCharging) "充电中" else "未充电"})") "(${if (caseCharging) "充电中" else "未充电"})"
)
} }
notifyDeviceInfoReceived(cmdType, resultMap) notifyDeviceInfoReceived(cmdType, resultMap)
} }
BleConst.CMD_CONTROL_CODEC -> { BleConst.CMD_CONTROL_CODEC -> {
val resultMap = mutableMapOf<String, Any>( val resultMap = mutableMapOf<String, Any>(
"success" to true, "success" to true,
@ -673,6 +729,7 @@ object BleService {
notifyDeviceInfoReceived(cmdType, resultMap) notifyDeviceInfoReceived(cmdType, resultMap)
} }
else -> { else -> {
// 对于未知命令,返回原始数据 // 对于未知命令,返回原始数据
val resultMap = mapOf( val resultMap = mapOf(
@ -691,7 +748,10 @@ object BleService {
* 处理设备主动上报数据 - 帧头为0xCC * 处理设备主动上报数据 - 帧头为0xCC
*/ */
private fun processDeviceNotification(data: ByteArray) { private fun processDeviceNotification(data: ByteArray) {
FileLogger.d(TAG, "收到设备主动上报: ${data.joinToString(", ") { "0x${(it.toInt() and 0xFF).toString(16)}" }}") FileLogger.d(
TAG,
"收到设备主动上报: ${data.joinToString(", ") { "0x${(it.toInt() and 0xFF).toString(16)}" }}"
)
if (data.size < 3) { if (data.size < 3) {
FileLogger.e(TAG, "设备主动上报数据格式错误:数据长度过短") FileLogger.e(TAG, "设备主动上报数据格式错误:数据长度过短")
return return
@ -713,7 +773,10 @@ object BleService {
val receivedCrc = data[data.size - 1].toInt() and 0xFF val receivedCrc = data[data.size - 1].toInt() and 0xFF
if (calculatedCrc.toInt() and 0xFF != receivedCrc) { if (calculatedCrc.toInt() and 0xFF != receivedCrc) {
FileLogger.e(TAG, "设备主动上报CRC校验失败: 计算=${calculatedCrc.toInt() and 0xFF}, 接收=${receivedCrc}") FileLogger.e(
TAG,
"设备主动上报CRC校验失败: 计算=${calculatedCrc.toInt() and 0xFF}, 接收=${receivedCrc}"
)
return return
} }
@ -723,7 +786,16 @@ object BleService {
System.arraycopy(data, 3, notifyData, 0, length) System.arraycopy(data, 3, notifyData, 0, length)
} }
FileLogger.d(TAG, "收到设备主动上报: 类型=0x${infoType.toString(16)}, 数据=${notifyData.joinToString(", ") { "0x${(it.toInt() and 0xFF).toString(16)}" }}") FileLogger.d(
TAG,
"收到设备主动上报: 类型=0x${infoType.toString(16)}, 数据=${
notifyData.joinToString(", ") {
"0x${
(it.toInt() and 0xFF).toString(16)
}"
}
}"
)
// 根据上报类型处理 // 根据上报类型处理
when (infoType) { when (infoType) {
@ -740,11 +812,14 @@ object BleService {
val leftEarBattery = notifyData[0].toInt() and 0x7F val leftEarBattery = notifyData[0].toInt() and 0x7F
val leftEarCharging = (notifyData[0].toInt() and 0x80) != 0 val leftEarCharging = (notifyData[0].toInt() and 0x80) != 0
val rightEarBattery = if (notifyData.size > 1) notifyData[1].toInt() and 0x7F else 0 val rightEarBattery =
val rightEarCharging = if (notifyData.size > 1) (notifyData[1].toInt() and 0x80) != 0 else false if (notifyData.size > 1) notifyData[1].toInt() and 0x7F else 0
val rightEarCharging =
if (notifyData.size > 1) (notifyData[1].toInt() and 0x80) != 0 else false
val caseBattery = if (notifyData.size > 2) notifyData[2].toInt() and 0x7F else 0 val caseBattery = if (notifyData.size > 2) notifyData[2].toInt() and 0x7F else 0
val caseCharging = if (notifyData.size > 2) (notifyData[2].toInt() and 0x80) != 0 else false val caseCharging =
if (notifyData.size > 2) (notifyData[2].toInt() and 0x80) != 0 else false
// 添加到结果Map // 添加到结果Map
@ -756,14 +831,17 @@ object BleService {
resultMap["caseCharging"] = caseCharging resultMap["caseCharging"] = caseCharging
resultMap["batteryLevel"] = leftEarBattery // 整体电量用左耳表示 resultMap["batteryLevel"] = leftEarBattery // 整体电量用左耳表示
FileLogger.i(TAG, "收到主动上报电量信息: 左耳=${leftEarBattery}% " + FileLogger.i(
TAG, "收到主动上报电量信息: 左耳=${leftEarBattery}% " +
"(${if (leftEarCharging) "充电中" else "未充电"}), 右耳=${rightEarBattery}% " + "(${if (leftEarCharging) "充电中" else "未充电"}), 右耳=${rightEarBattery}% " +
"(${if (rightEarCharging) "充电中" else "未充电"}), 充电仓=${caseBattery}% " + "(${if (rightEarCharging) "充电中" else "未充电"}), 充电仓=${caseBattery}% " +
"(${if(caseCharging) "充电中" else "未充电"})") "(${if (caseCharging) "充电中" else "未充电"})"
)
} }
notifyDeviceInfoReceived(infoType, resultMap) notifyDeviceInfoReceived(infoType, resultMap)
} }
BleConst.CMD_WAKE_UP -> { BleConst.CMD_WAKE_UP -> {
FileLogger.i(TAG, "收到唤醒指令") FileLogger.i(TAG, "收到唤醒指令")
// 接收到唤醒信号,打开编码器 // 接收到唤醒信号,打开编码器
@ -827,7 +905,10 @@ object BleService {
*/ */
fun closeCodec(): Boolean { fun closeCodec(): Boolean {
FileLogger.i(TAG, "关闭编解码...") FileLogger.i(TAG, "关闭编解码...")
return sendCommand(BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf(BleConst.CODEC_CONTROL_CLOSE.toByte())) return sendCommand(
BleConst.CMD_CONTROL_CODEC.toByte(),
byteArrayOf(BleConst.CODEC_CONTROL_CLOSE.toByte())
)
} }
/** /**
@ -835,7 +916,10 @@ object BleService {
*/ */
fun openDecoder(): Boolean { fun openDecoder(): Boolean {
FileLogger.i(TAG, "打开解码...") FileLogger.i(TAG, "打开解码...")
return sendCommand(BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf(BleConst.CODEC_CONTROL_DECODE_ON.toByte())) return sendCommand(
BleConst.CMD_CONTROL_CODEC.toByte(),
byteArrayOf(BleConst.CODEC_CONTROL_DECODE_ON.toByte())
)
} }
/** /**
@ -851,7 +935,13 @@ object BleService {
*/ */
fun openEncoder(): Boolean { fun openEncoder(): Boolean {
FileLogger.i(TAG, "打开编码...") FileLogger.i(TAG, "打开编码...")
return sendCommand(BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf(BleConst.CODEC_CONTROL_ENCODE_ON.toByte())) return sendCommand(
BleConst.CMD_CONTROL_CODEC.toByte(),
byteArrayOf(
BleConst.CODEC_CONTROL_ENCODE_ON.toByte(),
BleConst.AUDIO_CHANNEL_RIGHT.toByte()
)
)
} }
/** /**
@ -870,10 +960,12 @@ object BleService {
} }
// 高级控制:设置命令类型、长度和两个参数 // 高级控制:设置命令类型、长度和两个参数
return sendCommand(BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf( return sendCommand(
BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf(
0xA1.toByte(), // 状态码 - 使用解码状态 0xA1.toByte(), // 状态码 - 使用解码状态
channelMode.toByte() // 声道模式 channelMode.toByte() // 声道模式
)) )
)
} }
/** /**
@ -1046,9 +1138,12 @@ object BleService {
cmdPacket[3 + cmdLength] = crc cmdPacket[3 + cmdLength] = crc
// 记录命令详情 // 记录命令详情
FileLogger.d(TAG, "发送命令: CMD=0x${(cmdType.toInt() and 0xFF).toString(16)}, 长度=$cmdLength, " + FileLogger.d(
TAG,
"发送命令: CMD=0x${(cmdType.toInt() and 0xFF).toString(16)}, 长度=$cmdLength, " +
"数据=${cmdData.joinToString(", ") { "0x${(it.toInt() and 0xFF).toString(16)}" }}, " + "数据=${cmdData.joinToString(", ") { "0x${(it.toInt() and 0xFF).toString(16)}" }}, " +
"CRC=0x${(crc.toInt() and 0xFF).toString(16)}") "CRC=0x${(crc.toInt() and 0xFF).toString(16)}"
)
// 发送数据 // 发送数据
writeChar!!.value = cmdPacket writeChar!!.value = cmdPacket

5
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/ChatAgent.kt

@ -31,7 +31,7 @@ object ChatAgent : BleService.Callback, AgentServiceListener {
FileLogger.d(TAG, "初始化语音代理..., $config") FileLogger.d(TAG, "初始化语音代理..., $config")
// 1. 初始化AgentService // 1. 初始化AgentService
if (!AgentService.initialize(context, config)) { if (!AgentService.initialize(context, config, true)) {
FileLogger.e(TAG, "语音服务初始化失败") FileLogger.e(TAG, "语音服务初始化失败")
return false return false
} }
@ -67,7 +67,7 @@ object ChatAgent : BleService.Callback, AgentServiceListener {
return false return false
} }
return AgentService.startRecognition() return AgentService.startRecognition(true)
} }
/** /**
@ -127,6 +127,7 @@ object ChatAgent : BleService.Callback, AgentServiceListener {
} }
override fun onAudioDataReceived(data: ByteArray) { override fun onAudioDataReceived(data: ByteArray) {
AgentService.azureAsrHelper?.pushAudioData(data)
// 可选:处理音频数据 // 可选:处理音频数据
} }

8
local_plugins/jieli/android/src/main/kotlin/com/yunqiinnovation/jieli/BleService.kt

@ -70,6 +70,7 @@ class BleService(private val context: Context) {
// 获取插件实例 // 获取插件实例
private val opusPlugin = JlOpusPlugin() private val opusPlugin = JlOpusPlugin()
// 蓝牙管理器和适配器 // 蓝牙管理器和适配器
private val bluetoothManager = private val bluetoothManager =
context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
@ -326,12 +327,13 @@ class BleService(private val context: Context) {
setupNotifications(gatt) setupNotifications(gatt)
opusPlugin?.startOpusDecodeStream(false, 1, 16000, 40, object : opusPlugin?.startOpusDecodeStream(false, 1, 16000, 40, object :
JlOpusPlugin.Callback1 { JlOpusPlugin.CallbackOpus {
override fun onAudioDataReceived1(pcmData: ByteArray) { override fun onAudioDataReceivedOpus(pcmData: ByteArray) {
// Log.i(TAG, "收到解码控制通知: ${pcmData.contentToString()}") // Log.i(TAG, "收到解码控制通知: ${pcmData.contentToString()}")
callback?.onAudioDataReceived(pcmData) callback?.onAudioDataReceived(pcmData)
// 处理解码后的 PCM 数据(如播放或写入文件) // 处理解码后的 PCM 数据(如播放或写入文件)
}}) }
})
} else { } else {
Log.e(TAG, "未找到所需特征") Log.e(TAG, "未找到所需特征")
updateConnectionState(STATE_ERROR) updateConnectionState(STATE_ERROR)

107
local_plugins/jl_opus/android/src/main/kotlin/com/yunqiinnovation/jl_opus/JlOpusPlugin.kt

@ -25,6 +25,7 @@ class JlOpusPlugin: FlutterPlugin, MethodCallHandler {
private lateinit var context: Context private lateinit var context: Context
private var opusManager: OpusManager? = null private var opusManager: OpusManager? = null
private var streamSink: EventChannel.EventSink? = null private var streamSink: EventChannel.EventSink? = null
// 修正1:正确的主构造函数写法 // 修正1:正确的主构造函数写法
constructor() { constructor() {
opusManager = OpusManager() opusManager = OpusManager()
@ -33,9 +34,10 @@ class JlOpusPlugin: FlutterPlugin, MethodCallHandler {
// 添加主线程Handler用于确保在主线程回调 // 添加主线程Handler用于确保在主线程回调
private val mainHandler = Handler(Looper.getMainLooper()) private val mainHandler = Handler(Looper.getMainLooper())
interface Callback1 {
fun onAudioDataReceived1(data: ByteArray) interface CallbackOpus {
fun onAudioDataReceivedOpus(data: ByteArray)
} }
@ -102,7 +104,12 @@ class JlOpusPlugin: FlutterPlugin, MethodCallHandler {
} }
// 在主线程上安全返回错误 // 在主线程上安全返回错误
private fun sendErrorOnMainThread(result: Result, errorCode: String, errorMessage: String, errorDetails: Any?) { private fun sendErrorOnMainThread(
result: Result,
errorCode: String,
errorMessage: String,
errorDetails: Any?
) {
if (Looper.myLooper() == Looper.getMainLooper()) { if (Looper.myLooper() == Looper.getMainLooper()) {
// 如果已经在主线程,直接返回 // 如果已经在主线程,直接返回
result.error(errorCode, errorMessage, errorDetails) result.error(errorCode, errorMessage, errorDetails)
@ -124,6 +131,7 @@ class JlOpusPlugin: FlutterPlugin, MethodCallHandler {
"initOpusDecoder" -> { "initOpusDecoder" -> {
result.success(opusManager != null) result.success(opusManager != null)
} }
"decodeOpusFile" -> { "decodeOpusFile" -> {
val inPath = call.argument<String>("inPath") val inPath = call.argument<String>("inPath")
val outPath = call.argument<String>("outPath") val outPath = call.argument<String>("outPath")
@ -156,34 +164,46 @@ class JlOpusPlugin: FlutterPlugin, MethodCallHandler {
opusManager?.decodeFile(inPath, outPath, option, object : OnStateCallback { opusManager?.decodeFile(inPath, outPath, option, object : OnStateCallback {
override fun onStart() { override fun onStart() {
Log.d(TAG, "文件解码开始") Log.d(TAG, "文件解码开始")
sendEventOnMainThread(mapOf( sendEventOnMainThread(
mapOf(
"event" to "onStart", "event" to "onStart",
"type" to "file" "type" to "file"
)) )
)
} }
override fun onComplete(outFilePath: String?) { override fun onComplete(outFilePath: String?) {
Log.d(TAG, "文件解码完成: $outFilePath") Log.d(TAG, "文件解码完成: $outFilePath")
sendEventOnMainThread(mapOf( sendEventOnMainThread(
mapOf(
"event" to "onComplete", "event" to "onComplete",
"type" to "file", "type" to "file",
"filePath" to outFilePath "filePath" to outFilePath
)) )
)
sendResultOnMainThread(result, outFilePath) sendResultOnMainThread(result, outFilePath)
} }
override fun onError(code: Int, message: String?) { override fun onError(code: Int, message: String?) {
Log.e(TAG, "文件解码错误: [$code] $message") Log.e(TAG, "文件解码错误: [$code] $message")
sendEventOnMainThread(mapOf( sendEventOnMainThread(
mapOf(
"event" to "onError", "event" to "onError",
"type" to "file", "type" to "file",
"code" to code, "code" to code,
"message" to message "message" to message
)) )
sendErrorOnMainThread(result, "DECODE_ERROR", "解码出错: [$code] $message", null) )
sendErrorOnMainThread(
result,
"DECODE_ERROR",
"解码出错: [$code] $message",
null
)
} }
}) })
} }
"startDecodeStream" -> { "startDecodeStream" -> {
val hasHeader = call.argument<Boolean>("hasHeader") ?: false val hasHeader = call.argument<Boolean>("hasHeader") ?: false
val channel = call.argument<Int>("channel") ?: 1 val channel = call.argument<Int>("channel") ?: 1
@ -207,42 +227,56 @@ class JlOpusPlugin: FlutterPlugin, MethodCallHandler {
opusManager?.startDecodeStream(option, object : OnDecodeStreamCallback { opusManager?.startDecodeStream(option, object : OnDecodeStreamCallback {
override fun onDecodeStream(data: ByteArray?) { override fun onDecodeStream(data: ByteArray?) {
if (data != null) { if (data != null) {
sendEventOnMainThread(mapOf( sendEventOnMainThread(
mapOf(
"event" to "onDecodeStream", "event" to "onDecodeStream",
"data" to data "data" to data
)) )
)
} }
} }
override fun onStart() { override fun onStart() {
Log.d(TAG, "数据流解码开始") Log.d(TAG, "数据流解码开始")
sendEventOnMainThread(mapOf( sendEventOnMainThread(
mapOf(
"event" to "onStart", "event" to "onStart",
"type" to "stream" "type" to "stream"
)) )
)
sendResultOnMainThread(result, true) sendResultOnMainThread(result, true)
} }
override fun onComplete(outPath: String?) { override fun onComplete(outPath: String?) {
Log.d(TAG, "数据流解码完成: $outPath") Log.d(TAG, "数据流解码完成: $outPath")
sendEventOnMainThread(mapOf( sendEventOnMainThread(
mapOf(
"event" to "onComplete", "event" to "onComplete",
"type" to "stream" "type" to "stream"
)) )
)
} }
override fun onError(code: Int, message: String?) { override fun onError(code: Int, message: String?) {
Log.e(TAG, "数据流解码错误: [$code] $message") Log.e(TAG, "数据流解码错误: [$code] $message")
sendEventOnMainThread(mapOf( sendEventOnMainThread(
mapOf(
"event" to "onError", "event" to "onError",
"type" to "stream", "type" to "stream",
"code" to code, "code" to code,
"message" to message "message" to message
)) )
sendErrorOnMainThread(result, "DECODE_STREAM_ERROR", "数据流解码出错: [$code] $message", null) )
sendErrorOnMainThread(
result,
"DECODE_STREAM_ERROR",
"数据流解码出错: [$code] $message",
null
)
} }
}) })
} }
"stopDecodeStream" -> { "stopDecodeStream" -> {
if (opusManager?.isDecodeStream == true) { if (opusManager?.isDecodeStream == true) {
opusManager?.stopDecodeStream() opusManager?.stopDecodeStream()
@ -251,6 +285,7 @@ class JlOpusPlugin: FlutterPlugin, MethodCallHandler {
result.success(false) result.success(false)
} }
} }
"writeAudioStream" -> { "writeAudioStream" -> {
val data = call.argument<ByteArray>("data") val data = call.argument<ByteArray>("data")
if (data == null) { if (data == null) {
@ -265,9 +300,11 @@ class JlOpusPlugin: FlutterPlugin, MethodCallHandler {
result.error("NOT_DECODING", "当前没有处于解码状态", null) result.error("NOT_DECODING", "当前没有处于解码状态", null)
} }
} }
"isDecoding" -> { "isDecoding" -> {
result.success(opusManager?.isDecodeStream == true) result.success(opusManager?.isDecodeStream == true)
} }
"dispose" -> { "dispose" -> {
opusManager?.let { opusManager?.let {
if (it.isDecodeStream) { if (it.isDecodeStream) {
@ -278,11 +315,13 @@ class JlOpusPlugin: FlutterPlugin, MethodCallHandler {
opusManager = null opusManager = null
result.success(true) result.success(true)
} }
else -> { else -> {
result.notImplemented() result.notImplemented()
} }
} }
} }
fun handleDecodeRequest(opusdata: ByteArray) { fun handleDecodeRequest(opusdata: ByteArray) {
// Log.d(TAG, "handleDecodeRequest${opusdata}") // Log.d(TAG, "handleDecodeRequest${opusdata}")
@ -298,12 +337,13 @@ class JlOpusPlugin: FlutterPlugin, MethodCallHandler {
} }
} }
fun startOpusDecodeStream( fun startOpusDecodeStream(
hasHeader: Boolean, hasHeader: Boolean,
channel: Int, channel: Int,
sampleRate: Int, sampleRate: Int,
packetSize: Int, packetSize: Int,
callback: Callback1 callback: CallbackOpus
) { ) {
Log.d(TAG, "开始解码数据流,startOpusDecodeStream: $opusManager") Log.d(TAG, "开始解码数据流,startOpusDecodeStream: $opusManager")
@ -325,43 +365,52 @@ class JlOpusPlugin: FlutterPlugin, MethodCallHandler {
override fun onDecodeStream(data: ByteArray?) { override fun onDecodeStream(data: ByteArray?) {
if (data != null) { if (data != null) {
//Log.d(TAG, "onDecodeStream${data}") //Log.d(TAG, "onDecodeStream${data}")
callback.onAudioDataReceived1(data) callback.onAudioDataReceivedOpus(data)
sendEventOnMainThread(mapOf( sendEventOnMainThread(
mapOf(
"event" to "onDecodeStream", "event" to "onDecodeStream",
"data" to data "data" to data
)) )
)
} }
} }
override fun onStart() { override fun onStart() {
Log.d(TAG, "数据流解码开始") Log.d(TAG, "数据流解码开始")
sendEventOnMainThread(mapOf( sendEventOnMainThread(
mapOf(
"event" to "onStart", "event" to "onStart",
"type" to "stream" "type" to "stream"
)) )
)
} }
override fun onComplete(outPath: String?) { override fun onComplete(outPath: String?) {
Log.d(TAG, "数据流解码完成: $outPath") Log.d(TAG, "数据流解码完成: $outPath")
sendEventOnMainThread(mapOf( sendEventOnMainThread(
mapOf(
"event" to "onComplete", "event" to "onComplete",
"type" to "stream" "type" to "stream"
)) )
)
} }
override fun onError(code: Int, message: String?) { override fun onError(code: Int, message: String?) {
Log.e(TAG, "数据流解码错误: [$code] $message") Log.e(TAG, "数据流解码错误: [$code] $message")
sendEventOnMainThread(mapOf( sendEventOnMainThread(
mapOf(
"event" to "onError", "event" to "onError",
"type" to "stream", "type" to "stream",
"code" to code, "code" to code,
"message" to message "message" to message
)) )
)
} }
}) })
} }
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null) channel.setMethodCallHandler(null)

Loading…
Cancel
Save