This commit is contained in:
终究是寂寞
2026-03-08 19:22:22 +08:00
parent 416d4eeb3a
commit 662755e97a
274 changed files with 895 additions and 1310 deletions
+1
View File
@@ -0,0 +1 @@
/build
+44
View File
@@ -0,0 +1,44 @@
plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
}
android {
namespace 'com.example.ty_recording'
compileSdk 35
defaultConfig {
minSdk 22
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = '11'
}
}
dependencies {
compileOnly fileTree(include: ['*.aar'], dir: '../simpleDemo/libs')
compileOnly 'com.alibaba:fastjson:1.1.46.android'
compileOnly 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.10'
compileOnly 'androidx.core:core-ktx:1.6.0'
compileOnly 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.0'
compileOnly 'org.jetbrains.kotlin:kotlin-reflect:1.6.0'
compileOnly 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.8'
compileOnly 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.8'
}
+21
View File
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
@@ -0,0 +1,24 @@
package com.example.ty_recording
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.example.ty_recording.test", appContext.packageName)
}
}
@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>
@@ -0,0 +1,296 @@
@file:Suppress("UNCHECKED_CAST", "USELESS_CAST", "INAPPLICABLE_JVM_NAME", "UNUSED_ANONYMOUS_PARAMETER", "NAME_SHADOWING", "UNNECESSARY_NOT_NULL_ASSERTION")
package uts.sdk.modules.tyRecording
import android.media.AudioFormat
import android.media.AudioRecord
import android.media.MediaRecorder
import io.dcloud.uniapp.*
import io.dcloud.uniapp.extapi.*
import io.dcloud.uts.*
import io.dcloud.uts.Map
import io.dcloud.uts.Set
import java.util.concurrent.atomic.AtomicBoolean
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
open class AudioStartRes (
@JsonNotNull
open var recordId: String,
open var sampleRate: Number? = null,
@JsonNotNull
open var startTime: Number,
) : UTSObject()
open class AudioFileRes (
@JsonNotNull
open var filePath: String,
@JsonNotNull
open var fileSize: Number,
@JsonNotNull
open var duration: Number,
@JsonNotNull
open var format: String,
@JsonNotNull
open var endTime: Number,
) : UTSObject()
typealias AudioErrorCode = Number
interface AudioErrorRes : IUniError {
override var errCode: Number
}
open class StartAudioRecordOptions (
open var autoStopDuration: Number? = null,
open var onFrame: ((frameData: Any) -> Unit)? = null,
open var onVolume: ((volume: Number) -> Unit)? = null,
open var onStart: ((res: AudioStartRes) -> Unit)? = null,
open var onError: ((res: AudioErrorRes) -> Unit)? = null,
open var onStop: (() -> Unit)? = null,
open var onFile: ((res: AudioFileRes) -> Unit)? = null,
) : UTSObject()
val UniErrorSubject = "ty-recording"
val AudioErrors = Map<AudioErrorCode, String>(utsArrayOf(
utsArrayOf(
9010001,
"麦克风权限未授予,请前往设置开启1"
),
utsArrayOf(
9010002,
"麦克风权限被永久拒绝,请前往设置开启"
),
utsArrayOf(
9010003,
"获取麦克风权限状态失败"
),
utsArrayOf(
9010010,
"音频录制实例初始化失败"
),
utsArrayOf(
9010011,
"音频录制实例未初始化,无法执行操作"
),
utsArrayOf(
9010012,
"系统资源不足,无法创建音频录制实例"
),
utsArrayOf(
9010020,
"音频采集读取数据失败"
),
utsArrayOf(
9010021,
"音频采集被中断(麦克风被其他应用抢占)"
),
utsArrayOf(
9010022,
"音频采集缓冲区无有效数据"
),
utsArrayOf(
9010023,
"启动音频采集失败"
),
utsArrayOf(
9010030,
"采集线程创建失败"
),
utsArrayOf(
9010031,
"采集线程启动失败"
),
utsArrayOf(
9010032,
"采集线程中断失败"
),
utsArrayOf(
9010033,
"采集线程运行崩溃"
),
utsArrayOf(
9010040,
"停止音频采集失败"
),
utsArrayOf(
9010041,
"音频录制资源释放失败"
),
utsArrayOf(
9010042,
"音频资源重复释放"
),
utsArrayOf(
9010043,
"音频资源泄漏(未正常释放)"
),
utsArrayOf(
9010050,
"当前设备不支持该采样率"
),
utsArrayOf(
9010051,
"声道配置非法或不被支持"
),
utsArrayOf(
9010052,
"音频格式非法或不被支持"
),
utsArrayOf(
9010053,
"采集缓冲区大小低于系统最小值"
),
utsArrayOf(
9010054,
"采集缓冲区大小配置非法"
),
utsArrayOf(
9010060,
"麦克风被其他应用占用"
),
utsArrayOf(
9010061,
"音频硬件故障"
),
utsArrayOf(
9010062,
"系统资源不足,无法创建音频实例"
),
utsArrayOf(
9010063,
"系统音频服务异常或未启动"
),
utsArrayOf(
9010070,
"重复启动音频录制"
),
utsArrayOf(
9010071,
"未在录制状态,无法执行停止/暂停操作"
),
utsArrayOf(
9010072,
"音频数据缓冲区拷贝失败"
),
utsArrayOf(
9010080,
"预留错误码"
)
))
open class StartAudioRecordFailImpl : UniError, AudioErrorRes {
constructor(errCode: Number) : super() {
this.errSubject = UniErrorSubject
this.errCode = errCode
this.errMsg = AudioErrors.get(errCode as AudioErrorCode) ?: ""
}
}
val isRecording = AtomicBoolean(false)
val sampleRate: Int = 16000
val channelConfig = AudioFormat.CHANNEL_IN_MONO
val audioFormat = AudioFormat.ENCODING_PCM_16BIT
val minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat)
val bufferSize = minBufferSize * 2
var audioRecord: AudioRecord? = null
var recordThread: Thread? = null
var recordStartTime: Number? = null
fun startAudioRecord(options: StartAudioRecordOptions): Unit {
if (isRecording.get()) {
return
}
val _audioRecord = AudioRecord(MediaRecorder.AudioSource.MIC, sampleRate, channelConfig, audioFormat, bufferSize)
if (_audioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
val err = StartAudioRecordFailImpl(9010001)
options.onError?.invoke(err)
return
}
audioRecord = _audioRecord
isRecording.set(true)
val _recordThread = Thread(fun(){
_audioRecord.startRecording()
val recordId = "" + Date().getTime() + "_" + Math.floor(Math.random() * 10000)
recordStartTime = Date().getTime()
options.onStart?.invoke(AudioStartRes(startTime = recordStartTime as Number, sampleRate = sampleRate as Number, recordId = recordId))
console.log("AudioRecord已启动采集")
val buffer = ByteArray(bufferSize)
while(isRecording.get()){
val readSize = _audioRecord.read(buffer, 0, bufferSize)
if (readSize > 0) {
val data = ByteArray(readSize)
java.lang.System.arraycopy(buffer, 0, data, 0, readSize)
options.onFrame?.invoke(data)
}
}
if (_audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
console.log("停止采集xxxxxxxxx")
_audioRecord.stop()
options.onStop?.invoke()
}
}
)
_recordThread.start()
recordThread = _recordThread
}
fun stopAudioRecord(): Unit {
console.log("停止麦克风采集", isRecording.get())
if (!isRecording.get()) {
return
}
isRecording.set(false)
val _audioRecord = audioRecord
if (_audioRecord != null) {
try {
if (_audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
_audioRecord.stop()
}
_audioRecord.release()
}
catch (e: Throwable) {}
}
audioRecord = null
console.log("等待线程结束")
val _recordThread = recordThread
if (_recordThread != null) {
try {
_recordThread.join(1000)
console.log("后台线程已停止")
}
catch (e: Throwable) {
console.warn("停止后台线程时出现异常:", e)
}
finally{
recordThread = null
}
}
}
fun preRequestRecordPermission(): Unit {}
open class StartAudioRecordOptionsJSONObject : UTSJSONObject() {
open var autoStopDuration: Number? = null
open var onFrame: UTSCallback? = null
open var onVolume: UTSCallback? = null
open var onStart: UTSCallback? = null
open var onError: UTSCallback? = null
open var onStop: UTSCallback? = null
open var onFile: UTSCallback? = null
}
fun startAudioRecordByJs(options: StartAudioRecordOptionsJSONObject): Unit {
return startAudioRecord(StartAudioRecordOptions(autoStopDuration = options.autoStopDuration, onFrame = fun(frameData: Any): Unit {
options.onFrame?.invoke(frameData)
}
, onVolume = fun(volume: Number): Unit {
options.onVolume?.invoke(volume)
}
, onStart = fun(res: AudioStartRes): Unit {
options.onStart?.invoke(res)
}
, onError = fun(res: AudioErrorRes): Unit {
options.onError?.invoke(res)
}
, onStop = fun(): Unit {
options.onStop?.invoke()
}
, onFile = fun(res: AudioFileRes): Unit {
options.onFile?.invoke(res)
}
))
}
fun stopAudioRecordByJs(): Unit {
return stopAudioRecord()
}
fun preRequestRecordPermissionByJs(): Unit {
return preRequestRecordPermission()
}
@@ -0,0 +1,17 @@
package com.example.ty_recording
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}