@siteed/expo-audio-stream 2.1.0 → 2.2.1-beta.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 (189) hide show
  1. package/README.md +23 -260
  2. package/build/index.d.ts +11 -15
  3. package/build/index.js +54 -14
  4. package/build/src/index.d.ts +11 -0
  5. package/build/src/index.js +54 -0
  6. package/package.json +49 -110
  7. package/src/index.ts +18 -32
  8. package/CHANGELOG.md +0 -206
  9. package/android/build.gradle +0 -105
  10. package/android/src/main/AndroidManifest.xml +0 -27
  11. package/android/src/main/java/net/siteed/audiostream/AudioAnalysisData.kt +0 -166
  12. package/android/src/main/java/net/siteed/audiostream/AudioDataEncoder.kt +0 -9
  13. package/android/src/main/java/net/siteed/audiostream/AudioFileHandler.kt +0 -131
  14. package/android/src/main/java/net/siteed/audiostream/AudioFormatUtils.kt +0 -103
  15. package/android/src/main/java/net/siteed/audiostream/AudioNotificationsManager.kt +0 -435
  16. package/android/src/main/java/net/siteed/audiostream/AudioProcessor.kt +0 -2235
  17. package/android/src/main/java/net/siteed/audiostream/AudioRecorderManager.kt +0 -1437
  18. package/android/src/main/java/net/siteed/audiostream/AudioRecordingService.kt +0 -152
  19. package/android/src/main/java/net/siteed/audiostream/AudioTrimmer.kt +0 -1099
  20. package/android/src/main/java/net/siteed/audiostream/Constants.kt +0 -21
  21. package/android/src/main/java/net/siteed/audiostream/EventSender.kt +0 -7
  22. package/android/src/main/java/net/siteed/audiostream/ExpoAudioStreamModule.kt +0 -739
  23. package/android/src/main/java/net/siteed/audiostream/FFT.kt +0 -99
  24. package/android/src/main/java/net/siteed/audiostream/Features.kt +0 -98
  25. package/android/src/main/java/net/siteed/audiostream/NotificationConfig.kt +0 -70
  26. package/android/src/main/java/net/siteed/audiostream/PermissionUtils.kt +0 -59
  27. package/android/src/main/java/net/siteed/audiostream/RecordingActionReceiver.kt +0 -59
  28. package/android/src/main/java/net/siteed/audiostream/RecordingConfig.kt +0 -205
  29. package/android/src/main/java/net/siteed/audiostream/WaveformConfig.kt +0 -19
  30. package/android/src/main/java/net/siteed/audiostream/WaveformRenderer.kt +0 -159
  31. package/android/src/main/res/drawable/ic_default_action_icon.xml +0 -16
  32. package/android/src/main/res/drawable/ic_microphone.xml +0 -13
  33. package/android/src/main/res/drawable/ic_pause.xml +0 -10
  34. package/android/src/main/res/drawable/ic_play.xml +0 -10
  35. package/android/src/main/res/drawable/ic_stop.xml +0 -10
  36. package/android/src/main/res/layout/notification_recording.xml +0 -37
  37. package/android/src/main/test/java/net/siteed/audiostream/AudioProcessorTest.kt +0 -56
  38. package/app.plugin.js +0 -1
  39. package/build/AudioAnalysis/AudioAnalysis.types.d.ts +0 -179
  40. package/build/AudioAnalysis/AudioAnalysis.types.d.ts.map +0 -1
  41. package/build/AudioAnalysis/AudioAnalysis.types.js +0 -3
  42. package/build/AudioAnalysis/AudioAnalysis.types.js.map +0 -1
  43. package/build/AudioAnalysis/extractAudioAnalysis.d.ts +0 -68
  44. package/build/AudioAnalysis/extractAudioAnalysis.d.ts.map +0 -1
  45. package/build/AudioAnalysis/extractAudioAnalysis.js +0 -203
  46. package/build/AudioAnalysis/extractAudioAnalysis.js.map +0 -1
  47. package/build/AudioAnalysis/extractAudioData.d.ts +0 -3
  48. package/build/AudioAnalysis/extractAudioData.d.ts.map +0 -1
  49. package/build/AudioAnalysis/extractAudioData.js +0 -5
  50. package/build/AudioAnalysis/extractAudioData.js.map +0 -1
  51. package/build/AudioAnalysis/extractMelSpectrogram.d.ts +0 -14
  52. package/build/AudioAnalysis/extractMelSpectrogram.d.ts.map +0 -1
  53. package/build/AudioAnalysis/extractMelSpectrogram.js +0 -85
  54. package/build/AudioAnalysis/extractMelSpectrogram.js.map +0 -1
  55. package/build/AudioAnalysis/extractPreview.d.ts +0 -11
  56. package/build/AudioAnalysis/extractPreview.d.ts.map +0 -1
  57. package/build/AudioAnalysis/extractPreview.js +0 -25
  58. package/build/AudioAnalysis/extractPreview.js.map +0 -1
  59. package/build/AudioAnalysis/extractWaveform.d.ts +0 -8
  60. package/build/AudioAnalysis/extractWaveform.d.ts.map +0 -1
  61. package/build/AudioAnalysis/extractWaveform.js +0 -11
  62. package/build/AudioAnalysis/extractWaveform.js.map +0 -1
  63. package/build/AudioRecorder.provider.d.ts +0 -11
  64. package/build/AudioRecorder.provider.d.ts.map +0 -1
  65. package/build/AudioRecorder.provider.js +0 -37
  66. package/build/AudioRecorder.provider.js.map +0 -1
  67. package/build/ExpoAudioStream.native.d.ts +0 -3
  68. package/build/ExpoAudioStream.native.d.ts.map +0 -1
  69. package/build/ExpoAudioStream.native.js +0 -6
  70. package/build/ExpoAudioStream.native.js.map +0 -1
  71. package/build/ExpoAudioStream.types.d.ts +0 -532
  72. package/build/ExpoAudioStream.types.d.ts.map +0 -1
  73. package/build/ExpoAudioStream.types.js +0 -2
  74. package/build/ExpoAudioStream.types.js.map +0 -1
  75. package/build/ExpoAudioStream.web.d.ts +0 -59
  76. package/build/ExpoAudioStream.web.d.ts.map +0 -1
  77. package/build/ExpoAudioStream.web.js +0 -285
  78. package/build/ExpoAudioStream.web.js.map +0 -1
  79. package/build/ExpoAudioStreamModule.d.ts +0 -3
  80. package/build/ExpoAudioStreamModule.d.ts.map +0 -1
  81. package/build/ExpoAudioStreamModule.js +0 -693
  82. package/build/ExpoAudioStreamModule.js.map +0 -1
  83. package/build/WebRecorder.web.d.ts +0 -119
  84. package/build/WebRecorder.web.d.ts.map +0 -1
  85. package/build/WebRecorder.web.js +0 -436
  86. package/build/WebRecorder.web.js.map +0 -1
  87. package/build/constants.d.ts +0 -11
  88. package/build/constants.d.ts.map +0 -1
  89. package/build/constants.js +0 -14
  90. package/build/constants.js.map +0 -1
  91. package/build/events.d.ts +0 -26
  92. package/build/events.d.ts.map +0 -1
  93. package/build/events.js +0 -21
  94. package/build/events.js.map +0 -1
  95. package/build/index.d.ts.map +0 -1
  96. package/build/index.js.map +0 -1
  97. package/build/trimAudio.d.ts +0 -25
  98. package/build/trimAudio.d.ts.map +0 -1
  99. package/build/trimAudio.js +0 -67
  100. package/build/trimAudio.js.map +0 -1
  101. package/build/useAudioRecorder.d.ts +0 -21
  102. package/build/useAudioRecorder.d.ts.map +0 -1
  103. package/build/useAudioRecorder.js +0 -427
  104. package/build/useAudioRecorder.js.map +0 -1
  105. package/build/utils/BlobFix.d.ts +0 -9
  106. package/build/utils/BlobFix.d.ts.map +0 -1
  107. package/build/utils/BlobFix.js +0 -498
  108. package/build/utils/BlobFix.js.map +0 -1
  109. package/build/utils/audioProcessing.d.ts +0 -24
  110. package/build/utils/audioProcessing.d.ts.map +0 -1
  111. package/build/utils/audioProcessing.js +0 -133
  112. package/build/utils/audioProcessing.js.map +0 -1
  113. package/build/utils/concatenateBuffers.d.ts +0 -8
  114. package/build/utils/concatenateBuffers.d.ts.map +0 -1
  115. package/build/utils/concatenateBuffers.js +0 -21
  116. package/build/utils/concatenateBuffers.js.map +0 -1
  117. package/build/utils/convertPCMToFloat32.d.ts +0 -13
  118. package/build/utils/convertPCMToFloat32.d.ts.map +0 -1
  119. package/build/utils/convertPCMToFloat32.js +0 -120
  120. package/build/utils/convertPCMToFloat32.js.map +0 -1
  121. package/build/utils/encodingToBitDepth.d.ts +0 -5
  122. package/build/utils/encodingToBitDepth.d.ts.map +0 -1
  123. package/build/utils/encodingToBitDepth.js +0 -13
  124. package/build/utils/encodingToBitDepth.js.map +0 -1
  125. package/build/utils/getWavFileInfo.d.ts +0 -26
  126. package/build/utils/getWavFileInfo.d.ts.map +0 -1
  127. package/build/utils/getWavFileInfo.js +0 -92
  128. package/build/utils/getWavFileInfo.js.map +0 -1
  129. package/build/utils/writeWavHeader.d.ts +0 -49
  130. package/build/utils/writeWavHeader.d.ts.map +0 -1
  131. package/build/utils/writeWavHeader.js +0 -91
  132. package/build/utils/writeWavHeader.js.map +0 -1
  133. package/build/workers/InlineFeaturesExtractor.web.d.ts +0 -2
  134. package/build/workers/InlineFeaturesExtractor.web.d.ts.map +0 -1
  135. package/build/workers/InlineFeaturesExtractor.web.js +0 -828
  136. package/build/workers/InlineFeaturesExtractor.web.js.map +0 -1
  137. package/build/workers/inlineAudioWebWorker.web.d.ts +0 -2
  138. package/build/workers/inlineAudioWebWorker.web.d.ts.map +0 -1
  139. package/build/workers/inlineAudioWebWorker.web.js +0 -157
  140. package/build/workers/inlineAudioWebWorker.web.js.map +0 -1
  141. package/expo-module.config.json +0 -9
  142. package/ios/AudioAnalysisData.swift +0 -74
  143. package/ios/AudioNotificationManager.swift +0 -135
  144. package/ios/AudioProcessingHelpers.swift +0 -743
  145. package/ios/AudioProcessor.swift +0 -1313
  146. package/ios/AudioStreamError.swift +0 -7
  147. package/ios/AudioStreamManager.swift +0 -1708
  148. package/ios/AudioStreamManagerDelegate.swift +0 -16
  149. package/ios/DataPoint.swift +0 -54
  150. package/ios/DecodingConfig.swift +0 -47
  151. package/ios/ExpoAudioStream.podspec +0 -27
  152. package/ios/ExpoAudioStreamModule.swift +0 -805
  153. package/ios/FFT.swift +0 -62
  154. package/ios/Features.swift +0 -95
  155. package/ios/Logger.swift +0 -7
  156. package/ios/NotificationExtension.swift +0 -15
  157. package/ios/RecordingResult.swift +0 -22
  158. package/ios/RecordingSettings.swift +0 -265
  159. package/ios/WaveformExtractor.swift +0 -105
  160. package/plugin/build/index.d.ts +0 -21
  161. package/plugin/build/index.js +0 -191
  162. package/plugin/src/index.ts +0 -278
  163. package/plugin/tsconfig.json +0 -10
  164. package/plugin/tsconfig.tsbuildinfo +0 -1
  165. package/src/AudioAnalysis/AudioAnalysis.types.ts +0 -202
  166. package/src/AudioAnalysis/extractAudioAnalysis.ts +0 -333
  167. package/src/AudioAnalysis/extractAudioData.ts +0 -6
  168. package/src/AudioAnalysis/extractMelSpectrogram.ts +0 -144
  169. package/src/AudioAnalysis/extractPreview.ts +0 -34
  170. package/src/AudioAnalysis/extractWaveform.ts +0 -22
  171. package/src/AudioRecorder.provider.tsx +0 -54
  172. package/src/ExpoAudioStream.native.ts +0 -6
  173. package/src/ExpoAudioStream.types.ts +0 -641
  174. package/src/ExpoAudioStream.web.ts +0 -359
  175. package/src/ExpoAudioStreamModule.ts +0 -967
  176. package/src/WebRecorder.web.ts +0 -580
  177. package/src/constants.ts +0 -18
  178. package/src/events.ts +0 -60
  179. package/src/trimAudio.ts +0 -90
  180. package/src/useAudioRecorder.tsx +0 -620
  181. package/src/utils/BlobFix.ts +0 -559
  182. package/src/utils/audioProcessing.ts +0 -205
  183. package/src/utils/concatenateBuffers.ts +0 -24
  184. package/src/utils/convertPCMToFloat32.ts +0 -170
  185. package/src/utils/encodingToBitDepth.ts +0 -18
  186. package/src/utils/getWavFileInfo.ts +0 -132
  187. package/src/utils/writeWavHeader.ts +0 -114
  188. package/src/workers/InlineFeaturesExtractor.web.tsx +0 -827
  189. 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
- )