class FyUniClientBuilder : UniModule() {
    private var isRecording = false
    private var audioRecord: AudioRecord? = null
    private var recordBufferSize = 0

    /**麦克风的实时数据*/
    private val recordingLiveType = 1

    /**录音结束*/
    private val recordingCompleteType = 2
    private var mRecordingCallback: UniJSCallback? = null

    /**
     * 麦克风数据监听
     * */
    @UniJSMethod(uiThread = false)
    fun setRecordingCallback(callback: UniJSCallback) {
        mRecordingCallback = callback
    }

    /**开始录音,录音结束后,自动发送数据给服务器*/
    @UniJSMethod(uiThread = true)
    fun startRecording() {
        Log.i(TAG, "startRecording: 尝试开始录音")
        try {
            if (!canRecording()) {
                requestPermission()
                mJSCallback?.invoke(JSONObject().apply {
                    put("error", "没有录音权限")
                })
                return
            }
            if (isRecording) {
                Log.w(TAG, "startRecording: 已经开始录音")
                return
            }
            if (audioRecord == null) {
                recordBufferSize = AudioRecord.getMinBufferSize(
                    mSampleRate, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT
                )
                audioRecord = AudioRecord(
                    MediaRecorder.AudioSource.MIC,
                    mSampleRate,
                    AudioFormat.CHANNEL_IN_MONO,
                    AudioFormat.ENCODING_PCM_16BIT,
                    recordBufferSize
                )
            }
            audioRecord?.startRecording() ?: error("audioRecord is null")
            isRecording = true
            Log.i(TAG, "startRecording: 开始录音")
            createRecordFile()

        } catch (e: Exception) {
            if (e is InvocationTargetException) {
                Log.e(TAG, "startRecording反射异常: ${e.targetException}")
                e.printStackTrace()
                return
            }
            Log.e(TAG, "startRecording异常: ", e)
        }
        Log.i(TAG, "startRecording: 录音线程开启")
        Thread {
            try {
                val inFile = recordFile
                Log.i(TAG, "startRecording: 开始读取麦克风数据in线程")
                val buffer = ByteArray(recordBufferSize)
                while (isRecording) {
                    val read = audioRecord?.read(buffer, 0, recordBufferSize)
                        ?: AudioRecord.ERROR_INVALID_OPERATION
                    if (read != AudioRecord.ERROR_INVALID_OPERATION) {
//                    Log.i(
//                        TAG,
//                        "startRecording: 读取到了麦克风数据: $read, ${buffer.size},file:${inFile?.absolutePath}"
//                    )
                        mRecordingCallback?.invokeAndKeepAlive(JSONObject().apply {
                            put("type", recordingLiveType)
                            put("data", buffer)
                        })
                        inFile?.appendBytes(buffer)
                    }
                }
                val retVal = JSONObject()
                retVal["type"] = recordingCompleteType
                retVal["recordFilePath"] = recordFile?.absolutePath.toString()
                retVal["recordFileBytes"] = recordFile?.readBytes()
                retVal["recordFileLength"] = recordFile?.length()
                mRecordingCallback?.invokeAndKeepAlive(retVal)
                Log.i(TAG, "startRecording: 读取麦克风数据in线程结束")
            } catch (e: Exception) {
                Log.e(TAG, "startRecording: ", e)
            }
        }.start()
    }

    private fun createRecordFile() {
        val parent = context.getExternalFilesDir(null)?.absolutePath ?: error("no path")
        val recordDirPath = parent + File.separator + "record"
        Log.i(TAG, "startRecording: recordDirPath: $recordDirPath")
        val recordDirFile = File(recordDirPath)
        if (recordDirFile.exists().not()) {
            recordDirFile.mkdirs()
        }
        val fileName = "record_${timestampToDateString(System.currentTimeMillis())}.m4a"
        Log.i(TAG, "startRecording: fileName: $fileName")
        recordFile = File(recordDirPath + File.separator + fileName)
        try {
            if (recordFile!!.exists()) {
                recordFile!!.delete()
                Log.i(TAG, "startRecording: 删除文件成功,${recordFile!!.absolutePath}")
            } else {
                recordFile!!.createNewFile()
                Log.i(TAG, "startRecording: 创建文件成功")
            }
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }

    @UniJSMethod(uiThread = false)
    fun stopRecording() {
        try {
            Log.i(TAG, "stopRecording: 停止录音")
            isRecording = false
            audioRecord?.stop()
            audioRecord?.release()
            audioRecord = null
            try {
                Thread { sendFyFileSize(recordFile!!) }.start()
            } catch (e: Exception) {
                Log.e(TAG, "sendFyFileSize: ", e)
            }
        } catch (e: Exception) {
            if (e is InvocationTargetException) {
                Log.e(TAG, "stopRecording反射异常: $e")
                e.printStackTrace()
                return
            }
            Log.e(TAG, "stopRecording异常: ", e)
        }
    }
}

在js层引入,注意 可能会提示插件存在但未打包,引用失败等云云。在确认插件的package.json class精确到Class类名,以及里面的id name需要和文件夹名一致。否则都会出现失败,如果都没问题还显示这个,上传自定义基座再多试几次。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐