Browse Source

修改ble通信

weicu
fdp 1 year ago
parent
commit
eab05419d6
  1. 20
      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. 201
      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. 16
      local_plugins/jieli/android/src/main/kotlin/com/yunqiinnovation/jieli/BleService.kt
  10. 653
      local_plugins/jl_opus/android/src/main/kotlin/com/yunqiinnovation/jl_opus/JlOpusPlugin.kt

20
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"
// Azure服务
private var azureAsrHelper: AzureAsrHelper? = null
var azureAsrHelper: AzureAsrHelper? = null
private var azureTtsHelper: AzureTtsHelper? = null
// OpenAI服务
@ -112,7 +112,7 @@ object AgentService : CoroutineScope {
* @param config 配置参数,包含所需的所有API密钥和设置
* @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
try {
@ -149,7 +149,8 @@ object AgentService : CoroutineScope {
initialize(
subscriptionKey = azureSpeechKey,
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) {
FileLogger.e(TAG, "服务未初始化")
return false
@ -295,7 +296,14 @@ object AgentService : CoroutineScope {
isRecognitionActive = true
hasSpeechDetected = false
try {
try {
FileLogger.d(TAG, "选择音频源类型: ${isExternalActive}") //
// 选择音频源类型
val audioSourceType = if (isExternalActive) {
AzureAsrHelper.AudioSourceType.EXTERNAL
} else {
AzureAsrHelper.AudioSourceType.MICROPHONE
}
azureAsrHelper?.startContinuousRecognition(object : AzureAsrHelper.ContinuousRecognizeCallback {
override fun onRecognizing(recognizing: String, detectedLanguage: String) {
if (recognizing.isNotEmpty()) {
@ -370,7 +378,7 @@ object AgentService : CoroutineScope {
"message" to error
))
}
})
},audioSourceType)
return true
} catch (e: Exception) {
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" -> {
try {
val isStarted = AgentService.startRecognition()
val isStarted = AgentService.startRecognition(false)
result.success(isStarted)
} catch (e: Exception) {
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 识别结果回调
*/
fun recognizeOnce(callback: RecognizeCallback) {
fun recognizeOnce(callback: RecognizeCallback, audioSourceType: AudioSourceType = AudioSourceType.MICROPHONE) {
if (speechConfig == null) {
callback.onError("语音服务未初始化")
return
}
this.audioSourceType = audioSourceType
// 确保不在连续识别中
if (isContinuousRecognitionActive) {
stopContinuousRecognition()
@ -299,7 +299,7 @@ class AzureAsrHelper(private val context: Context) {
* @param callback 连续识别结果回调
* @return 是否成功开始识别
*/
fun startContinuousRecognition(callback: ContinuousRecognizeCallback): Boolean {
fun startContinuousRecognition(callback: ContinuousRecognizeCallback, audioSourceType: AudioSourceType = AudioSourceType.MICROPHONE): Boolean {
if (speechConfig == null) {
callback.onError("语音服务未初始化")
return false
@ -308,7 +308,7 @@ class AzureAsrHelper(private val context: Context) {
if (isContinuousRecognitionActive) {
return true
}
this.audioSourceType = audioSourceType
// 重置识别器
if (!setupRecognizer()) {
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
try {
FileLogger.d(tag, "选择音频源类型: ${useExternalAudio}") //
// 选择音频源类型
val audioSourceType = if (useExternalAudio) {
AzureAsrHelper.AudioSourceType.EXTERNAL
@ -156,7 +157,15 @@ class AzureSpeechPlugin: FlutterPlugin, CoroutineScope {
}
}
"recognizeOnce" -> {
val useExternalAudio = call.argument<Boolean>("useExternalAudio") ?: false
try {
FileLogger.d(tag, "选择音频源类型: ${useExternalAudio}") //
// 选择音频源类型
val audioSourceType = if (useExternalAudio) {
AzureAsrHelper.AudioSourceType.EXTERNAL
} else {
AzureAsrHelper.AudioSourceType.MICROPHONE
}
// 直接使用同步方式进行一次性识别
azureAsrHelper.recognizeOnce(object : AzureAsrHelper.RecognizeCallback {
override fun onResult(text: String, detectedLanguage: String) {
@ -170,12 +179,13 @@ class AzureSpeechPlugin: FlutterPlugin, CoroutineScope {
override fun onError(error: String) {
result.error("RECOGNITION_ERROR", error, null)
}
})
},audioSourceType)
} catch (e: Exception) {
result.error("RECOGNITION_ERROR", "识别过程异常: ${e.message}", null)
}
}
"startContinuousRecognition" -> {
val useExternalAudio = call.argument<Boolean>("useExternalAudio") ?: false
// 确保事件通道已准备好
if (asrEventSink == null) {
result.error("EVENT_CHANNEL_NOT_READY", "事件通道未准备好,无法开始连续识别", null)
@ -183,6 +193,13 @@ class AzureSpeechPlugin: FlutterPlugin, CoroutineScope {
}
try {
FileLogger.d(tag, "选择音频源类型: ${useExternalAudio}") //
// 选择音频源类型
val audioSourceType = if (useExternalAudio) {
AzureAsrHelper.AudioSourceType.EXTERNAL
} else {
AzureAsrHelper.AudioSourceType.MICROPHONE
}
val success = azureAsrHelper.startContinuousRecognition(object : AzureAsrHelper.ContinuousRecognizeCallback {
override fun onResult(text: String, detectedLanguage: String) {
sendAsrEvent(mapOf(
@ -222,7 +239,7 @@ class AzureSpeechPlugin: FlutterPlugin, CoroutineScope {
"message" to error
))
}
})
},audioSourceType)
result.success(success)
} catch (e: Exception) {
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(project(":deepvoice_utils"))
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")
/** 客户端特征配置描述符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 */
const val ACTION_SCAN_RESULT = "com.yunqiinnovation.ble_service.ACTION_BLE_SCAN"

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

16
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 bluetoothManager =
context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
@ -325,13 +326,14 @@ class BleService(private val context: Context) {
// 注册特征通知
setupNotifications(gatt)
opusPlugin?.startOpusDecodeStream(false, 1, 16000, 40, object :
JlOpusPlugin.Callback1 {
override fun onAudioDataReceived1(pcmData: ByteArray) {
// Log.i(TAG, "收到解码控制通知: ${pcmData.contentToString()}")
opusPlugin?.startOpusDecodeStream(false, 1, 16000, 40, object :
JlOpusPlugin.CallbackOpus {
override fun onAudioDataReceivedOpus(pcmData: ByteArray) {
// Log.i(TAG, "收到解码控制通知: ${pcmData.contentToString()}")
callback?.onAudioDataReceived(pcmData)
// 处理解码后的 PCM 数据(如播放或写入文件)
}})
}
})
} else {
Log.e(TAG, "未找到所需特征")
updateConnectionState(STATE_ERROR)
@ -356,7 +358,7 @@ class BleService(private val context: Context) {
characteristic: BluetoothGattCharacteristic
) {
val characteristicUuid = characteristic.uuid.toString()
// Log.d(TAG, "收到特征变化通知: $characteristicUuid")
// Log.d(TAG, "收到特征变化通知: $characteristicUuid")
when (characteristicUuid) {
// NOTIFY_CHAR_UUID -> {
@ -369,7 +371,7 @@ class BleService(private val context: Context) {
NOTIFY_CHAR_UUID -> {
// 处理控制消息
val value = characteristic.value
// Log.i(TAG, "收到控制通知: ${value.size}")
// Log.i(TAG, "收到控制通知: ${value.size}")
// 判断是否收到音频数据
if (value.isNotEmpty() && (value[0].toInt() and 0xFF == 0x4b || value[0].toInt() and 0xFF == 0x48)) {

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

@ -19,359 +19,408 @@ import io.flutter.plugin.common.MethodChannel.Result
import java.io.File
/** JlOpusPlugin */
class JlOpusPlugin: FlutterPlugin, MethodCallHandler {
private lateinit var channel: MethodChannel
private lateinit var eventChannel: EventChannel
private lateinit var context: Context
private var opusManager: OpusManager? = null
private var streamSink: EventChannel.EventSink? = null
// 修正1:正确的主构造函数写法
constructor() {
class JlOpusPlugin : FlutterPlugin, MethodCallHandler {
private lateinit var channel: MethodChannel
private lateinit var eventChannel: EventChannel
private lateinit var context: Context
private var opusManager: OpusManager? = null
private var streamSink: EventChannel.EventSink? = null
// 修正1:正确的主构造函数写法
constructor() {
opusManager = OpusManager()
Log.d(TAG, "OpusManager初始化成功")
}
// 添加主线程Handler用于确保在主线程回调
private val mainHandler = Handler(Looper.getMainLooper())
interface Callback1 {
// 添加主线程Handler用于确保在主线程回调
private val mainHandler = Handler(Looper.getMainLooper())
fun onAudioDataReceived1(data: ByteArray)
interface CallbackOpus {
fun onAudioDataReceivedOpus(data: ByteArray)
}
companion object {
private const val TAG = "JlOpusPlugin"
private const val CHANNEL_NAME = "com.yunqiinnovation.jl_opus"
private const val EVENT_CHANNEL_NAME = "com.yunqiinnovation.jl_opus/events"
companion object {
private const val TAG = "JlOpusPlugin"
private const val CHANNEL_NAME = "com.yunqiinnovation.jl_opus"
private const val EVENT_CHANNEL_NAME = "com.yunqiinnovation.jl_opus/events"
}
}
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
context = flutterPluginBinding.applicationContext
channel = MethodChannel(flutterPluginBinding.binaryMessenger, CHANNEL_NAME)
channel.setMethodCallHandler(this)
context = flutterPluginBinding.applicationContext
channel = MethodChannel(flutterPluginBinding.binaryMessenger, CHANNEL_NAME)
channel.setMethodCallHandler(this)
eventChannel = EventChannel(flutterPluginBinding.binaryMessenger, EVENT_CHANNEL_NAME)
eventChannel = EventChannel(flutterPluginBinding.binaryMessenger, EVENT_CHANNEL_NAME)
eventChannel.setStreamHandler(object : EventChannel.StreamHandler {
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
streamSink = events
}
eventChannel.setStreamHandler(object : EventChannel.StreamHandler {
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
streamSink = events
}
override fun onCancel(arguments: Any?) {
streamSink = null
}
})
override fun onCancel(arguments: Any?) {
streamSink = null
}
})
// 初始化OpusManager
try {
opusManager = OpusManager()
Log.d(TAG, "OpusManager初始化成功")
} catch (e: OpusException) {
Log.e(TAG, "OpusManager初始化失败: ${e.message}", e)
}
}
// 在主线程上安全发送事件
private fun sendEventOnMainThread(event: Map<String, Any?>) {
if (streamSink == null) return
if (Looper.myLooper() == Looper.getMainLooper()) {
// 如果已经在主线程,直接发送
streamSink?.success(event)
} else {
// 否则切换到主线程
mainHandler.post {
streamSink?.success(event)
}
}
}
// 在主线程上安全返回结果
private fun sendResultOnMainThread(result: Result, value: Any?) {
if (Looper.myLooper() == Looper.getMainLooper()) {
// 如果已经在主线程,直接返回
result.success(value)
} else {
// 否则切换到主线程
mainHandler.post {
result.success(value)
}
}
}
// 在主线程上安全返回错误
private fun sendErrorOnMainThread(result: Result, errorCode: String, errorMessage: String, errorDetails: Any?) {
if (Looper.myLooper() == Looper.getMainLooper()) {
// 如果已经在主线程,直接返回
result.error(errorCode, errorMessage, errorDetails)
} else {
// 否则切换到主线程
mainHandler.post {
result.error(errorCode, errorMessage, errorDetails)
}
// 初始化OpusManager
try {
opusManager = OpusManager()
Log.d(TAG, "OpusManager初始化成功")
} catch (e: OpusException) {
Log.e(TAG, "OpusManager初始化失败: ${e.message}", e)
}
}
}
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
if (opusManager == null) {
result.error("OPUS_MANAGER_NULL", "OpusManager未初始化", null)
return
// 在主线程上安全发送事件
private fun sendEventOnMainThread(event: Map<String, Any?>) {
if (streamSink == null) return
if (Looper.myLooper() == Looper.getMainLooper()) {
// 如果已经在主线程,直接发送
streamSink?.success(event)
} else {
// 否则切换到主线程
mainHandler.post {
streamSink?.success(event)
}
}
}
when (call.method) {
"initOpusDecoder" -> {
result.success(opusManager != null)
}
"decodeOpusFile" -> {
val inPath = call.argument<String>("inPath")
val outPath = call.argument<String>("outPath")
val hasHeader = call.argument<Boolean>("hasHeader") ?: false
val channel = call.argument<Int>("channel") ?: 1
val sampleRate = call.argument<Int>("sampleRate") ?: 16000
val packetSize = call.argument<Int>("packetSize") ?: 40
if (inPath == null || outPath == null) {
result.error("INVALID_ARGS", "输入或输出路径不能为空", null)
return
// 在主线程上安全返回结果
private fun sendResultOnMainThread(result: Result, value: Any?) {
if (Looper.myLooper() == Looper.getMainLooper()) {
// 如果已经在主线程,直接返回
result.success(value)
} else {
// 否则切换到主线程
mainHandler.post {
result.success(value)
}
}
}
// 确保输入文件存在
val inFile = File(inPath)
if (!inFile.exists()) {
result.error("FILE_NOT_FOUND", "输入文件不存在: $inPath", null)
return
// 在主线程上安全返回错误
private fun sendErrorOnMainThread(
result: Result,
errorCode: String,
errorMessage: String,
errorDetails: Any?
) {
if (Looper.myLooper() == Looper.getMainLooper()) {
// 如果已经在主线程,直接返回
result.error(errorCode, errorMessage, errorDetails)
} else {
// 否则切换到主线程
mainHandler.post {
result.error(errorCode, errorMessage, errorDetails)
}
}
}
// 创建解码参数
val option = OpusOption()
.setHasHead(hasHeader)
.setChannel(channel)
.setSampleRate(sampleRate)
.setPacketSize(packetSize)
Log.d(TAG, "开始解码文件, 参数: $option, 输入: $inPath, 输出: $outPath")
opusManager?.decodeFile(inPath, outPath, option, object : OnStateCallback {
override fun onStart() {
Log.d(TAG, "文件解码开始")
sendEventOnMainThread(mapOf(
"event" to "onStart",
"type" to "file"
))
}
override fun onComplete(outFilePath: String?) {
Log.d(TAG, "文件解码完成: $outFilePath")
sendEventOnMainThread(mapOf(
"event" to "onComplete",
"type" to "file",
"filePath" to outFilePath
))
sendResultOnMainThread(result, outFilePath)
}
override fun onError(code: Int, message: String?) {
Log.e(TAG, "文件解码错误: [$code] $message")
sendEventOnMainThread(mapOf(
"event" to "onError",
"type" to "file",
"code" to code,
"message" to message
))
sendErrorOnMainThread(result, "DECODE_ERROR", "解码出错: [$code] $message", null)
}
})
}
"startDecodeStream" -> {
val hasHeader = call.argument<Boolean>("hasHeader") ?: false
val channel = call.argument<Int>("channel") ?: 1
val sampleRate = call.argument<Int>("sampleRate") ?: 16000
val packetSize = call.argument<Int>("packetSize") ?: 40
// 如果已经在解码流,先停止
if (opusManager?.isDecodeStream == true) {
opusManager?.stopDecodeStream()
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
if (opusManager == null) {
result.error("OPUS_MANAGER_NULL", "OpusManager未初始化", null)
return
}
// 创建解码参数
val option = OpusOption()
.setHasHead(hasHeader)
.setChannel(channel)
.setSampleRate(sampleRate)
.setPacketSize(packetSize)
when (call.method) {
"initOpusDecoder" -> {
result.success(opusManager != null)
}
Log.d(TAG, "开始解码数据流, 参数: $option")
"decodeOpusFile" -> {
val inPath = call.argument<String>("inPath")
val outPath = call.argument<String>("outPath")
val hasHeader = call.argument<Boolean>("hasHeader") ?: false
val channel = call.argument<Int>("channel") ?: 1
val sampleRate = call.argument<Int>("sampleRate") ?: 16000
val packetSize = call.argument<Int>("packetSize") ?: 40
if (inPath == null || outPath == null) {
result.error("INVALID_ARGS", "输入或输出路径不能为空", null)
return
}
// 确保输入文件存在
val inFile = File(inPath)
if (!inFile.exists()) {
result.error("FILE_NOT_FOUND", "输入文件不存在: $inPath", null)
return
}
// 创建解码参数
val option = OpusOption()
.setHasHead(hasHeader)
.setChannel(channel)
.setSampleRate(sampleRate)
.setPacketSize(packetSize)
Log.d(TAG, "开始解码文件, 参数: $option, 输入: $inPath, 输出: $outPath")
opusManager?.decodeFile(inPath, outPath, option, object : OnStateCallback {
override fun onStart() {
Log.d(TAG, "文件解码开始")
sendEventOnMainThread(
mapOf(
"event" to "onStart",
"type" to "file"
)
)
}
override fun onComplete(outFilePath: String?) {
Log.d(TAG, "文件解码完成: $outFilePath")
sendEventOnMainThread(
mapOf(
"event" to "onComplete",
"type" to "file",
"filePath" to outFilePath
)
)
sendResultOnMainThread(result, outFilePath)
}
override fun onError(code: Int, message: String?) {
Log.e(TAG, "文件解码错误: [$code] $message")
sendEventOnMainThread(
mapOf(
"event" to "onError",
"type" to "file",
"code" to code,
"message" to message
)
)
sendErrorOnMainThread(
result,
"DECODE_ERROR",
"解码出错: [$code] $message",
null
)
}
})
}
opusManager?.startDecodeStream(option, object : OnDecodeStreamCallback {
override fun onDecodeStream(data: ByteArray?) {
if (data != null) {
sendEventOnMainThread(mapOf(
"event" to "onDecodeStream",
"data" to data
))
"startDecodeStream" -> {
val hasHeader = call.argument<Boolean>("hasHeader") ?: false
val channel = call.argument<Int>("channel") ?: 1
val sampleRate = call.argument<Int>("sampleRate") ?: 16000
val packetSize = call.argument<Int>("packetSize") ?: 40
// 如果已经在解码流,先停止
if (opusManager?.isDecodeStream == true) {
opusManager?.stopDecodeStream()
}
// 创建解码参数
val option = OpusOption()
.setHasHead(hasHeader)
.setChannel(channel)
.setSampleRate(sampleRate)
.setPacketSize(packetSize)
Log.d(TAG, "开始解码数据流, 参数: $option")
opusManager?.startDecodeStream(option, object : OnDecodeStreamCallback {
override fun onDecodeStream(data: ByteArray?) {
if (data != null) {
sendEventOnMainThread(
mapOf(
"event" to "onDecodeStream",
"data" to data
)
)
}
}
override fun onStart() {
Log.d(TAG, "数据流解码开始")
sendEventOnMainThread(
mapOf(
"event" to "onStart",
"type" to "stream"
)
)
sendResultOnMainThread(result, true)
}
override fun onComplete(outPath: String?) {
Log.d(TAG, "数据流解码完成: $outPath")
sendEventOnMainThread(
mapOf(
"event" to "onComplete",
"type" to "stream"
)
)
}
override fun onError(code: Int, message: String?) {
Log.e(TAG, "数据流解码错误: [$code] $message")
sendEventOnMainThread(
mapOf(
"event" to "onError",
"type" to "stream",
"code" to code,
"message" to message
)
)
sendErrorOnMainThread(
result,
"DECODE_STREAM_ERROR",
"数据流解码出错: [$code] $message",
null
)
}
})
}
}
override fun onStart() {
Log.d(TAG, "数据流解码开始")
sendEventOnMainThread(mapOf(
"event" to "onStart",
"type" to "stream"
))
sendResultOnMainThread(result, true)
}
override fun onComplete(outPath: String?) {
Log.d(TAG, "数据流解码完成: $outPath")
sendEventOnMainThread(mapOf(
"event" to "onComplete",
"type" to "stream"
))
}
override fun onError(code: Int, message: String?) {
Log.e(TAG, "数据流解码错误: [$code] $message")
sendEventOnMainThread(mapOf(
"event" to "onError",
"type" to "stream",
"code" to code,
"message" to message
))
sendErrorOnMainThread(result, "DECODE_STREAM_ERROR", "数据流解码出错: [$code] $message", null)
}
})
}
"stopDecodeStream" -> {
if (opusManager?.isDecodeStream == true) {
opusManager?.stopDecodeStream()
result.success(true)
} else {
result.success(false)
}
}
"writeAudioStream" -> {
val data = call.argument<ByteArray>("data")
if (data == null) {
result.error("INVALID_ARGS", "音频数据不能为空", null)
return
}
if (opusManager?.isDecodeStream == true) {
opusManager?.writeAudioStream(data)
result.success(true)
} else {
result.error("NOT_DECODING", "当前没有处于解码状态", null)
}
}
"isDecoding" -> {
result.success(opusManager?.isDecodeStream == true)
}
"dispose" -> {
opusManager?.let {
if (it.isDecodeStream) {
it.stopDecodeStream()
}
it.release()
"stopDecodeStream" -> {
if (opusManager?.isDecodeStream == true) {
opusManager?.stopDecodeStream()
result.success(true)
} else {
result.success(false)
}
}
"writeAudioStream" -> {
val data = call.argument<ByteArray>("data")
if (data == null) {
result.error("INVALID_ARGS", "音频数据不能为空", null)
return
}
if (opusManager?.isDecodeStream == true) {
opusManager?.writeAudioStream(data)
result.success(true)
} else {
result.error("NOT_DECODING", "当前没有处于解码状态", null)
}
}
"isDecoding" -> {
result.success(opusManager?.isDecodeStream == true)
}
"dispose" -> {
opusManager?.let {
if (it.isDecodeStream) {
it.stopDecodeStream()
}
it.release()
}
opusManager = null
result.success(true)
}
else -> {
result.notImplemented()
}
}
opusManager = null
result.success(true)
}
else -> {
result.notImplemented()
}
}
}
fun handleDecodeRequest(opusdata: ByteArray) {
fun handleDecodeRequest(opusdata: ByteArray) {
// Log.d(TAG, "handleDecodeRequest${opusdata}")
if (opusdata == null) {
return
return
}
if (opusManager?.isDecodeStream == true) {
opusManager?.writeAudioStream(opusdata)
if (opusManager?.isDecodeStream == true) {
opusManager?.writeAudioStream(opusdata)
} else {
}
}
fun startOpusDecodeStream(
hasHeader: Boolean,
channel: Int,
sampleRate: Int,
packetSize: Int,
callback: Callback1
) {
Log.d(TAG, "开始解码数据流,startOpusDecodeStream: $opusManager")
// 如果已经在解码流,先停止
}
fun startOpusDecodeStream(
hasHeader: Boolean,
channel: Int,
sampleRate: Int,
packetSize: Int,
callback: CallbackOpus
) {
Log.d(TAG, "开始解码数据流,startOpusDecodeStream: $opusManager")
// 如果已经在解码流,先停止
if (opusManager?.isDecodeStream == true) {
opusManager?.stopDecodeStream()
opusManager?.stopDecodeStream()
}
// 创建解码参数
val option = OpusOption()
.setHasHead(hasHeader)
.setChannel(channel)
.setSampleRate(sampleRate)
.setPacketSize(packetSize)
.setHasHead(hasHeader)
.setChannel(channel)
.setSampleRate(sampleRate)
.setPacketSize(packetSize)
Log.d(TAG, "开始解码数据流, 参数: $option")
opusManager?.startDecodeStream(option, object : OnDecodeStreamCallback {
override fun onDecodeStream(data: ByteArray?) {
if (data != null) {
//Log.d(TAG, "onDecodeStream${data}")
callback.onAudioDataReceived1(data)
sendEventOnMainThread(mapOf(
"event" to "onDecodeStream",
"data" to data
))
override fun onDecodeStream(data: ByteArray?) {
if (data != null) {
//Log.d(TAG, "onDecodeStream${data}")
callback.onAudioDataReceivedOpus(data)
sendEventOnMainThread(
mapOf(
"event" to "onDecodeStream",
"data" to data
)
)
}
}
override fun onStart() {
Log.d(TAG, "数据流解码开始")
sendEventOnMainThread(
mapOf(
"event" to "onStart",
"type" to "stream"
)
)
}
override fun onComplete(outPath: String?) {
Log.d(TAG, "数据流解码完成: $outPath")
sendEventOnMainThread(
mapOf(
"event" to "onComplete",
"type" to "stream"
)
)
}
override fun onError(code: Int, message: String?) {
Log.e(TAG, "数据流解码错误: [$code] $message")
sendEventOnMainThread(
mapOf(
"event" to "onError",
"type" to "stream",
"code" to code,
"message" to message
)
)
}
}
override fun onStart() {
Log.d(TAG, "数据流解码开始")
sendEventOnMainThread(mapOf(
"event" to "onStart",
"type" to "stream"
))
}
override fun onComplete(outPath: String?) {
Log.d(TAG, "数据流解码完成: $outPath")
sendEventOnMainThread(mapOf(
"event" to "onComplete",
"type" to "stream"
))
}
override fun onError(code: Int, message: String?) {
Log.e(TAG, "数据流解码错误: [$code] $message")
sendEventOnMainThread(mapOf(
"event" to "onError",
"type" to "stream",
"code" to code,
"message" to message
))
}
})
}
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
eventChannel.setStreamHandler(null)
opusManager?.let {
if (it.isDecodeStream) {
it.stopDecodeStream()
}
it.release()
}
opusManager = null
}
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
eventChannel.setStreamHandler(null)
opusManager?.let {
if (it.isDecodeStream) {
it.stopDecodeStream()
}
it.release()
}
opusManager = null
}
}
Loading…
Cancel
Save