@siteed/expo-audio-stream 2.0.1 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -27
- package/build/index.d.ts +11 -12
- package/build/index.js +44 -10
- package/package.json +49 -110
- package/src/index.ts +18 -33
- package/CHANGELOG.md +0 -195
- package/android/build.gradle +0 -105
- package/android/src/main/AndroidManifest.xml +0 -27
- package/android/src/main/java/net/siteed/audiostream/AudioAnalysisData.kt +0 -166
- package/android/src/main/java/net/siteed/audiostream/AudioDataEncoder.kt +0 -9
- package/android/src/main/java/net/siteed/audiostream/AudioFileHandler.kt +0 -131
- package/android/src/main/java/net/siteed/audiostream/AudioFormatUtils.kt +0 -103
- package/android/src/main/java/net/siteed/audiostream/AudioNotificationsManager.kt +0 -435
- package/android/src/main/java/net/siteed/audiostream/AudioProcessor.kt +0 -1936
- package/android/src/main/java/net/siteed/audiostream/AudioRecorderManager.kt +0 -1437
- package/android/src/main/java/net/siteed/audiostream/AudioRecordingService.kt +0 -138
- package/android/src/main/java/net/siteed/audiostream/Constants.kt +0 -20
- package/android/src/main/java/net/siteed/audiostream/EventSender.kt +0 -7
- package/android/src/main/java/net/siteed/audiostream/ExpoAudioStreamModule.kt +0 -509
- package/android/src/main/java/net/siteed/audiostream/FFT.kt +0 -99
- package/android/src/main/java/net/siteed/audiostream/Features.kt +0 -98
- package/android/src/main/java/net/siteed/audiostream/NotificationConfig.kt +0 -70
- package/android/src/main/java/net/siteed/audiostream/PermissionUtils.kt +0 -59
- package/android/src/main/java/net/siteed/audiostream/RecordingActionReceiver.kt +0 -59
- package/android/src/main/java/net/siteed/audiostream/RecordingConfig.kt +0 -205
- package/android/src/main/java/net/siteed/audiostream/WaveformConfig.kt +0 -19
- package/android/src/main/java/net/siteed/audiostream/WaveformRenderer.kt +0 -159
- package/android/src/main/res/drawable/ic_default_action_icon.xml +0 -16
- package/android/src/main/res/drawable/ic_microphone.xml +0 -13
- package/android/src/main/res/drawable/ic_pause.xml +0 -10
- package/android/src/main/res/drawable/ic_play.xml +0 -10
- package/android/src/main/res/drawable/ic_stop.xml +0 -10
- package/android/src/main/res/layout/notification_recording.xml +0 -37
- package/android/src/main/test/java/net/siteed/audiostream/AudioProcessorTest.kt +0 -56
- package/app.plugin.js +0 -1
- package/build/AudioAnalysis/AudioAnalysis.types.d.ts +0 -144
- package/build/AudioAnalysis/AudioAnalysis.types.d.ts.map +0 -1
- package/build/AudioAnalysis/AudioAnalysis.types.js +0 -3
- package/build/AudioAnalysis/AudioAnalysis.types.js.map +0 -1
- package/build/AudioAnalysis/extractAudioAnalysis.d.ts +0 -78
- package/build/AudioAnalysis/extractAudioAnalysis.d.ts.map +0 -1
- package/build/AudioAnalysis/extractAudioAnalysis.js +0 -229
- package/build/AudioAnalysis/extractAudioAnalysis.js.map +0 -1
- package/build/AudioAnalysis/extractWaveform.d.ts +0 -8
- package/build/AudioAnalysis/extractWaveform.d.ts.map +0 -1
- package/build/AudioAnalysis/extractWaveform.js +0 -11
- package/build/AudioAnalysis/extractWaveform.js.map +0 -1
- package/build/AudioRecorder.provider.d.ts +0 -11
- package/build/AudioRecorder.provider.d.ts.map +0 -1
- package/build/AudioRecorder.provider.js +0 -37
- package/build/AudioRecorder.provider.js.map +0 -1
- package/build/ExpoAudioStream.native.d.ts +0 -3
- package/build/ExpoAudioStream.native.d.ts.map +0 -1
- package/build/ExpoAudioStream.native.js +0 -6
- package/build/ExpoAudioStream.native.js.map +0 -1
- package/build/ExpoAudioStream.types.d.ts +0 -206
- package/build/ExpoAudioStream.types.d.ts.map +0 -1
- package/build/ExpoAudioStream.types.js +0 -2
- package/build/ExpoAudioStream.types.js.map +0 -1
- package/build/ExpoAudioStream.web.d.ts +0 -59
- package/build/ExpoAudioStream.web.d.ts.map +0 -1
- package/build/ExpoAudioStream.web.js +0 -285
- package/build/ExpoAudioStream.web.js.map +0 -1
- package/build/ExpoAudioStreamModule.d.ts +0 -3
- package/build/ExpoAudioStreamModule.d.ts.map +0 -1
- package/build/ExpoAudioStreamModule.js +0 -239
- package/build/ExpoAudioStreamModule.js.map +0 -1
- package/build/WebRecorder.web.d.ts +0 -119
- package/build/WebRecorder.web.d.ts.map +0 -1
- package/build/WebRecorder.web.js +0 -436
- package/build/WebRecorder.web.js.map +0 -1
- package/build/constants.d.ts +0 -11
- package/build/constants.d.ts.map +0 -1
- package/build/constants.js +0 -14
- package/build/constants.js.map +0 -1
- package/build/events.d.ts +0 -26
- package/build/events.d.ts.map +0 -1
- package/build/events.js +0 -21
- package/build/events.js.map +0 -1
- package/build/index.d.ts.map +0 -1
- package/build/index.js.map +0 -1
- package/build/useAudioRecorder.d.ts +0 -21
- package/build/useAudioRecorder.d.ts.map +0 -1
- package/build/useAudioRecorder.js +0 -427
- package/build/useAudioRecorder.js.map +0 -1
- package/build/utils/BlobFix.d.ts +0 -9
- package/build/utils/BlobFix.d.ts.map +0 -1
- package/build/utils/BlobFix.js +0 -498
- package/build/utils/BlobFix.js.map +0 -1
- package/build/utils/audioProcessing.d.ts +0 -24
- package/build/utils/audioProcessing.d.ts.map +0 -1
- package/build/utils/audioProcessing.js +0 -133
- package/build/utils/audioProcessing.js.map +0 -1
- package/build/utils/concatenateBuffers.d.ts +0 -8
- package/build/utils/concatenateBuffers.d.ts.map +0 -1
- package/build/utils/concatenateBuffers.js +0 -21
- package/build/utils/concatenateBuffers.js.map +0 -1
- package/build/utils/convertPCMToFloat32.d.ts +0 -13
- package/build/utils/convertPCMToFloat32.d.ts.map +0 -1
- package/build/utils/convertPCMToFloat32.js +0 -120
- package/build/utils/convertPCMToFloat32.js.map +0 -1
- package/build/utils/encodingToBitDepth.d.ts +0 -5
- package/build/utils/encodingToBitDepth.d.ts.map +0 -1
- package/build/utils/encodingToBitDepth.js +0 -13
- package/build/utils/encodingToBitDepth.js.map +0 -1
- package/build/utils/getWavFileInfo.d.ts +0 -26
- package/build/utils/getWavFileInfo.d.ts.map +0 -1
- package/build/utils/getWavFileInfo.js +0 -92
- package/build/utils/getWavFileInfo.js.map +0 -1
- package/build/utils/writeWavHeader.d.ts +0 -49
- package/build/utils/writeWavHeader.d.ts.map +0 -1
- package/build/utils/writeWavHeader.js +0 -91
- package/build/utils/writeWavHeader.js.map +0 -1
- package/build/workers/InlineFeaturesExtractor.web.d.ts +0 -2
- package/build/workers/InlineFeaturesExtractor.web.d.ts.map +0 -1
- package/build/workers/InlineFeaturesExtractor.web.js +0 -828
- package/build/workers/InlineFeaturesExtractor.web.js.map +0 -1
- package/build/workers/inlineAudioWebWorker.web.d.ts +0 -2
- package/build/workers/inlineAudioWebWorker.web.d.ts.map +0 -1
- package/build/workers/inlineAudioWebWorker.web.js +0 -157
- package/build/workers/inlineAudioWebWorker.web.js.map +0 -1
- package/expo-module.config.json +0 -9
- package/ios/AudioAnalysisData.swift +0 -74
- package/ios/AudioNotificationManager.swift +0 -135
- package/ios/AudioProcessingHelpers.swift +0 -743
- package/ios/AudioProcessor.swift +0 -858
- package/ios/AudioStreamError.swift +0 -7
- package/ios/AudioStreamManager.swift +0 -1708
- package/ios/AudioStreamManagerDelegate.swift +0 -16
- package/ios/DataPoint.swift +0 -54
- package/ios/DecodingConfig.swift +0 -47
- package/ios/ExpoAudioStream.podspec +0 -27
- package/ios/ExpoAudioStreamModule.swift +0 -698
- package/ios/FFT.swift +0 -62
- package/ios/Features.swift +0 -95
- package/ios/Logger.swift +0 -7
- package/ios/NotificationExtension.swift +0 -15
- package/ios/RecordingResult.swift +0 -22
- package/ios/RecordingSettings.swift +0 -265
- package/ios/WaveformExtractor.swift +0 -105
- package/plugin/build/index.d.ts +0 -21
- package/plugin/build/index.js +0 -191
- package/plugin/src/index.ts +0 -278
- package/plugin/tsconfig.json +0 -10
- package/plugin/tsconfig.tsbuildinfo +0 -1
- package/src/AudioAnalysis/AudioAnalysis.types.ts +0 -165
- package/src/AudioAnalysis/extractAudioAnalysis.ts +0 -370
- package/src/AudioAnalysis/extractWaveform.ts +0 -22
- package/src/AudioRecorder.provider.tsx +0 -54
- package/src/ExpoAudioStream.native.ts +0 -6
- package/src/ExpoAudioStream.types.ts +0 -329
- package/src/ExpoAudioStream.web.ts +0 -359
- package/src/ExpoAudioStreamModule.ts +0 -286
- package/src/WebRecorder.web.ts +0 -580
- package/src/constants.ts +0 -18
- package/src/events.ts +0 -60
- package/src/useAudioRecorder.tsx +0 -620
- package/src/utils/BlobFix.ts +0 -559
- package/src/utils/audioProcessing.ts +0 -205
- package/src/utils/concatenateBuffers.ts +0 -24
- package/src/utils/convertPCMToFloat32.ts +0 -170
- package/src/utils/encodingToBitDepth.ts +0 -18
- package/src/utils/getWavFileInfo.ts +0 -132
- package/src/utils/writeWavHeader.ts +0 -114
- package/src/workers/InlineFeaturesExtractor.web.tsx +0 -827
- package/src/workers/inlineAudioWebWorker.web.tsx +0 -156
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
package net.siteed.audiostream
|
|
2
|
-
|
|
3
|
-
import android.app.Service
|
|
4
|
-
import android.content.Context
|
|
5
|
-
import android.content.Intent
|
|
6
|
-
import android.os.Build
|
|
7
|
-
import android.os.IBinder
|
|
8
|
-
import android.util.Log
|
|
9
|
-
import android.os.Handler
|
|
10
|
-
import android.os.Looper
|
|
11
|
-
import expo.modules.kotlin.Promise
|
|
12
|
-
import android.app.NotificationChannel
|
|
13
|
-
import android.app.NotificationManager
|
|
14
|
-
import android.os.Build.VERSION_CODES
|
|
15
|
-
import android.app.Notification
|
|
16
|
-
import androidx.core.app.NotificationCompat
|
|
17
|
-
|
|
18
|
-
class AudioRecordingService : Service() {
|
|
19
|
-
private val notificationManager by lazy {
|
|
20
|
-
AudioNotificationManager.getInstance(applicationContext)
|
|
21
|
-
}
|
|
22
|
-
private val mainHandler = Handler(Looper.getMainLooper())
|
|
23
|
-
private var isRunning = false
|
|
24
|
-
|
|
25
|
-
override fun onBind(intent: Intent?): IBinder? = null
|
|
26
|
-
|
|
27
|
-
override fun onCreate() {
|
|
28
|
-
super.onCreate()
|
|
29
|
-
Log.d(Constants.TAG, "AudioRecordingService onCreate")
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
|
33
|
-
Log.d(Constants.TAG, "AudioRecordingService onStartCommand")
|
|
34
|
-
|
|
35
|
-
// Check if service is being started from BOOT_COMPLETED
|
|
36
|
-
val isFromBoot = intent?.action == Intent.ACTION_BOOT_COMPLETED
|
|
37
|
-
|
|
38
|
-
if (!isRunning) {
|
|
39
|
-
isRunning = true
|
|
40
|
-
|
|
41
|
-
// Don't start foreground service if coming from BOOT_COMPLETED on Android 15+
|
|
42
|
-
if (!isFromBoot || Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
|
43
|
-
// Start as foreground service if keepAwake is true, regardless of notification settings
|
|
44
|
-
val keepAwake = AudioRecorderManager.getInstance()?.getKeepAwakeStatus() ?: true
|
|
45
|
-
if (keepAwake) {
|
|
46
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
47
|
-
// Create a minimal notification channel if needed
|
|
48
|
-
val channel = NotificationChannel(
|
|
49
|
-
"recording_service",
|
|
50
|
-
"Recording Service",
|
|
51
|
-
NotificationManager.IMPORTANCE_LOW
|
|
52
|
-
).apply {
|
|
53
|
-
setSound(null, null)
|
|
54
|
-
enableLights(false)
|
|
55
|
-
enableVibration(false)
|
|
56
|
-
}
|
|
57
|
-
val notificationManager = getSystemService(NotificationManager::class.java)
|
|
58
|
-
notificationManager.createNotificationChannel(channel)
|
|
59
|
-
|
|
60
|
-
// Create minimal silent notification
|
|
61
|
-
val notification = NotificationCompat.Builder(this, "recording_service")
|
|
62
|
-
.setContentTitle("")
|
|
63
|
-
.setContentText("")
|
|
64
|
-
.setSmallIcon(R.drawable.ic_microphone)
|
|
65
|
-
.setOngoing(true)
|
|
66
|
-
.setSound(null)
|
|
67
|
-
.setVibrate(null)
|
|
68
|
-
.setDefaults(0)
|
|
69
|
-
.setPriority(NotificationCompat.PRIORITY_LOW)
|
|
70
|
-
.build()
|
|
71
|
-
|
|
72
|
-
startForeground(1, notification)
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return START_STICKY
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
override fun onDestroy() {
|
|
82
|
-
Log.d(Constants.TAG, "AudioRecordingService onDestroy")
|
|
83
|
-
|
|
84
|
-
stopForeground(STOP_FOREGROUND_REMOVE)
|
|
85
|
-
|
|
86
|
-
isRunning = false
|
|
87
|
-
super.onDestroy()
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
override fun onTaskRemoved(rootIntent: Intent?) {
|
|
91
|
-
super.onTaskRemoved(rootIntent)
|
|
92
|
-
Log.d(Constants.TAG, "AudioRecordingService onTaskRemoved")
|
|
93
|
-
|
|
94
|
-
// Stop recording when app is killed
|
|
95
|
-
AudioRecorderManager.getInstance()?.let { manager ->
|
|
96
|
-
mainHandler.post {
|
|
97
|
-
// Create a simple promise object for internal use
|
|
98
|
-
val promise = object : Promise {
|
|
99
|
-
override fun resolve(value: Any?) {
|
|
100
|
-
Log.d(Constants.TAG, "Successfully stopped recording on task removed")
|
|
101
|
-
cleanup()
|
|
102
|
-
}
|
|
103
|
-
override fun reject(code: String, message: String?, cause: Throwable?) {
|
|
104
|
-
Log.e(Constants.TAG, "Failed to stop recording on task removed: $message")
|
|
105
|
-
cleanup()
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
try {
|
|
110
|
-
manager.stopRecording(promise)
|
|
111
|
-
} catch (e: Exception) {
|
|
112
|
-
promise.reject("ERROR", e.message, e)
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
private fun cleanup() {
|
|
120
|
-
stopForeground(STOP_FOREGROUND_REMOVE)
|
|
121
|
-
stopSelf()
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
companion object {
|
|
125
|
-
fun startService(context: Context) {
|
|
126
|
-
val serviceIntent = Intent(context, AudioRecordingService::class.java)
|
|
127
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
128
|
-
context.startForegroundService(serviceIntent)
|
|
129
|
-
} else {
|
|
130
|
-
context.startService(serviceIntent)
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
fun stopService(context: Context) {
|
|
135
|
-
context.stopService(Intent(context, AudioRecordingService::class.java))
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
package net.siteed.audiostream
|
|
2
|
-
|
|
3
|
-
object Constants {
|
|
4
|
-
const val AUDIO_EVENT_NAME = "AudioData"
|
|
5
|
-
const val AUDIO_ANALYSIS_EVENT_NAME = "AudioAnalysis"
|
|
6
|
-
const val RECORDING_INTERRUPTED_EVENT_NAME = "onRecordingInterrupted"
|
|
7
|
-
const val DEFAULT_SAMPLE_RATE = 16000 // Default sample rate for audio recording
|
|
8
|
-
const val DEFAULT_CHANNEL_CONFIG = 1 // Mono
|
|
9
|
-
const val DEFAULT_AUDIO_FORMAT = 16 // 16-bit PCM
|
|
10
|
-
const val DEFAULT_INTERVAL = 1000L
|
|
11
|
-
const val DEFAULT_INTERVAL_ANALYSIS = 500L
|
|
12
|
-
const val MIN_INTERVAL = 10L // Minimum interval in ms for emitting audio data
|
|
13
|
-
const val WAV_HEADER_SIZE = 44
|
|
14
|
-
const val RIFF_HEADER = 0x52494646 // "RIFF"
|
|
15
|
-
const val WAVE_HEADER = 0x57415645 // "WAVE"
|
|
16
|
-
const val FMT_CHUNK_ID = 0x666d7420 // "fmt "
|
|
17
|
-
const val DATA_CHUNK_ID = 0x64617461 // "data"
|
|
18
|
-
const val INFO_CHUNK_ID = 0x494E464F // "info"
|
|
19
|
-
const val TAG = "AudioRecorderModule"
|
|
20
|
-
}
|
|
@@ -1,509 +0,0 @@
|
|
|
1
|
-
// packages/expo-audio-stream/android/src/main/java/net/siteed/audiostream/ExpoAudioStreamModule.kt
|
|
2
|
-
package net.siteed.audiostream
|
|
3
|
-
|
|
4
|
-
import android.Manifest
|
|
5
|
-
import android.app.ActivityManager
|
|
6
|
-
import android.content.Context
|
|
7
|
-
import android.os.Build
|
|
8
|
-
import android.os.Bundle
|
|
9
|
-
import android.util.Log
|
|
10
|
-
import androidx.annotation.RequiresApi
|
|
11
|
-
import androidx.core.os.bundleOf
|
|
12
|
-
import expo.modules.kotlin.Promise
|
|
13
|
-
import expo.modules.kotlin.modules.Module
|
|
14
|
-
import expo.modules.kotlin.modules.ModuleDefinition
|
|
15
|
-
import expo.modules.interfaces.permissions.Permissions
|
|
16
|
-
import java.util.zip.CRC32
|
|
17
|
-
|
|
18
|
-
class ExpoAudioStreamModule : Module(), EventSender {
|
|
19
|
-
private lateinit var audioRecorderManager: AudioRecorderManager
|
|
20
|
-
private lateinit var audioProcessor: AudioProcessor
|
|
21
|
-
|
|
22
|
-
@RequiresApi(Build.VERSION_CODES.R)
|
|
23
|
-
override fun definition() = ModuleDefinition {
|
|
24
|
-
// The module will be accessible from `requireNativeModule('ExpoAudioStream')` in JavaScript.
|
|
25
|
-
Name("ExpoAudioStream")
|
|
26
|
-
|
|
27
|
-
Events(
|
|
28
|
-
Constants.AUDIO_EVENT_NAME,
|
|
29
|
-
Constants.AUDIO_ANALYSIS_EVENT_NAME,
|
|
30
|
-
Constants.RECORDING_INTERRUPTED_EVENT_NAME
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
// Initialize AudioRecorderManager
|
|
34
|
-
initializeManager()
|
|
35
|
-
|
|
36
|
-
AsyncFunction("startRecording") { options: Map<String, Any?>, promise: Promise ->
|
|
37
|
-
audioRecorderManager.startRecording(options, promise)
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
Function("clearAudioFiles") {
|
|
41
|
-
audioRecorderManager.clearAudioStorage()
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
Function("status") {
|
|
45
|
-
return@Function audioRecorderManager.getStatus()
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
AsyncFunction("listAudioFiles") { promise: Promise ->
|
|
49
|
-
audioRecorderManager.listAudioFiles(promise)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
AsyncFunction("pauseRecording") { promise: Promise ->
|
|
53
|
-
audioRecorderManager.pauseRecording(promise)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
AsyncFunction("extractAudioAnalysis") { options: Map<String, Any>, promise: Promise ->
|
|
57
|
-
try {
|
|
58
|
-
val fileUri = requireNotNull(options["fileUri"] as? String) { "fileUri is required" }
|
|
59
|
-
|
|
60
|
-
// Get time or byte range options
|
|
61
|
-
val startTimeMs = options["startTimeMs"] as? Number
|
|
62
|
-
val endTimeMs = options["endTimeMs"] as? Number
|
|
63
|
-
val position = options["position"] as? Number
|
|
64
|
-
val length = options["length"] as? Number
|
|
65
|
-
val segmentDurationMs = (options["segmentDurationMs"] as? Number)?.toInt() ?: 100
|
|
66
|
-
|
|
67
|
-
// Validate ranges - can have time range OR byte range OR no range
|
|
68
|
-
val hasTimeRange = startTimeMs != null && endTimeMs != null
|
|
69
|
-
val hasByteRange = position != null && length != null
|
|
70
|
-
|
|
71
|
-
// Only throw if both ranges are provided
|
|
72
|
-
if (hasTimeRange && hasByteRange) {
|
|
73
|
-
throw IllegalArgumentException("Cannot specify both time range and byte range")
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Get decoding options with default configuration
|
|
77
|
-
val defaultConfig = DecodingConfig(
|
|
78
|
-
targetSampleRate = null,
|
|
79
|
-
targetChannels = 1, // Default to mono
|
|
80
|
-
targetBitDepth = 16,
|
|
81
|
-
normalizeAudio = false
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
val config = (options["decodingOptions"] as? Map<String, Any>)?.let { decodingOptionsMap ->
|
|
85
|
-
DecodingConfig(
|
|
86
|
-
targetSampleRate = decodingOptionsMap["targetSampleRate"] as? Int,
|
|
87
|
-
targetChannels = decodingOptionsMap["targetChannels"] as? Int,
|
|
88
|
-
targetBitDepth = (decodingOptionsMap["targetBitDepth"] as? Int) ?: 16,
|
|
89
|
-
normalizeAudio = (decodingOptionsMap["normalizeAudio"] as? Boolean) ?: false
|
|
90
|
-
)
|
|
91
|
-
} ?: defaultConfig
|
|
92
|
-
|
|
93
|
-
// Load audio data based on range type (or full file if no range specified)
|
|
94
|
-
val audioData = when {
|
|
95
|
-
hasByteRange -> {
|
|
96
|
-
val format = audioProcessor.getAudioFormat(fileUri)
|
|
97
|
-
?: throw IllegalArgumentException("Could not determine audio format")
|
|
98
|
-
|
|
99
|
-
// Calculate time range from byte position
|
|
100
|
-
val bytesPerSecond = format.sampleRate * format.channels * (format.bitDepth / 8)
|
|
101
|
-
val effectiveStartTimeMs = (position!!.toLong() * 1000) / bytesPerSecond
|
|
102
|
-
val effectiveEndTimeMs = effectiveStartTimeMs + (length!!.toLong() * 1000) / bytesPerSecond
|
|
103
|
-
|
|
104
|
-
Log.d(Constants.TAG, "Loading audio with byte range: position=$position, length=$length")
|
|
105
|
-
|
|
106
|
-
audioProcessor.loadAudioRange(
|
|
107
|
-
fileUri = fileUri,
|
|
108
|
-
startTimeMs = effectiveStartTimeMs,
|
|
109
|
-
endTimeMs = effectiveEndTimeMs,
|
|
110
|
-
config = config
|
|
111
|
-
)
|
|
112
|
-
}
|
|
113
|
-
hasTimeRange -> {
|
|
114
|
-
Log.d(Constants.TAG, "Loading audio with time range: startTimeMs=$startTimeMs, endTimeMs=$endTimeMs")
|
|
115
|
-
|
|
116
|
-
audioProcessor.loadAudioRange(
|
|
117
|
-
fileUri = fileUri,
|
|
118
|
-
startTimeMs = startTimeMs!!.toLong(),
|
|
119
|
-
endTimeMs = endTimeMs!!.toLong(),
|
|
120
|
-
config = config
|
|
121
|
-
)
|
|
122
|
-
}
|
|
123
|
-
else -> {
|
|
124
|
-
Log.d(Constants.TAG, "Loading entire audio file")
|
|
125
|
-
audioProcessor.loadAudioFromAnyFormat(fileUri, config)
|
|
126
|
-
}
|
|
127
|
-
} ?: throw IllegalStateException("Failed to load audio data")
|
|
128
|
-
|
|
129
|
-
val featuresMap = options["features"] as? Map<*, *>
|
|
130
|
-
val features = Features.parseFeatureOptions(featuresMap)
|
|
131
|
-
|
|
132
|
-
val recordingConfig = RecordingConfig(
|
|
133
|
-
sampleRate = audioData.sampleRate,
|
|
134
|
-
channels = audioData.channels,
|
|
135
|
-
encoding = when (audioData.bitDepth) {
|
|
136
|
-
8 -> "pcm_8bit"
|
|
137
|
-
16 -> "pcm_16bit"
|
|
138
|
-
32 -> "pcm_32bit"
|
|
139
|
-
else -> throw IllegalArgumentException("Unsupported bit depth: ${audioData.bitDepth}")
|
|
140
|
-
},
|
|
141
|
-
segmentDurationMs = segmentDurationMs,
|
|
142
|
-
features = features
|
|
143
|
-
)
|
|
144
|
-
|
|
145
|
-
Log.d(Constants.TAG, "extractAudioAnalysis: $recordingConfig")
|
|
146
|
-
audioProcessor.resetCumulativeAmplitudeRange()
|
|
147
|
-
|
|
148
|
-
val analysisData = audioProcessor.processAudioData(audioData.data, recordingConfig)
|
|
149
|
-
promise.resolve(analysisData.toDictionary())
|
|
150
|
-
} catch (e: Exception) {
|
|
151
|
-
Log.e(Constants.TAG, "Failed to extract audio analysis: ${e.message}", e)
|
|
152
|
-
promise.reject("PROCESSING_ERROR", e.message ?: "Unknown error", e)
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
AsyncFunction("resumeRecording") { promise: Promise ->
|
|
157
|
-
audioRecorderManager.resumeRecording(promise)
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
AsyncFunction("stopRecording") { promise: Promise ->
|
|
161
|
-
audioRecorderManager.stopRecording(promise)
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
AsyncFunction("requestPermissionsAsync") { promise: Promise ->
|
|
165
|
-
try {
|
|
166
|
-
val permissions = mutableListOf(
|
|
167
|
-
Manifest.permission.RECORD_AUDIO,
|
|
168
|
-
Manifest.permission.READ_PHONE_STATE
|
|
169
|
-
)
|
|
170
|
-
|
|
171
|
-
// Add foreground service permission for Android 14+
|
|
172
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
|
173
|
-
Log.d(Constants.TAG, "Adding FOREGROUND_SERVICE_MICROPHONE permission request")
|
|
174
|
-
permissions.add(Manifest.permission.FOREGROUND_SERVICE_MICROPHONE)
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
Log.d(Constants.TAG, "Requesting permissions: $permissions")
|
|
178
|
-
Permissions.askForPermissionsWithPermissionsManager(
|
|
179
|
-
appContext.permissions,
|
|
180
|
-
promise,
|
|
181
|
-
*permissions.toTypedArray()
|
|
182
|
-
)
|
|
183
|
-
} catch (e: Exception) {
|
|
184
|
-
Log.e(Constants.TAG, "Error requesting permissions", e)
|
|
185
|
-
promise.reject("PERMISSION_ERROR", "Failed to request permissions: ${e.message}", e)
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
AsyncFunction("getPermissionsAsync") { promise: Promise ->
|
|
190
|
-
val permissions = mutableListOf(
|
|
191
|
-
Manifest.permission.RECORD_AUDIO,
|
|
192
|
-
Manifest.permission.READ_PHONE_STATE
|
|
193
|
-
)
|
|
194
|
-
|
|
195
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
|
196
|
-
permissions.add(Manifest.permission.FOREGROUND_SERVICE_MICROPHONE)
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
Permissions.getPermissionsWithPermissionsManager(
|
|
200
|
-
appContext.permissions,
|
|
201
|
-
promise,
|
|
202
|
-
*permissions.toTypedArray()
|
|
203
|
-
)
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
AsyncFunction("requestNotificationPermissionsAsync") { promise: Promise ->
|
|
207
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
208
|
-
Permissions.askForPermissionsWithPermissionsManager(
|
|
209
|
-
appContext.permissions,
|
|
210
|
-
promise,
|
|
211
|
-
Manifest.permission.POST_NOTIFICATIONS
|
|
212
|
-
)
|
|
213
|
-
} else {
|
|
214
|
-
promise.resolve(
|
|
215
|
-
bundleOf(
|
|
216
|
-
"status" to "granted",
|
|
217
|
-
"expires" to "never",
|
|
218
|
-
"granted" to true
|
|
219
|
-
)
|
|
220
|
-
)
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
AsyncFunction("getNotificationPermissionsAsync") { promise: Promise ->
|
|
225
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
226
|
-
Permissions.getPermissionsWithPermissionsManager(
|
|
227
|
-
appContext.permissions,
|
|
228
|
-
promise,
|
|
229
|
-
Manifest.permission.POST_NOTIFICATIONS
|
|
230
|
-
)
|
|
231
|
-
} else {
|
|
232
|
-
promise.resolve(
|
|
233
|
-
bundleOf(
|
|
234
|
-
"status" to "granted",
|
|
235
|
-
"expires" to "never",
|
|
236
|
-
"granted" to true
|
|
237
|
-
)
|
|
238
|
-
)
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
AsyncFunction("trimAudio") { options: Map<String, Any>, promise: Promise ->
|
|
243
|
-
try {
|
|
244
|
-
val fileUri = requireNotNull(options["fileUri"] as? String) { "fileUri is required" }
|
|
245
|
-
val startTimeMs = requireNotNull(options["startTimeMs"] as? Number)?.toLong()
|
|
246
|
-
?: throw IllegalArgumentException("startTimeMs is required")
|
|
247
|
-
val endTimeMs = requireNotNull(options["endTimeMs"] as? Number)?.toLong()
|
|
248
|
-
?: throw IllegalArgumentException("endTimeMs is required")
|
|
249
|
-
val outputFileName = options["outputFileName"] as? String
|
|
250
|
-
|
|
251
|
-
// Get decoding options
|
|
252
|
-
val decodingOptionsMap = options["decodingOptions"] as? Map<String, Any>
|
|
253
|
-
val decodingConfig = if (decodingOptionsMap != null) {
|
|
254
|
-
DecodingConfig(
|
|
255
|
-
targetSampleRate = decodingOptionsMap["targetSampleRate"] as? Int,
|
|
256
|
-
targetChannels = decodingOptionsMap["targetChannels"] as? Int,
|
|
257
|
-
targetBitDepth = (decodingOptionsMap["targetBitDepth"] as? Int) ?: 16,
|
|
258
|
-
normalizeAudio = (decodingOptionsMap["normalizeAudio"] as? Boolean) ?: false
|
|
259
|
-
)
|
|
260
|
-
} else null
|
|
261
|
-
|
|
262
|
-
Log.d(Constants.TAG, """
|
|
263
|
-
Trimming audio with params:
|
|
264
|
-
- fileUri: $fileUri
|
|
265
|
-
- startTimeMs: $startTimeMs
|
|
266
|
-
- endTimeMs: $endTimeMs
|
|
267
|
-
- outputFileName: ${outputFileName ?: "auto-generated"}
|
|
268
|
-
""".trimIndent())
|
|
269
|
-
|
|
270
|
-
val trimmedAudio = audioProcessor.trimAudio(
|
|
271
|
-
fileUri = fileUri,
|
|
272
|
-
startTimeMs = startTimeMs,
|
|
273
|
-
endTimeMs = endTimeMs,
|
|
274
|
-
config = decodingConfig,
|
|
275
|
-
outputFileName = outputFileName
|
|
276
|
-
) ?: throw IllegalStateException("Failed to trim audio")
|
|
277
|
-
|
|
278
|
-
// Create a map with the available data
|
|
279
|
-
val resultMap = mapOf<String, Any>(
|
|
280
|
-
"sampleRate" to trimmedAudio.sampleRate,
|
|
281
|
-
"channels" to trimmedAudio.channels,
|
|
282
|
-
"bitDepth" to trimmedAudio.bitDepth,
|
|
283
|
-
"dataSize" to trimmedAudio.data.size
|
|
284
|
-
)
|
|
285
|
-
|
|
286
|
-
promise.resolve(resultMap)
|
|
287
|
-
} catch (e: Exception) {
|
|
288
|
-
Log.e(Constants.TAG, "Failed to trim audio: ${e.message}", e)
|
|
289
|
-
promise.reject("TRIM_ERROR", e.message ?: "Unknown error", e)
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
OnDestroy {
|
|
294
|
-
AudioRecorderManager.destroy()
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// Add a new function to check if recording is actually running
|
|
298
|
-
AsyncFunction("checkRecordingStatus") { promise: Promise ->
|
|
299
|
-
val isServiceRunning = AudioRecordingService::class.java.name.let { className ->
|
|
300
|
-
val manager = appContext.reactContext?.getSystemService(Context.ACTIVITY_SERVICE) as? ActivityManager
|
|
301
|
-
manager?.getRunningServices(Integer.MAX_VALUE)
|
|
302
|
-
?.any { it.service.className == className }
|
|
303
|
-
} ?: false
|
|
304
|
-
|
|
305
|
-
val status = audioRecorderManager.getStatus()
|
|
306
|
-
|
|
307
|
-
// If service is running but isRecording is false, we need to cleanup
|
|
308
|
-
if (isServiceRunning && !status.getBoolean("isRecording")) {
|
|
309
|
-
audioRecorderManager.cleanup()
|
|
310
|
-
AudioRecordingService.stopService(appContext.reactContext!!)
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
promise.resolve(status)
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
AsyncFunction("extractAudioData") { options: Map<String, Any>, promise: Promise ->
|
|
317
|
-
try {
|
|
318
|
-
val fileUri = requireNotNull(options["fileUri"] as? String) { "fileUri is required" }
|
|
319
|
-
val startTimeMs = options["startTimeMs"] as? Number
|
|
320
|
-
val endTimeMs = options["endTimeMs"] as? Number
|
|
321
|
-
val position = options["position"] as? Number
|
|
322
|
-
val length = options["length"] as? Number
|
|
323
|
-
|
|
324
|
-
// Validate that we have either time range or byte range, but not both and not neither
|
|
325
|
-
val hasTimeRange = startTimeMs != null && endTimeMs != null
|
|
326
|
-
val hasByteRange = position != null && length != null
|
|
327
|
-
|
|
328
|
-
if (!hasTimeRange && !hasByteRange) {
|
|
329
|
-
throw IllegalArgumentException("Must specify either time range (startTimeMs, endTimeMs) or byte range (position, length)")
|
|
330
|
-
}
|
|
331
|
-
if (hasTimeRange && hasByteRange) {
|
|
332
|
-
throw IllegalArgumentException("Cannot specify both time range and byte range")
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
// Get decoding options
|
|
336
|
-
val decodingOptionsMap = options["decodingOptions"] as? Map<String, Any>
|
|
337
|
-
val decodingConfig = if (decodingOptionsMap != null) {
|
|
338
|
-
DecodingConfig(
|
|
339
|
-
targetSampleRate = decodingOptionsMap["targetSampleRate"] as? Int,
|
|
340
|
-
targetChannels = decodingOptionsMap["targetChannels"] as? Int,
|
|
341
|
-
targetBitDepth = (decodingOptionsMap["targetBitDepth"] as? Int) ?: 16,
|
|
342
|
-
normalizeAudio = (decodingOptionsMap["normalizeAudio"] as? Boolean) ?: false
|
|
343
|
-
).also {
|
|
344
|
-
Log.d(Constants.TAG, """
|
|
345
|
-
Using decoding config:
|
|
346
|
-
- targetSampleRate: ${it.targetSampleRate ?: "original"}
|
|
347
|
-
- targetChannels: ${it.targetChannels ?: "original"}
|
|
348
|
-
- targetBitDepth: ${it.targetBitDepth}
|
|
349
|
-
- normalizeAudio: ${it.normalizeAudio}
|
|
350
|
-
""".trimIndent())
|
|
351
|
-
}
|
|
352
|
-
} else null
|
|
353
|
-
|
|
354
|
-
val audioData = if (hasByteRange) {
|
|
355
|
-
val format = audioProcessor.getAudioFormat(fileUri)
|
|
356
|
-
?: throw IllegalArgumentException("Could not determine audio format")
|
|
357
|
-
|
|
358
|
-
// Calculate time range from byte position
|
|
359
|
-
val bytesPerSecond = format.sampleRate * format.channels * (format.bitDepth / 8)
|
|
360
|
-
val effectiveStartTimeMs = (position!!.toLong() * 1000) / bytesPerSecond
|
|
361
|
-
val effectiveEndTimeMs = effectiveStartTimeMs + (length!!.toLong() * 1000) / bytesPerSecond
|
|
362
|
-
|
|
363
|
-
Log.d(Constants.TAG, """
|
|
364
|
-
Converting byte range to time range:
|
|
365
|
-
- position: $position bytes
|
|
366
|
-
- length: $length bytes
|
|
367
|
-
- bytesPerSecond: $bytesPerSecond
|
|
368
|
-
- effectiveStartTimeMs: $effectiveStartTimeMs
|
|
369
|
-
- effectiveEndTimeMs: $effectiveEndTimeMs
|
|
370
|
-
""".trimIndent())
|
|
371
|
-
|
|
372
|
-
audioProcessor.loadAudioRange(
|
|
373
|
-
fileUri = fileUri,
|
|
374
|
-
startTimeMs = effectiveStartTimeMs,
|
|
375
|
-
endTimeMs = effectiveEndTimeMs,
|
|
376
|
-
config = decodingConfig
|
|
377
|
-
)
|
|
378
|
-
} else {
|
|
379
|
-
// Must be time range due to earlier validation
|
|
380
|
-
Log.d(Constants.TAG, """
|
|
381
|
-
Using time range:
|
|
382
|
-
- startTimeMs: $startTimeMs
|
|
383
|
-
- endTimeMs: $endTimeMs
|
|
384
|
-
""".trimIndent())
|
|
385
|
-
|
|
386
|
-
audioProcessor.loadAudioRange(
|
|
387
|
-
fileUri = fileUri,
|
|
388
|
-
startTimeMs = startTimeMs!!.toLong(),
|
|
389
|
-
endTimeMs = endTimeMs!!.toLong(),
|
|
390
|
-
config = decodingConfig
|
|
391
|
-
)
|
|
392
|
-
} ?: throw IllegalStateException("Failed to load audio data")
|
|
393
|
-
|
|
394
|
-
Log.d(Constants.TAG, """
|
|
395
|
-
Audio data loaded successfully:
|
|
396
|
-
- data size: ${audioData.data.size} bytes
|
|
397
|
-
- sampleRate: ${audioData.sampleRate}
|
|
398
|
-
- channels: ${audioData.channels}
|
|
399
|
-
- bitDepth: ${audioData.bitDepth}
|
|
400
|
-
- durationMs: ${audioData.durationMs}
|
|
401
|
-
""".trimIndent())
|
|
402
|
-
|
|
403
|
-
val includeNormalizedData = options["includeNormalizedData"] as? Boolean ?: false
|
|
404
|
-
val includeBase64Data = options["includeBase64Data"] as? Boolean ?: false
|
|
405
|
-
val includeWavHeader = options["includeWavHeader"] as? Boolean ?: false
|
|
406
|
-
val bytesPerSample = audioData.bitDepth / 8
|
|
407
|
-
val samples = audioData.data.size / (bytesPerSample * audioData.channels)
|
|
408
|
-
|
|
409
|
-
// Create the result map
|
|
410
|
-
val resultMap = mutableMapOf<String, Any>()
|
|
411
|
-
|
|
412
|
-
// Add WAV header if requested
|
|
413
|
-
if (includeWavHeader) {
|
|
414
|
-
// Use ByteArrayOutputStream to write the WAV header and data
|
|
415
|
-
val outputStream = java.io.ByteArrayOutputStream()
|
|
416
|
-
val audioFileHandler = AudioFileHandler(appContext.reactContext!!.filesDir)
|
|
417
|
-
|
|
418
|
-
// Write the WAV header
|
|
419
|
-
audioFileHandler.writeWavHeader(
|
|
420
|
-
outputStream,
|
|
421
|
-
audioData.sampleRate,
|
|
422
|
-
audioData.channels,
|
|
423
|
-
audioData.bitDepth
|
|
424
|
-
)
|
|
425
|
-
|
|
426
|
-
// Write the PCM data
|
|
427
|
-
outputStream.write(audioData.data)
|
|
428
|
-
|
|
429
|
-
// Get the complete WAV data
|
|
430
|
-
val wavData = outputStream.toByteArray()
|
|
431
|
-
|
|
432
|
-
resultMap["pcmData"] = wavData
|
|
433
|
-
resultMap["hasWavHeader"] = true
|
|
434
|
-
|
|
435
|
-
Log.d(Constants.TAG, "Added WAV header to PCM data, total size: ${wavData.size} bytes")
|
|
436
|
-
} else {
|
|
437
|
-
resultMap["pcmData"] = audioData.data
|
|
438
|
-
resultMap["hasWavHeader"] = false
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
// Add the rest of the data
|
|
442
|
-
resultMap.putAll(mapOf(
|
|
443
|
-
"sampleRate" to audioData.sampleRate,
|
|
444
|
-
"channels" to audioData.channels,
|
|
445
|
-
"bitDepth" to audioData.bitDepth,
|
|
446
|
-
"durationMs" to audioData.durationMs,
|
|
447
|
-
"format" to "pcm_${audioData.bitDepth}bit",
|
|
448
|
-
"samples" to samples
|
|
449
|
-
))
|
|
450
|
-
|
|
451
|
-
// Add checksum if requested
|
|
452
|
-
if (options["computeChecksum"] == true) {
|
|
453
|
-
val crc32 = CRC32()
|
|
454
|
-
crc32.update(audioData.data)
|
|
455
|
-
resultMap["checksum"] = crc32.value.toInt()
|
|
456
|
-
|
|
457
|
-
Log.d(Constants.TAG, "Computed CRC32 checksum: ${crc32.value}")
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
if (includeNormalizedData) {
|
|
461
|
-
val float32Data = AudioFormatUtils.convertByteArrayToFloatArray(
|
|
462
|
-
audioData.data,
|
|
463
|
-
"pcm_${audioData.bitDepth}bit"
|
|
464
|
-
)
|
|
465
|
-
resultMap["normalizedData"] = float32Data
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
if (includeBase64Data) {
|
|
469
|
-
// Convert the PCM data to a base64 string
|
|
470
|
-
val base64Data = android.util.Base64.encodeToString(
|
|
471
|
-
audioData.data,
|
|
472
|
-
android.util.Base64.NO_WRAP
|
|
473
|
-
)
|
|
474
|
-
resultMap["base64Data"] = base64Data
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
promise.resolve(resultMap)
|
|
478
|
-
} catch (e: Exception) {
|
|
479
|
-
Log.e(Constants.TAG, "Failed to extract audio data: ${e.message}")
|
|
480
|
-
Log.e(Constants.TAG, "Stack trace: ${e.stackTraceToString()}")
|
|
481
|
-
promise.reject("PROCESSING_ERROR", e.message ?: "Unknown error", e)
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
private fun initializeManager() {
|
|
487
|
-
val androidContext =
|
|
488
|
-
appContext.reactContext ?: throw IllegalStateException("Android context not available")
|
|
489
|
-
val permissionUtils = PermissionUtils(androidContext)
|
|
490
|
-
val audioEncoder = AudioDataEncoder()
|
|
491
|
-
audioRecorderManager =
|
|
492
|
-
AudioRecorderManager(androidContext, androidContext.filesDir, permissionUtils, audioEncoder, this)
|
|
493
|
-
audioRecorderManager = AudioRecorderManager.initialize(
|
|
494
|
-
androidContext,
|
|
495
|
-
androidContext.filesDir,
|
|
496
|
-
permissionUtils,
|
|
497
|
-
audioEncoder,
|
|
498
|
-
this
|
|
499
|
-
)
|
|
500
|
-
audioProcessor = AudioProcessor(androidContext.filesDir)
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
override fun sendExpoEvent(eventName: String, params: Bundle) {
|
|
505
|
-
Log.d(Constants.TAG, "Sending event: $eventName")
|
|
506
|
-
this@ExpoAudioStreamModule.sendEvent(eventName, params)
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
}
|