@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.
Files changed (166) hide show
  1. package/README.md +46 -27
  2. package/build/index.d.ts +11 -12
  3. package/build/index.js +44 -10
  4. package/package.json +49 -110
  5. package/src/index.ts +18 -33
  6. package/CHANGELOG.md +0 -195
  7. package/android/build.gradle +0 -105
  8. package/android/src/main/AndroidManifest.xml +0 -27
  9. package/android/src/main/java/net/siteed/audiostream/AudioAnalysisData.kt +0 -166
  10. package/android/src/main/java/net/siteed/audiostream/AudioDataEncoder.kt +0 -9
  11. package/android/src/main/java/net/siteed/audiostream/AudioFileHandler.kt +0 -131
  12. package/android/src/main/java/net/siteed/audiostream/AudioFormatUtils.kt +0 -103
  13. package/android/src/main/java/net/siteed/audiostream/AudioNotificationsManager.kt +0 -435
  14. package/android/src/main/java/net/siteed/audiostream/AudioProcessor.kt +0 -1936
  15. package/android/src/main/java/net/siteed/audiostream/AudioRecorderManager.kt +0 -1437
  16. package/android/src/main/java/net/siteed/audiostream/AudioRecordingService.kt +0 -138
  17. package/android/src/main/java/net/siteed/audiostream/Constants.kt +0 -20
  18. package/android/src/main/java/net/siteed/audiostream/EventSender.kt +0 -7
  19. package/android/src/main/java/net/siteed/audiostream/ExpoAudioStreamModule.kt +0 -509
  20. package/android/src/main/java/net/siteed/audiostream/FFT.kt +0 -99
  21. package/android/src/main/java/net/siteed/audiostream/Features.kt +0 -98
  22. package/android/src/main/java/net/siteed/audiostream/NotificationConfig.kt +0 -70
  23. package/android/src/main/java/net/siteed/audiostream/PermissionUtils.kt +0 -59
  24. package/android/src/main/java/net/siteed/audiostream/RecordingActionReceiver.kt +0 -59
  25. package/android/src/main/java/net/siteed/audiostream/RecordingConfig.kt +0 -205
  26. package/android/src/main/java/net/siteed/audiostream/WaveformConfig.kt +0 -19
  27. package/android/src/main/java/net/siteed/audiostream/WaveformRenderer.kt +0 -159
  28. package/android/src/main/res/drawable/ic_default_action_icon.xml +0 -16
  29. package/android/src/main/res/drawable/ic_microphone.xml +0 -13
  30. package/android/src/main/res/drawable/ic_pause.xml +0 -10
  31. package/android/src/main/res/drawable/ic_play.xml +0 -10
  32. package/android/src/main/res/drawable/ic_stop.xml +0 -10
  33. package/android/src/main/res/layout/notification_recording.xml +0 -37
  34. package/android/src/main/test/java/net/siteed/audiostream/AudioProcessorTest.kt +0 -56
  35. package/app.plugin.js +0 -1
  36. package/build/AudioAnalysis/AudioAnalysis.types.d.ts +0 -144
  37. package/build/AudioAnalysis/AudioAnalysis.types.d.ts.map +0 -1
  38. package/build/AudioAnalysis/AudioAnalysis.types.js +0 -3
  39. package/build/AudioAnalysis/AudioAnalysis.types.js.map +0 -1
  40. package/build/AudioAnalysis/extractAudioAnalysis.d.ts +0 -78
  41. package/build/AudioAnalysis/extractAudioAnalysis.d.ts.map +0 -1
  42. package/build/AudioAnalysis/extractAudioAnalysis.js +0 -229
  43. package/build/AudioAnalysis/extractAudioAnalysis.js.map +0 -1
  44. package/build/AudioAnalysis/extractWaveform.d.ts +0 -8
  45. package/build/AudioAnalysis/extractWaveform.d.ts.map +0 -1
  46. package/build/AudioAnalysis/extractWaveform.js +0 -11
  47. package/build/AudioAnalysis/extractWaveform.js.map +0 -1
  48. package/build/AudioRecorder.provider.d.ts +0 -11
  49. package/build/AudioRecorder.provider.d.ts.map +0 -1
  50. package/build/AudioRecorder.provider.js +0 -37
  51. package/build/AudioRecorder.provider.js.map +0 -1
  52. package/build/ExpoAudioStream.native.d.ts +0 -3
  53. package/build/ExpoAudioStream.native.d.ts.map +0 -1
  54. package/build/ExpoAudioStream.native.js +0 -6
  55. package/build/ExpoAudioStream.native.js.map +0 -1
  56. package/build/ExpoAudioStream.types.d.ts +0 -206
  57. package/build/ExpoAudioStream.types.d.ts.map +0 -1
  58. package/build/ExpoAudioStream.types.js +0 -2
  59. package/build/ExpoAudioStream.types.js.map +0 -1
  60. package/build/ExpoAudioStream.web.d.ts +0 -59
  61. package/build/ExpoAudioStream.web.d.ts.map +0 -1
  62. package/build/ExpoAudioStream.web.js +0 -285
  63. package/build/ExpoAudioStream.web.js.map +0 -1
  64. package/build/ExpoAudioStreamModule.d.ts +0 -3
  65. package/build/ExpoAudioStreamModule.d.ts.map +0 -1
  66. package/build/ExpoAudioStreamModule.js +0 -239
  67. package/build/ExpoAudioStreamModule.js.map +0 -1
  68. package/build/WebRecorder.web.d.ts +0 -119
  69. package/build/WebRecorder.web.d.ts.map +0 -1
  70. package/build/WebRecorder.web.js +0 -436
  71. package/build/WebRecorder.web.js.map +0 -1
  72. package/build/constants.d.ts +0 -11
  73. package/build/constants.d.ts.map +0 -1
  74. package/build/constants.js +0 -14
  75. package/build/constants.js.map +0 -1
  76. package/build/events.d.ts +0 -26
  77. package/build/events.d.ts.map +0 -1
  78. package/build/events.js +0 -21
  79. package/build/events.js.map +0 -1
  80. package/build/index.d.ts.map +0 -1
  81. package/build/index.js.map +0 -1
  82. package/build/useAudioRecorder.d.ts +0 -21
  83. package/build/useAudioRecorder.d.ts.map +0 -1
  84. package/build/useAudioRecorder.js +0 -427
  85. package/build/useAudioRecorder.js.map +0 -1
  86. package/build/utils/BlobFix.d.ts +0 -9
  87. package/build/utils/BlobFix.d.ts.map +0 -1
  88. package/build/utils/BlobFix.js +0 -498
  89. package/build/utils/BlobFix.js.map +0 -1
  90. package/build/utils/audioProcessing.d.ts +0 -24
  91. package/build/utils/audioProcessing.d.ts.map +0 -1
  92. package/build/utils/audioProcessing.js +0 -133
  93. package/build/utils/audioProcessing.js.map +0 -1
  94. package/build/utils/concatenateBuffers.d.ts +0 -8
  95. package/build/utils/concatenateBuffers.d.ts.map +0 -1
  96. package/build/utils/concatenateBuffers.js +0 -21
  97. package/build/utils/concatenateBuffers.js.map +0 -1
  98. package/build/utils/convertPCMToFloat32.d.ts +0 -13
  99. package/build/utils/convertPCMToFloat32.d.ts.map +0 -1
  100. package/build/utils/convertPCMToFloat32.js +0 -120
  101. package/build/utils/convertPCMToFloat32.js.map +0 -1
  102. package/build/utils/encodingToBitDepth.d.ts +0 -5
  103. package/build/utils/encodingToBitDepth.d.ts.map +0 -1
  104. package/build/utils/encodingToBitDepth.js +0 -13
  105. package/build/utils/encodingToBitDepth.js.map +0 -1
  106. package/build/utils/getWavFileInfo.d.ts +0 -26
  107. package/build/utils/getWavFileInfo.d.ts.map +0 -1
  108. package/build/utils/getWavFileInfo.js +0 -92
  109. package/build/utils/getWavFileInfo.js.map +0 -1
  110. package/build/utils/writeWavHeader.d.ts +0 -49
  111. package/build/utils/writeWavHeader.d.ts.map +0 -1
  112. package/build/utils/writeWavHeader.js +0 -91
  113. package/build/utils/writeWavHeader.js.map +0 -1
  114. package/build/workers/InlineFeaturesExtractor.web.d.ts +0 -2
  115. package/build/workers/InlineFeaturesExtractor.web.d.ts.map +0 -1
  116. package/build/workers/InlineFeaturesExtractor.web.js +0 -828
  117. package/build/workers/InlineFeaturesExtractor.web.js.map +0 -1
  118. package/build/workers/inlineAudioWebWorker.web.d.ts +0 -2
  119. package/build/workers/inlineAudioWebWorker.web.d.ts.map +0 -1
  120. package/build/workers/inlineAudioWebWorker.web.js +0 -157
  121. package/build/workers/inlineAudioWebWorker.web.js.map +0 -1
  122. package/expo-module.config.json +0 -9
  123. package/ios/AudioAnalysisData.swift +0 -74
  124. package/ios/AudioNotificationManager.swift +0 -135
  125. package/ios/AudioProcessingHelpers.swift +0 -743
  126. package/ios/AudioProcessor.swift +0 -858
  127. package/ios/AudioStreamError.swift +0 -7
  128. package/ios/AudioStreamManager.swift +0 -1708
  129. package/ios/AudioStreamManagerDelegate.swift +0 -16
  130. package/ios/DataPoint.swift +0 -54
  131. package/ios/DecodingConfig.swift +0 -47
  132. package/ios/ExpoAudioStream.podspec +0 -27
  133. package/ios/ExpoAudioStreamModule.swift +0 -698
  134. package/ios/FFT.swift +0 -62
  135. package/ios/Features.swift +0 -95
  136. package/ios/Logger.swift +0 -7
  137. package/ios/NotificationExtension.swift +0 -15
  138. package/ios/RecordingResult.swift +0 -22
  139. package/ios/RecordingSettings.swift +0 -265
  140. package/ios/WaveformExtractor.swift +0 -105
  141. package/plugin/build/index.d.ts +0 -21
  142. package/plugin/build/index.js +0 -191
  143. package/plugin/src/index.ts +0 -278
  144. package/plugin/tsconfig.json +0 -10
  145. package/plugin/tsconfig.tsbuildinfo +0 -1
  146. package/src/AudioAnalysis/AudioAnalysis.types.ts +0 -165
  147. package/src/AudioAnalysis/extractAudioAnalysis.ts +0 -370
  148. package/src/AudioAnalysis/extractWaveform.ts +0 -22
  149. package/src/AudioRecorder.provider.tsx +0 -54
  150. package/src/ExpoAudioStream.native.ts +0 -6
  151. package/src/ExpoAudioStream.types.ts +0 -329
  152. package/src/ExpoAudioStream.web.ts +0 -359
  153. package/src/ExpoAudioStreamModule.ts +0 -286
  154. package/src/WebRecorder.web.ts +0 -580
  155. package/src/constants.ts +0 -18
  156. package/src/events.ts +0 -60
  157. package/src/useAudioRecorder.tsx +0 -620
  158. package/src/utils/BlobFix.ts +0 -559
  159. package/src/utils/audioProcessing.ts +0 -205
  160. package/src/utils/concatenateBuffers.ts +0 -24
  161. package/src/utils/convertPCMToFloat32.ts +0 -170
  162. package/src/utils/encodingToBitDepth.ts +0 -18
  163. package/src/utils/getWavFileInfo.ts +0 -132
  164. package/src/utils/writeWavHeader.ts +0 -114
  165. package/src/workers/InlineFeaturesExtractor.web.tsx +0 -827
  166. package/src/workers/inlineAudioWebWorker.web.tsx +0 -156
@@ -1,99 +0,0 @@
1
- // packages/expo-audio-stream/android/src/main/java/net/siteed/audiostream/FFT.kt
2
- package net.siteed.audiostream
3
-
4
- import kotlin.math.PI
5
- import kotlin.math.cos
6
- import kotlin.math.sin
7
- import kotlin.math.sqrt
8
-
9
- class FFT(private val n: Int) {
10
- private val cosTable = FloatArray(n / 2)
11
- private val sinTable = FloatArray(n / 2)
12
- private val hannWindow = FloatArray(n)
13
-
14
- init {
15
- // Precompute trig tables
16
- for (i in 0 until n / 2) {
17
- cosTable[i] = cos(2.0 * PI * i / n).toFloat()
18
- sinTable[i] = sin(2.0 * PI * i / n).toFloat()
19
- }
20
-
21
- // Precompute normalized Hann window to match vDSP
22
- val normalizationFactor = sqrt(2.0f / n) // Match vDSP normalization
23
- for (i in hannWindow.indices) {
24
- hannWindow[i] = normalizationFactor * 0.5f * (1 - cos(2.0 * PI * i / (n - 1))).toFloat()
25
- }
26
- }
27
-
28
- fun processSegment(segment: FloatArray): FloatArray {
29
- // Pad or truncate input to match FFT length
30
- val paddedSegment = if (segment.size < n) {
31
- segment + FloatArray(n - segment.size)
32
- } else {
33
- segment.copyOf(n)
34
- }
35
-
36
- // Apply normalized Hann window
37
- for (i in paddedSegment.indices) {
38
- paddedSegment[i] *= hannWindow[i]
39
- }
40
-
41
- // Perform FFT
42
- realForward(paddedSegment)
43
-
44
- return paddedSegment
45
- }
46
-
47
- fun realForward(data: FloatArray) {
48
- realForwardRecursive(data)
49
- }
50
-
51
- private fun realForwardRecursive(data: FloatArray) {
52
- val n = data.size
53
- if (n <= 1) return
54
-
55
- val even = FloatArray(n / 2)
56
- val odd = FloatArray(n / 2)
57
-
58
- for (i in 0 until n / 2) {
59
- even[i] = data[2 * i]
60
- odd[i] = data[2 * i + 1]
61
- }
62
-
63
- realForwardRecursive(even)
64
- realForwardRecursive(odd)
65
-
66
- for (i in 0 until n / 2) {
67
- val t = cosTable[i] * odd[i] - sinTable[i] * even[i]
68
- val u = sinTable[i] * odd[i] + cosTable[i] * even[i]
69
- data[i] = even[i] + t
70
- data[i + n / 2] = even[i] - t
71
- }
72
- }
73
-
74
- fun realInverse(powerSpectrum: FloatArray, output: FloatArray) {
75
- // Copy power spectrum to complex format for inverse FFT
76
- val complexData = FloatArray(n * 2)
77
- for (i in 0 until n/2 + 1) {
78
- complexData[2 * i] = powerSpectrum[i]
79
- if (2 * i + 1 < complexData.size) {
80
- complexData[2 * i + 1] = 0f
81
- }
82
- }
83
-
84
- // Conjugate for inverse FFT
85
- for (i in 0 until n) {
86
- if (2 * i + 1 < complexData.size) {
87
- complexData[2 * i + 1] = -complexData[2 * i + 1]
88
- }
89
- }
90
-
91
- // Perform forward FFT (which is inverse when input is conjugated)
92
- realForward(complexData)
93
-
94
- // Copy real part to output and conjugate again
95
- for (i in 0 until n) {
96
- output[i] = complexData[2 * i] / n
97
- }
98
- }
99
- }
@@ -1,98 +0,0 @@
1
- package net.siteed.audiostream
2
-
3
- import android.os.Bundle
4
- import androidx.core.os.bundleOf
5
-
6
- data class Features(
7
- val energy: Float = 0f,
8
- val mfcc: List<Float> = emptyList(),
9
- val rms: Float = 0f,
10
- val minAmplitude: Float = 0f,
11
- val maxAmplitude: Float = 0f,
12
- val zcr: Float = 0f,
13
- val spectralCentroid: Float = 0f,
14
- val spectralFlatness: Float = 0f,
15
- val spectralRollOff: Float = 0f,
16
- val spectralBandwidth: Float = 0f,
17
- val tempo: Float = 0f,
18
- val hnr: Float = 0f,
19
- val melSpectrogram: List<Float> = emptyList(),
20
- val chromagram: List<Float> = emptyList(),
21
- val spectralContrast: List<Float> = emptyList(),
22
- val tonnetz: List<Float> = emptyList(),
23
- val pitch: Float = 0f,
24
- val crc32: Long? = null
25
- ) {
26
- fun toDictionary(): Map<String, Any> {
27
- val baseMap = mapOf(
28
- "energy" to energy,
29
- "mfcc" to mfcc,
30
- "rms" to rms,
31
- "minAmplitude" to minAmplitude,
32
- "maxAmplitude" to maxAmplitude,
33
- "zcr" to zcr,
34
- "spectralCentroid" to spectralCentroid,
35
- "spectralFlatness" to spectralFlatness,
36
- "spectralRollOff" to spectralRollOff,
37
- "spectralBandwidth" to spectralBandwidth,
38
- "tempo" to tempo,
39
- "hnr" to hnr,
40
- "melSpectrogram" to melSpectrogram,
41
- "chromagram" to chromagram,
42
- "spectralContrast" to spectralContrast,
43
- "tonnetz" to tonnetz,
44
- "pitch" to pitch,
45
- "crc32" to (crc32 ?: 0)
46
- )
47
- return baseMap.filterValues { it != null }
48
- }
49
-
50
- fun toBundle(): Bundle {
51
- return bundleOf(
52
- "energy" to energy,
53
- "mfcc" to mfcc,
54
- "rms" to rms,
55
- "minAmplitude" to minAmplitude,
56
- "maxAmplitude" to maxAmplitude,
57
- "zcr" to zcr,
58
- "spectralCentroid" to spectralCentroid,
59
- "spectralFlatness" to spectralFlatness,
60
- "spectralRollOff" to spectralRollOff,
61
- "spectralBandwidth" to spectralBandwidth,
62
- "tempo" to tempo,
63
- "hnr" to hnr,
64
- "melSpectrogram" to melSpectrogram,
65
- "chromagram" to chromagram,
66
- "spectralContrast" to spectralContrast,
67
- "tonnetz" to tonnetz,
68
- "pitch" to pitch,
69
- "crc32" to (crc32 ?: 0)
70
- )
71
- }
72
-
73
- companion object {
74
- fun parseFeatureOptions(options: Map<*, *>?): Map<String, Boolean> {
75
- return options?.let { map ->
76
- mapOf(
77
- "energy" to (map["energy"] as? Boolean ?: false),
78
- "mfcc" to (map["mfcc"] as? Boolean ?: false),
79
- "rms" to (map["rms"] as? Boolean ?: false),
80
- "zcr" to (map["zcr"] as? Boolean ?: false),
81
- "dB" to (map["dB"] as? Boolean ?: false),
82
- "spectralCentroid" to (map["spectralCentroid"] as? Boolean ?: false),
83
- "spectralFlatness" to (map["spectralFlatness"] as? Boolean ?: false),
84
- "spectralRollOff" to (map["spectralRollOff"] as? Boolean ?: false),
85
- "spectralBandwidth" to (map["spectralBandwidth"] as? Boolean ?: false),
86
- "chromagram" to (map["chromagram"] as? Boolean ?: false),
87
- "tempo" to (map["tempo"] as? Boolean ?: false),
88
- "hnr" to (map["hnr"] as? Boolean ?: false),
89
- "melSpectrogram" to (map["melSpectrogram"] as? Boolean ?: false),
90
- "spectralContrast" to (map["spectralContrast"] as? Boolean ?: false),
91
- "tonnetz" to (map["tonnetz"] as? Boolean ?: false),
92
- "pitch" to (map["pitch"] as? Boolean ?: false),
93
- "crc32" to (map["crc32"] as? Boolean ?: false)
94
- )
95
- } ?: emptyMap()
96
- }
97
- }
98
- }
@@ -1,70 +0,0 @@
1
- package net.siteed.audiostream
2
-
3
- data class NotificationConfig(
4
- val title: String = "Recording...",
5
- val text: String = "",
6
- val icon: String? = null,
7
- val channelId: String = "audio_recording_channel",
8
- val notificationId: Int = 1,
9
- val actions: List<NotificationAction> = emptyList(),
10
- val channelName: String = "Audio Recording",
11
- val channelDescription: String = "Shows audio recording status",
12
- val waveform: WaveformConfig? = null,
13
- val lightColor: String = "#FF0000",
14
- val priority: String = "high",
15
- val accentColor: String? = null
16
- ) {
17
- companion object {
18
- fun fromMap(map: Map<String, Any?>?): NotificationConfig {
19
- if (map == null) return NotificationConfig()
20
-
21
- val androidMap = map["android"] as? Map<String, Any?> ?: emptyMap()
22
-
23
- return NotificationConfig(
24
- title = map["title"] as? String ?: "Recording...",
25
- text = map["text"] as? String ?: "",
26
- icon = map["icon"] as? String,
27
- channelId = androidMap["channelId"] as? String ?: "audio_recording_channel",
28
- notificationId = (androidMap["notificationId"] as? Number)?.toInt() ?: 1,
29
- actions = parseNotificationActions(androidMap["actions"] as? List<Map<String, Any?>>),
30
- channelName = androidMap["channelName"] as? String ?: "Audio Recording",
31
- channelDescription = androidMap["channelDescription"] as? String ?: "Shows audio recording status",
32
- waveform = parseWaveformConfig(androidMap["waveform"] as? Map<String, Any?>),
33
- lightColor = androidMap["lightColor"] as? String ?: "#FF0000",
34
- priority = androidMap["priority"] as? String ?: "high",
35
- accentColor = androidMap["accentColor"] as? String
36
- )
37
- }
38
-
39
- private fun parseNotificationActions(actionsList: List<Map<String, Any?>>?): List<NotificationAction> {
40
- return actionsList?.mapNotNull { actionMap ->
41
- if (actionMap["title"] != null && actionMap["identifier"] != null) {
42
- NotificationAction(
43
- title = actionMap["title"] as String,
44
- icon = actionMap["icon"] as? String,
45
- intentAction = actionMap["identifier"] as String
46
- )
47
- } else null
48
- } ?: emptyList()
49
- }
50
-
51
- private fun parseWaveformConfig(waveformMap: Map<String, Any?>?): WaveformConfig? {
52
- if (waveformMap == null) return null
53
-
54
- return WaveformConfig(
55
- color = waveformMap["color"] as? String ?: "#FFFFFF",
56
- opacity = (waveformMap["opacity"] as? Number)?.toFloat() ?: 1.0f,
57
- strokeWidth = (waveformMap["strokeWidth"] as? Number)?.toFloat() ?: 1.5f,
58
- style = waveformMap["style"] as? String ?: "stroke",
59
- mirror = waveformMap["mirror"] as? Boolean ?: true,
60
- height = (waveformMap["height"] as? Number)?.toInt() ?: 64
61
- )
62
- }
63
- }
64
- }
65
-
66
- data class NotificationAction(
67
- val title: String,
68
- val icon: String? = null,
69
- val intentAction: String
70
- )
@@ -1,59 +0,0 @@
1
- package net.siteed.audiostream
2
-
3
- import android.content.Context
4
- import android.content.pm.PackageManager
5
- import android.os.Build
6
- import androidx.core.content.ContextCompat
7
- import android.Manifest
8
- import android.util.Log
9
-
10
- class PermissionUtils(private val context: Context) {
11
- fun checkRecordingPermission(): Boolean {
12
- val hasRecordPermission = ContextCompat.checkSelfPermission(
13
- context,
14
- Manifest.permission.RECORD_AUDIO
15
- ) == PackageManager.PERMISSION_GRANTED
16
-
17
- Log.d(Constants.TAG, "RECORD_AUDIO permission: $hasRecordPermission")
18
-
19
- // Check for foreground service permission on Android 14+
20
- val hasForegroundService = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
21
- val result = ContextCompat.checkSelfPermission(
22
- context,
23
- Manifest.permission.FOREGROUND_SERVICE_MICROPHONE
24
- ) == PackageManager.PERMISSION_GRANTED
25
- Log.d(Constants.TAG, "FOREGROUND_SERVICE_MICROPHONE permission: $result (Android 14+)")
26
- result
27
- } else {
28
- Log.d(Constants.TAG, "FOREGROUND_SERVICE_MICROPHONE not required (Android < 14)")
29
- true
30
- }
31
-
32
- val result = hasRecordPermission && hasForegroundService
33
- Log.d(Constants.TAG, "Final recording permission result: $result")
34
- return result
35
- }
36
-
37
- fun checkNotificationPermission(): Boolean {
38
- val result = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
39
- val hasPermission = ContextCompat.checkSelfPermission(
40
- context,
41
- Manifest.permission.POST_NOTIFICATIONS
42
- ) == PackageManager.PERMISSION_GRANTED
43
- Log.d(Constants.TAG, "POST_NOTIFICATIONS permission: $hasPermission (Android 13+)")
44
- hasPermission
45
- } else {
46
- Log.d(Constants.TAG, "POST_NOTIFICATIONS not required (Android < 13)")
47
- true
48
- }
49
- return result
50
- }
51
-
52
- fun checkPhoneStatePermission(): Boolean {
53
- return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
54
- context.checkSelfPermission(android.Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED
55
- } else {
56
- true
57
- }
58
- }
59
- }
@@ -1,59 +0,0 @@
1
- package net.siteed.audiostream
2
-
3
- import android.content.BroadcastReceiver
4
- import android.content.Context
5
- import android.content.Intent
6
- import android.util.Log
7
- import expo.modules.kotlin.Promise
8
- import java.util.concurrent.atomic.AtomicBoolean
9
-
10
- class RecordingActionReceiver : BroadcastReceiver() {
11
- companion object {
12
- const val ACTION_PAUSE_RECORDING = "net.siteed.audiostream.PAUSE_RECORDING"
13
- const val ACTION_RESUME_RECORDING = "net.siteed.audiostream.RESUME_RECORDING"
14
- private val isProcessingAction = AtomicBoolean(false)
15
- }
16
-
17
- override fun onReceive(context: Context, intent: Intent) {
18
- when (intent.action) {
19
- ACTION_PAUSE_RECORDING, ACTION_RESUME_RECORDING -> handleRecordingAction(intent.action)
20
- else -> Log.w("RecordingActionReceiver", "Unknown action: ${intent.action}")
21
- }
22
- }
23
-
24
- private fun handleRecordingAction(action: String?) {
25
- if (!isProcessingAction.compareAndSet(false, true)) {
26
- Log.d("RecordingActionReceiver", "Action already in progress, skipping")
27
- return
28
- }
29
-
30
- try {
31
- val audioRecorderManager = AudioRecorderManager.getInstance()
32
- if (audioRecorderManager == null) {
33
- Log.e("RecordingActionReceiver", "AudioRecorderManager instance is null")
34
- isProcessingAction.set(false)
35
- return
36
- }
37
-
38
- val notificationPromise = object : Promise {
39
- override fun resolve(value: Any?) {
40
- Log.d("RecordingActionReceiver", "$action completed successfully")
41
- isProcessingAction.set(false)
42
- }
43
-
44
- override fun reject(code: String, message: String?, cause: Throwable?) {
45
- Log.e("RecordingActionReceiver", "$action failed: $message", cause)
46
- isProcessingAction.set(false)
47
- }
48
- }
49
-
50
- when (action) {
51
- ACTION_PAUSE_RECORDING -> audioRecorderManager.pauseRecording(notificationPromise)
52
- ACTION_RESUME_RECORDING -> audioRecorderManager.resumeRecording(notificationPromise)
53
- }
54
- } catch (e: Exception) {
55
- Log.e("RecordingActionReceiver", "Error processing $action", e)
56
- isProcessingAction.set(false)
57
- }
58
- }
59
- }
@@ -1,205 +0,0 @@
1
- package net.siteed.audiostream
2
-
3
- import android.media.AudioFormat
4
- import android.os.Build
5
- import java.io.File
6
-
7
- data class RecordingConfig(
8
- val sampleRate: Int = Constants.DEFAULT_SAMPLE_RATE,
9
- val channels: Int = 1,
10
- val encoding: String = "pcm_16bit",
11
- val keepAwake: Boolean = true,
12
- val interval: Long = Constants.DEFAULT_INTERVAL,
13
- val intervalAnalysis: Long = Constants.DEFAULT_INTERVAL_ANALYSIS,
14
- val enableProcessing: Boolean = false,
15
- val segmentDurationMs: Int = 100,
16
- val showNotification: Boolean = false,
17
- val showWaveformInNotification: Boolean = false,
18
- val notification: NotificationConfig = NotificationConfig(),
19
- val features: Map<String, Boolean> = emptyMap(),
20
- val enableCompressedOutput: Boolean = false,
21
- val compressedFormat: String = "opus",
22
- val compressedBitRate: Int = 24000,
23
- val autoResumeAfterInterruption: Boolean = false,
24
- val outputDirectory: String? = null,
25
- val filename: String? = null,
26
- ) {
27
- companion object {
28
- fun fromMap(options: Map<String, Any?>?): Result<Pair<RecordingConfig, AudioFormatInfo>> {
29
- if (options == null) {
30
- val defaultConfig = RecordingConfig()
31
- val defaultFormat = AudioFormatInfo(
32
- format = AudioFormat.ENCODING_PCM_16BIT,
33
- mimeType = "audio/wav",
34
- fileExtension = "wav"
35
- )
36
- return Result.success(Pair(defaultConfig, defaultFormat))
37
- }
38
-
39
- // Extract features using type-safe helper
40
- val features = options.getTypedMap<Boolean>("features") { it is Boolean }
41
-
42
- // Parse notification config using type-safe helper
43
- val notificationMap = options.getTypedMap<Any?>("notification") { true }
44
- val notificationConfig = NotificationConfig.fromMap(notificationMap)
45
-
46
- // Parse compression config
47
- val compressionMap = options.getTypedMap<Any?>("compression") { true }
48
- val enableCompressedOutput = compressionMap["enabled"] as? Boolean ?: false
49
- val compressedFormat = (compressionMap["format"] as? String)?.lowercase() ?: "aac"
50
- val compressedBitRate = (compressionMap["bitrate"] as? Number)?.toInt() ?: 128000
51
-
52
- // Validate bitrate if compression is enabled
53
- if (enableCompressedOutput) {
54
- when {
55
- compressedBitRate < 8000 -> return Result.failure(
56
- IllegalArgumentException("Bitrate must be at least 8000 bps")
57
- )
58
- compressedBitRate > 960000 -> return Result.failure(
59
- IllegalArgumentException("Bitrate cannot exceed 960000 bps")
60
- )
61
- }
62
- }
63
-
64
- // Only validate directory if it's provided
65
- val outputDirectory = options["outputDirectory"] as? String
66
- if (outputDirectory != null) {
67
- // Clean up the directory path by removing file:// protocol and normalizing
68
- val cleanDirectory = outputDirectory
69
- .replace(Regex("^file://"), "")
70
- .trim('/')
71
- .replace("//", "/")
72
-
73
- val directory = File(cleanDirectory)
74
- if (!directory.exists()) {
75
- return Result.failure(IllegalArgumentException("Directory does not exist: $cleanDirectory"))
76
- }
77
- if (!directory.isDirectory) {
78
- return Result.failure(IllegalArgumentException("Path is not a directory: $cleanDirectory"))
79
- }
80
- if (!directory.canWrite()) {
81
- return Result.failure(IllegalArgumentException("Directory is not writable: $cleanDirectory"))
82
- }
83
- }
84
-
85
- // Initialize the recording configuration with cleaned directory path
86
- val tempRecordingConfig = RecordingConfig(
87
- sampleRate = options.getNumberOrDefault("sampleRate", Constants.DEFAULT_SAMPLE_RATE),
88
- channels = options.getNumberOrDefault("channels", 1),
89
- encoding = options.getStringOrDefault("encoding", "pcm_16bit"),
90
- keepAwake = options.getBooleanOrDefault("keepAwake", true),
91
- interval = options.getNumberOrDefault("interval", Constants.DEFAULT_INTERVAL),
92
- intervalAnalysis = options.getNumberOrDefault("intervalAnalysis", Constants.DEFAULT_INTERVAL_ANALYSIS),
93
- enableProcessing = options.getBooleanOrDefault("enableProcessing", false),
94
- segmentDurationMs = options.getNumberOrDefault("segmentDurationMs", 100),
95
- showNotification = options.getBooleanOrDefault("showNotification", false),
96
- showWaveformInNotification = options.getBooleanOrDefault("showWaveformInNotification", false),
97
- notification = notificationConfig,
98
- features = features,
99
- enableCompressedOutput = enableCompressedOutput,
100
- compressedFormat = compressedFormat,
101
- compressedBitRate = compressedBitRate,
102
- autoResumeAfterInterruption = options.getBooleanOrDefault("autoResumeAfterInterruption", false),
103
- outputDirectory = outputDirectory?.let {
104
- it.replace(Regex("^file://"), "")
105
- .trim('/')
106
- .replace("//", "/")
107
- },
108
- filename = options["filename"] as? String
109
- )
110
-
111
- // Validate sample rate and channels
112
- if (tempRecordingConfig.sampleRate !in listOf(16000, 44100, 48000)) {
113
- return Result.failure(
114
- IllegalArgumentException("Sample rate must be one of 16000, 44100, or 48000 Hz")
115
- )
116
- }
117
- if (tempRecordingConfig.channels !in 1..2) {
118
- return Result.failure(
119
- IllegalArgumentException("Channels must be either 1 (Mono) or 2 (Stereo)")
120
- )
121
- }
122
-
123
- // Set encoding and file extension
124
- val audioFormatInfo = when (tempRecordingConfig.encoding) {
125
- "pcm_8bit" -> AudioFormatInfo(
126
- format = AudioFormat.ENCODING_PCM_8BIT,
127
- mimeType = "audio/wav",
128
- fileExtension = "wav"
129
- )
130
- "pcm_16bit" -> AudioFormatInfo(
131
- format = AudioFormat.ENCODING_PCM_16BIT,
132
- mimeType = "audio/wav",
133
- fileExtension = "wav"
134
- )
135
- "pcm_32bit" -> AudioFormatInfo(
136
- format = AudioFormat.ENCODING_PCM_FLOAT,
137
- mimeType = "audio/wav",
138
- fileExtension = "wav"
139
- )
140
- "opus" -> {
141
- if (Build.VERSION.SDK_INT < 29) {
142
- return Result.failure(
143
- IllegalArgumentException("Opus encoding not supported on this Android version.")
144
- )
145
- }
146
- AudioFormatInfo(
147
- format = if (Build.VERSION.SDK_INT >= 29) 20 else AudioFormat.ENCODING_DEFAULT, // 20 is ENCODING_OPUS
148
- mimeType = "audio/opus",
149
- fileExtension = "opus"
150
- )
151
- }
152
- "aac_lc" -> AudioFormatInfo(
153
- format = AudioFormat.ENCODING_AAC_LC,
154
- mimeType = "audio/aac",
155
- fileExtension = "aac"
156
- )
157
- else -> AudioFormatInfo(
158
- format = AudioFormat.ENCODING_DEFAULT,
159
- mimeType = "audio/wav",
160
- fileExtension = "wav"
161
- )
162
- }
163
-
164
- return Result.success(Pair(tempRecordingConfig, audioFormatInfo))
165
- }
166
- }
167
- }
168
-
169
- // Extension functions for type-safe map access
170
- private inline fun <reified T> Map<String, Any?>.getTypedMap(
171
- key: String,
172
- predicate: (Any?) -> Boolean
173
- ): Map<String, T> {
174
- return (this[key] as? Map<*, *>)?.mapNotNull { (k, v) ->
175
- if (k is String && predicate(v)) {
176
- k to (v as T)
177
- } else null
178
- }?.toMap() ?: emptyMap()
179
- }
180
-
181
- private fun Map<String, Any?>.getStringOrDefault(key: String, default: String): String {
182
- return this[key] as? String ?: default
183
- }
184
-
185
- private fun Map<String, Any?>.getBooleanOrDefault(key: String, default: Boolean): Boolean {
186
- return this[key] as? Boolean ?: default
187
- }
188
-
189
- private fun Map<String, Any?>.getNumberOrDefault(key: String, default: Int): Int {
190
- return (this[key] as? Number)?.toInt() ?: default
191
- }
192
-
193
- private fun Map<String, Any?>.getNumberOrDefault(key: String, default: Long): Long {
194
- return (this[key] as? Number)?.toLong() ?: default
195
- }
196
-
197
- private fun Map<String, Any?>.getNumberOrDefault(key: String, default: Double): Double {
198
- return (this[key] as? Number)?.toDouble() ?: default
199
- }
200
-
201
- data class AudioFormatInfo(
202
- val format: Int,
203
- val mimeType: String,
204
- val fileExtension: String
205
- )
@@ -1,19 +0,0 @@
1
- package net.siteed.audiostream
2
-
3
- /**
4
- * Configuration for the notification waveform visualization
5
- * @property color The color of the waveform (e.g., "#FFFFFF" for white)
6
- * @property opacity Opacity of the waveform (0.0-1.0)
7
- * @property strokeWidth Width of the waveform line (default: 1.5f)
8
- * @property style Drawing style: "stroke" for outline, "fill" for solid
9
- * @property mirror Whether to mirror the waveform (symmetrical display)
10
- * @property height Height of the waveform view in dp (default: 64)
11
- */
12
- data class WaveformConfig(
13
- val color: String = "#FFFFFF",
14
- val opacity: Float = 1.0f,
15
- val strokeWidth: Float = 1.5f,
16
- val style: String = "stroke", // "stroke" or "fill"
17
- val mirror: Boolean = true,
18
- val height: Int = 64
19
- )