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