@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,435 +0,0 @@
1
- package net.siteed.audiostream
2
-
3
- import android.app.Notification
4
- import android.app.NotificationChannel
5
- import android.app.NotificationManager
6
- import android.app.PendingIntent
7
- import android.content.Context
8
- import android.content.Intent
9
- import android.graphics.Color
10
- import android.os.Build
11
- import android.os.Handler
12
- import android.os.Looper
13
- import android.os.SystemClock
14
- import android.util.Log
15
- import android.view.View
16
- import android.widget.RemoteViews
17
- import androidx.core.app.NotificationCompat
18
- import java.lang.ref.WeakReference
19
- import java.util.Locale
20
- import java.util.concurrent.atomic.AtomicBoolean
21
- import java.util.Objects
22
-
23
- class AudioNotificationManager private constructor(context: Context) {
24
- private val contextRef = WeakReference(context.applicationContext)
25
- private val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
26
- private val mainHandler = Handler(Looper.getMainLooper())
27
- private val isUpdating = AtomicBoolean(false)
28
- private val isPaused = AtomicBoolean(false)
29
-
30
- private var lastRemoteViewsUpdate = 0L
31
- private var consecutiveUpdateFailures = 0
32
- private var lastSuccessfulUpdate: Long = 0
33
- private val maxUpdateFailures = 3
34
- private val remoteViewsRefreshInterval = 10000L // Refresh RemoteViews every 10 seconds
35
-
36
- private lateinit var notificationBuilder: NotificationCompat.Builder
37
- private lateinit var remoteViews: RemoteViews
38
- private lateinit var recordingConfig: RecordingConfig
39
- private var recordingStartTime: Long = 0
40
- private var pausedDuration: Long = 0
41
- private var lastPauseTime: Long = 0
42
- private var lastWaveformUpdate: Long = 0
43
- private val waveformRenderer = WaveformRenderer()
44
- private var lastNotificationHash: Int? = null
45
-
46
- companion object {
47
- private const val WAVEFORM_UPDATE_INTERVAL = 100L
48
- private const val UPDATE_INTERVAL = 1000L
49
-
50
- @Volatile
51
- private var instance: AudioNotificationManager? = null
52
-
53
- fun getInstance(context: Context): AudioNotificationManager {
54
- return instance ?: synchronized(this) {
55
- instance ?: AudioNotificationManager(context).also { instance = it }
56
- }
57
- }
58
- }
59
-
60
- fun initialize(config: RecordingConfig) {
61
- recordingConfig = config
62
- createNotificationChannel()
63
- initializeNotification()
64
- }
65
-
66
- private fun createNotificationChannel() {
67
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
68
- val channel = NotificationChannel(
69
- recordingConfig.notification.channelId,
70
- recordingConfig.notification.channelName,
71
- NotificationManager.IMPORTANCE_LOW
72
- ).apply {
73
- description = recordingConfig.notification.channelDescription
74
- enableLights(false)
75
- enableVibration(false)
76
- setSound(null, null)
77
- setShowBadge(false)
78
- vibrationPattern = null
79
- }
80
-
81
- notificationManager.createNotificationChannel(channel)
82
- }
83
- }
84
-
85
- private fun initializeNotification() {
86
- val context = contextRef.get() ?: return
87
- try {
88
- remoteViews = RemoteViews(context.packageName, R.layout.notification_recording)
89
- remoteViews.apply {
90
- setTextViewText(R.id.notification_title, recordingConfig.notification.title)
91
- setTextViewText(R.id.notification_text, recordingConfig.notification.text)
92
- setTextViewText(R.id.notification_duration, formatDuration(0))
93
- setViewVisibility(
94
- R.id.notification_waveform,
95
- if (recordingConfig.showWaveformInNotification &&
96
- recordingConfig.notification.waveform != null) View.VISIBLE else View.GONE
97
- )
98
- }
99
-
100
- buildNotification(context)
101
- } catch (e: Exception) {
102
- Log.e(Constants.TAG, "Failed to initialize notification", e)
103
- }
104
- }
105
-
106
- private fun buildNotification(context: Context) {
107
- val iconResId = recordingConfig.notification.icon?.let {
108
- getResourceIdByName(it)
109
- } ?: R.drawable.ic_microphone
110
-
111
- val pendingIntent = PendingIntent.getActivity(
112
- context,
113
- 0,
114
- context.packageManager.getLaunchIntentForPackage(context.packageName),
115
- PendingIntent.FLAG_IMMUTABLE
116
- )
117
-
118
- // Configure notification builder with settings optimized for recording service
119
- // and wearable device compatibility
120
- notificationBuilder = NotificationCompat.Builder(context, recordingConfig.notification.channelId)
121
- .setSmallIcon(iconResId)
122
- .setContentIntent(pendingIntent)
123
- .setOngoing(true) // Notification cannot be dismissed by user
124
- .setPriority(NotificationCompat.PRIORITY_HIGH)
125
- .setCategory(NotificationCompat.CATEGORY_SERVICE)
126
- .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
127
- .setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
128
- .setCustomContentView(remoteViews)
129
- .setCustomBigContentView(remoteViews)
130
- .setStyle(NotificationCompat.DecoratedCustomViewStyle())
131
- // Prevent repeated alerts and vibrations
132
- .setOnlyAlertOnce(true) // Only alert on first notification
133
- .setVibrate(null) // Disable vibration
134
- .setDefaults(0) // Clear all default notification behaviors
135
- .setLocalOnly(true) // Prevent notification from appearing on wearable devices
136
-
137
- addNotificationActions(context)
138
- }
139
-
140
- private fun addNotificationActions(context: Context) {
141
- // Clear existing actions first
142
- notificationBuilder.clearActions()
143
-
144
- // Create pause action
145
- val pauseIntent = Intent(context, RecordingActionReceiver::class.java).apply {
146
- action = RecordingActionReceiver.ACTION_PAUSE_RECORDING
147
- }
148
- val pausePendingIntent = PendingIntent.getBroadcast(
149
- context,
150
- 0,
151
- pauseIntent,
152
- PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
153
- )
154
-
155
- // Create resume action
156
- val resumeIntent = Intent(context, RecordingActionReceiver::class.java).apply {
157
- action = RecordingActionReceiver.ACTION_RESUME_RECORDING
158
- }
159
- val resumePendingIntent = PendingIntent.getBroadcast(
160
- context,
161
- 1,
162
- resumeIntent,
163
- PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
164
- )
165
-
166
- // Add only one pause/resume action based on current state
167
- if (isPaused.get()) {
168
- notificationBuilder.addAction(
169
- R.drawable.ic_play,
170
- "Resume",
171
- resumePendingIntent
172
- )
173
- } else {
174
- notificationBuilder.addAction(
175
- R.drawable.ic_pause,
176
- "Pause",
177
- pausePendingIntent
178
- )
179
- }
180
-
181
- // Add configured custom actions (only if they don't already exist)
182
- val existingActions = mutableSetOf<String>()
183
- recordingConfig.notification.actions.forEach { action ->
184
- if (existingActions.add(action.intentAction)) { // Only add if action is unique
185
- val intent = Intent(context, RecordingActionReceiver::class.java).apply {
186
- this.action = action.intentAction
187
- }
188
- val pendingIntent = PendingIntent.getBroadcast(
189
- context,
190
- action.intentAction.hashCode(),
191
- intent,
192
- PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
193
- )
194
- val actionIconResId = action.icon?.let { getResourceIdByName(it) }
195
- ?: R.drawable.ic_default_action_icon
196
- notificationBuilder.addAction(actionIconResId, action.title, pendingIntent)
197
- }
198
- }
199
- }
200
-
201
- private fun updateNotificationActions() {
202
- val context = contextRef.get() ?: return
203
- try {
204
- // Clear existing actions
205
- notificationBuilder.clearActions()
206
- // Add updated actions
207
- addNotificationActions(context)
208
-
209
- // Update the notification
210
- val updatedNotification = notificationBuilder
211
- .setCustomContentView(remoteViews)
212
- .setCustomBigContentView(remoteViews)
213
- .build()
214
-
215
- notificationManager.notify(recordingConfig.notification.notificationId, updatedNotification)
216
- } catch (e: Exception) {
217
- Log.e(Constants.TAG, "Failed to update notification actions", e)
218
- }
219
- }
220
-
221
- fun startUpdates(startTime: Long) {
222
- recordingStartTime = startTime
223
- pausedDuration = 0
224
- isPaused.set(false)
225
- updateNotificationActions() // Update actions when starting
226
- if (!isUpdating.getAndSet(true)) {
227
- scheduleUpdate()
228
- }
229
- }
230
-
231
- private fun scheduleUpdate() {
232
- mainHandler.postDelayed({
233
- if (isUpdating.get() && !isPaused.get()) {
234
- updateNotification()
235
- scheduleUpdate()
236
- }
237
- }, UPDATE_INTERVAL)
238
- }
239
-
240
- fun updateNotification(audioData: FloatArray? = null) {
241
- val context = contextRef.get() ?: return
242
-
243
- try {
244
- val currentTime = SystemClock.elapsedRealtime()
245
-
246
- // Calculate current notification state
247
- val recordingDuration = if (isPaused.get()) {
248
- lastPauseTime - recordingStartTime - pausedDuration
249
- } else {
250
- System.currentTimeMillis() - recordingStartTime - pausedDuration
251
- }
252
-
253
- // Create a hash of the current notification state
254
- val currentHash = Objects.hash(
255
- recordingConfig.notification.title,
256
- recordingConfig.notification.text,
257
- formatDuration(recordingDuration),
258
- isPaused.get()
259
- )
260
-
261
- val needsRemoteViewsRefresh = currentTime - lastRemoteViewsUpdate >= remoteViewsRefreshInterval ||
262
- consecutiveUpdateFailures >= maxUpdateFailures
263
-
264
- // Only update if content changed or refresh needed
265
- if (currentHash == lastNotificationHash && !needsRemoteViewsRefresh) {
266
- // Update waveform only if needed
267
- if (shouldUpdateWaveform(audioData, currentTime)) {
268
- updateWaveformOnly(audioData)
269
- }
270
- return
271
- }
272
-
273
- lastNotificationHash = currentHash
274
-
275
- // Only recreate RemoteViews periodically or after failures
276
- if (needsRemoteViewsRefresh) {
277
- remoteViews = RemoteViews(context.packageName, R.layout.notification_recording)
278
- lastRemoteViewsUpdate = currentTime
279
- consecutiveUpdateFailures = 0
280
- }
281
-
282
- // Update RemoteViews content
283
- remoteViews.apply {
284
- setTextViewText(R.id.notification_title, recordingConfig.notification.title)
285
- setTextViewText(R.id.notification_text, recordingConfig.notification.text)
286
- setTextViewText(R.id.notification_duration, formatDuration(recordingDuration))
287
-
288
- // Update waveform if needed
289
- if (recordingConfig.showWaveformInNotification &&
290
- audioData != null &&
291
- audioData.isNotEmpty() &&
292
- currentTime - lastWaveformUpdate >= WAVEFORM_UPDATE_INTERVAL
293
- ) {
294
- try {
295
- val waveformBitmap = waveformRenderer.generateWaveform(audioData, recordingConfig.notification.waveform)
296
- setImageViewBitmap(R.id.notification_waveform, waveformBitmap)
297
- lastWaveformUpdate = currentTime
298
- } catch (e: Exception) {
299
- Log.e(Constants.TAG, "Error generating waveform", e)
300
- }
301
- }
302
- }
303
-
304
- // Only rebuild notification if RemoteViews was refreshed
305
- if (needsRemoteViewsRefresh) {
306
- notificationBuilder
307
- .setCustomContentView(remoteViews)
308
- .setCustomBigContentView(remoteViews)
309
- .setOnlyAlertOnce(true)
310
- .setOngoing(true)
311
- addNotificationActions(context)
312
- }
313
-
314
- // Update the notification with disabled alerts
315
- notificationManager.notify(
316
- recordingConfig.notification.notificationId,
317
- notificationBuilder
318
- .setOnlyAlertOnce(true)
319
- .setVibrate(null)
320
- .setDefaults(0)
321
- .build()
322
- )
323
-
324
- lastSuccessfulUpdate = currentTime
325
- consecutiveUpdateFailures = 0
326
-
327
- } catch (e: Exception) {
328
- Log.e(Constants.TAG, "Error updating notification", e)
329
- consecutiveUpdateFailures++
330
-
331
- if (consecutiveUpdateFailures >= maxUpdateFailures) {
332
- reinitializeNotification()
333
- }
334
- }
335
- }
336
-
337
- private fun shouldUpdateWaveform(audioData: FloatArray?, currentTime: Long): Boolean {
338
- return recordingConfig.showWaveformInNotification &&
339
- audioData != null &&
340
- audioData.isNotEmpty() &&
341
- currentTime - lastWaveformUpdate >= WAVEFORM_UPDATE_INTERVAL
342
- }
343
-
344
- private fun updateWaveformOnly(audioData: FloatArray?) {
345
- if (audioData == null) return
346
-
347
- try {
348
- val waveformBitmap = waveformRenderer.generateWaveform(audioData, recordingConfig.notification.waveform)
349
- remoteViews.setImageViewBitmap(R.id.notification_waveform, waveformBitmap)
350
- lastWaveformUpdate = SystemClock.elapsedRealtime()
351
-
352
- notificationManager.notify(
353
- recordingConfig.notification.notificationId,
354
- notificationBuilder
355
- .setCustomContentView(remoteViews)
356
- .setCustomBigContentView(remoteViews)
357
- .setOnlyAlertOnce(true)
358
- .setVibrate(null)
359
- .setDefaults(0)
360
- .build()
361
- )
362
- } catch (e: Exception) {
363
- Log.e(Constants.TAG, "Error updating waveform", e)
364
- }
365
- }
366
-
367
- private fun reinitializeNotification() {
368
- try {
369
- val context = contextRef.get() ?: return
370
-
371
- // Force a RemoteViews refresh
372
- remoteViews = RemoteViews(context.packageName, R.layout.notification_recording)
373
- lastRemoteViewsUpdate = SystemClock.elapsedRealtime()
374
-
375
- buildNotification(context)
376
-
377
- notificationManager.notify(
378
- recordingConfig.notification.notificationId,
379
- notificationBuilder.build()
380
- )
381
-
382
- consecutiveUpdateFailures = 0
383
- Log.d(Constants.TAG, "Successfully reinitialized notification")
384
- } catch (e: Exception) {
385
- Log.e(Constants.TAG, "Failed to reinitialize notification", e)
386
- }
387
- }
388
-
389
- fun getNotification(): Notification = notificationBuilder.build()
390
-
391
- fun pauseUpdates() {
392
- isPaused.set(true)
393
- lastPauseTime = System.currentTimeMillis()
394
- updateNotificationActions() // Update actions when pausing
395
- }
396
-
397
- fun resumeUpdates() {
398
- pausedDuration += System.currentTimeMillis() - lastPauseTime
399
- isPaused.set(false)
400
- updateNotificationActions() // Update actions when resuming
401
- scheduleUpdate()
402
- }
403
-
404
- fun stopUpdates() {
405
- isUpdating.set(false)
406
- mainHandler.removeCallbacksAndMessages(null)
407
- notificationManager.cancel(recordingConfig.notification.notificationId)
408
- cleanup()
409
- }
410
-
411
- private fun cleanup() {
412
- recordingStartTime = 0
413
- pausedDuration = 0
414
- lastPauseTime = 0
415
- lastWaveformUpdate = 0
416
- lastSuccessfulUpdate = 0
417
- lastRemoteViewsUpdate = 0
418
- consecutiveUpdateFailures = 0
419
- isPaused.set(false)
420
- isUpdating.set(false)
421
- }
422
-
423
- private fun getResourceIdByName(resourceName: String): Int {
424
- val context = contextRef.get() ?: return R.drawable.ic_default_action_icon
425
- return context.resources.getIdentifier(resourceName, "drawable", context.packageName)
426
- .takeIf { it != 0 } ?: R.drawable.ic_default_action_icon
427
- }
428
-
429
- private fun formatDuration(durationMs: Long): String {
430
- val totalSeconds = durationMs / 1000
431
- val minutes = totalSeconds / 60
432
- val seconds = totalSeconds % 60
433
- return String.format(Locale.US, "%02d:%02d", minutes, seconds)
434
- }
435
- }