x
This commit is contained in:
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -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
@@ -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
|
||||
+24
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user