@siteed/expo-audio-stream 1.9.2 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -8,24 +8,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8
8
  ## [Unreleased]
9
9
 
10
10
 
11
+ ## [1.10.0] - 2025-01-14
12
+ - add support for pausing and resuming compressed recordings ([bc3f629](https://github.com/deeeed/expo-audio-stream/commit/bc3f6295d060396325e0f008ff00b3be9c8722cd))
13
+ - optimize notification channel settings ([daa075e](https://github.com/deeeed/expo-audio-stream/commit/daa075e668f8faf0b8d2849e18c37384bdd293b8))
14
+
11
15
  ## [1.9.2] - 2025-01-12
12
16
  - ios bitrate verification to prevent invalid values ([035a180](https://github.com/deeeed/expo-audio-stream/commit/035a1800833264edcc59724aaa8a2e12d5c78dc2))
13
17
 
18
+
14
19
  ## [1.9.1] - 2025-01-12
15
20
  - ios potentially missing compressed file info ([88a628c](https://github.com/deeeed/expo-audio-stream/commit/88a628c35f2bfd626a2a5de1eb6950efd814619d))
16
21
 
17
22
 
23
+
18
24
  ## [1.9.0] - 2025-01-11
19
25
  - feat(web-audio): optimize memory usage and streaming performance for web audio recording (#75) ([7b93e12](https://github.com/deeeed/expo-audio-stream/commit/7b93e12aae4bc0599b06b48ca34a60f65587fc75))
20
26
 
21
27
 
22
28
 
29
+
23
30
  ## [1.8.0] - 2025-01-10
24
31
  - feat(audio): implement audio compression support ([ff4e060](https://github.com/deeeed/expo-audio-stream/commit/ff4e060fef1061804c1cc0126d4344d2d50daa9a))
25
32
 
26
33
 
27
34
 
28
35
 
36
+
29
37
  ## [1.7.2] - 2025-01-07
30
38
  - fix(audio-stream): correct WAV header handling in web audio recording ([9ba7de5](https://github.com/deeeed/expo-audio-stream/commit/9ba7de5b96ca4cc937dea261c80d3fda9c99e8f4))
31
39
 
@@ -33,6 +41,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
33
41
 
34
42
 
35
43
 
44
+
36
45
  ## [1.7.1] - 2025-01-07
37
46
  - update notification to avoid triggering new alerts (#71) ([32dcfc5](https://github.com/deeeed/expo-audio-stream/commit/32dcfc55daf3236babefc17016f329c177d466fd))
38
47
 
@@ -41,6 +50,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
41
50
 
42
51
 
43
52
 
53
+
44
54
  ## [1.7.0] - 2025-01-05
45
55
  - feat(playground): enhance app configuration and build setup for production deployment (#58) ([929d443](https://github.com/deeeed/expo-audio-stream/commit/929d443145378b1430d215db5c00b13758420e2b))
46
56
  - chore(expo-audio-stream): release @siteed/expo-audio-stream@1.6.1 ([084e8ad](https://github.com/deeeed/expo-audio-stream/commit/084e8adb91da7874c9e608b55d9c7b2ffd7a8327))
@@ -55,6 +65,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
55
65
 
56
66
 
57
67
 
68
+
58
69
  ## [1.6.1] - 2024-12-11
59
70
  - chore(expo-audio-stream): remove git commit step from publish script ([4a772ce](https://github.com/deeeed/expo-audio-stream/commit/4a772ce93bb7405d9b8e981f46bdf8941a71ecfe))
60
71
  - chore: more publishing automation ([3693021](https://github.com/deeeed/expo-audio-stream/commit/369302107f9dca9dddd8ae68e6214481a39976ac))
@@ -73,6 +84,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
73
84
 
74
85
 
75
86
 
87
+
76
88
  ## [1.5.0] - 2024-12-10
77
89
  - UNPUBLISHED because of a bug in the build system
78
90
 
@@ -84,6 +96,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
84
96
 
85
97
 
86
98
 
99
+
87
100
  ## [1.4.0] - 2024-12-05
88
101
  - chore: remove unusded dependencies ([ad81dd5](https://github.com/deeeed/expo-audio-stream/commit/ad81dd560c93dd1d04995a323a4ae72d4de20f3e))
89
102
 
@@ -95,6 +108,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
95
108
 
96
109
 
97
110
 
111
+
98
112
  ## [1.3.1] - 2024-12-05
99
113
  - feat(web): implement throttling and optimize event processing (#49) ([da28765](https://github.com/deeeed/expo-audio-stream/commit/da2876524c2c9d6e0a980fde40a0197b929d8a7f))
100
114
 
@@ -106,6 +120,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
106
120
 
107
121
 
108
122
 
123
+
109
124
  ## [1.3.0] - 2024-11-28
110
125
  ### Added
111
126
  - refactor(permissions): standardize permission status response structure across platforms (#44) ([7c9c800](https://github.com/deeeed/expo-audio-stream/commit/7c9c800d83b7cea3516643371484d5e1f3b99e4c))
@@ -122,6 +137,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
122
137
 
123
138
 
124
139
 
140
+
125
141
  ## [1.2.5] - 2024-11-12
126
142
  ### Added
127
143
  - docs(license): add MIT license to all packages (6 files changed)
@@ -135,6 +151,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
135
151
 
136
152
 
137
153
 
154
+
138
155
  ## [1.2.4] - 2024-11-05
139
156
  ### Changed
140
157
  - Android minimum audio interval set to 10ms.
@@ -151,6 +168,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
151
168
 
152
169
 
153
170
 
171
+
154
172
  ## [1.2.0] - 2024-10-24
155
173
  ### Added
156
174
  - Feature: Keep device awake during recording with `keepAwake` option
@@ -167,6 +185,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
167
185
 
168
186
 
169
187
 
188
+
170
189
  ## [1.1.17] - 2024-10-21
171
190
  ### Added
172
191
  - Support bluetooth headset on ios
@@ -180,6 +199,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
180
199
 
181
200
 
182
201
 
202
+
183
203
  ## [1.0.0] - 2024-04-01
184
204
  ### Added
185
205
  - Initial release of @siteed/expo-audio-stream.
@@ -190,7 +210,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
190
210
  - Feature: Audio features extraction during recording.
191
211
  - Feature: Consistent WAV PCM recording format across all platforms.
192
212
 
193
- [unreleased]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-stream@1.9.2...HEAD
213
+ [unreleased]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-stream@1.10.0...HEAD
214
+ [1.10.0]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-stream@1.9.2...@siteed/expo-audio-stream@1.10.0
194
215
  [1.9.2]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-stream@1.9.1...@siteed/expo-audio-stream@1.9.2
195
216
  [1.9.1]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-stream@1.9.0...@siteed/expo-audio-stream@1.9.1
196
217
  [1.9.0]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-stream@1.8.0...@siteed/expo-audio-stream@1.9.0
@@ -18,6 +18,7 @@ import androidx.core.app.NotificationCompat
18
18
  import java.lang.ref.WeakReference
19
19
  import java.util.Locale
20
20
  import java.util.concurrent.atomic.AtomicBoolean
21
+ import java.util.Objects
21
22
 
22
23
  class AudioNotificationManager private constructor(context: Context) {
23
24
  private val contextRef = WeakReference(context.applicationContext)
@@ -40,6 +41,7 @@ class AudioNotificationManager private constructor(context: Context) {
40
41
  private var lastPauseTime: Long = 0
41
42
  private var lastWaveformUpdate: Long = 0
42
43
  private val waveformRenderer = WaveformRenderer()
44
+ private var lastNotificationHash: Int? = null
43
45
 
44
46
  companion object {
45
47
  private const val WAVEFORM_UPDATE_INTERVAL = 100L
@@ -66,21 +68,16 @@ class AudioNotificationManager private constructor(context: Context) {
66
68
  val channel = NotificationChannel(
67
69
  recordingConfig.notification.channelId,
68
70
  recordingConfig.notification.channelName,
69
- NotificationManager.IMPORTANCE_LOW // Lower importance means no sound by default
71
+ NotificationManager.IMPORTANCE_LOW
70
72
  ).apply {
71
73
  description = recordingConfig.notification.channelDescription
72
- enableLights(true)
73
- lightColor = Color.parseColor(recordingConfig.notification.lightColor)
74
- enableVibration(true)
75
- setShowBadge(true)
74
+ enableLights(false)
75
+ enableVibration(false)
76
+ setSound(null, null)
77
+ setShowBadge(false)
78
+ vibrationPattern = null
76
79
  }
77
80
 
78
- // Set description, disable lights, vibration, and sound.
79
- channel.enableLights(false);
80
- channel.enableVibration(false);
81
- channel.setSound(null, null);
82
- channel.setShowBadge(false);
83
-
84
81
  notificationManager.createNotificationChannel(channel)
85
82
  }
86
83
  }
@@ -134,6 +131,9 @@ class AudioNotificationManager private constructor(context: Context) {
134
131
  }
135
132
 
136
133
  private fun addNotificationActions(context: Context) {
134
+ // Clear existing actions first
135
+ notificationBuilder.clearActions()
136
+
137
137
  // Create pause action
138
138
  val pauseIntent = Intent(context, RecordingActionReceiver::class.java).apply {
139
139
  action = RecordingActionReceiver.ACTION_PAUSE_RECORDING
@@ -156,7 +156,7 @@ class AudioNotificationManager private constructor(context: Context) {
156
156
  PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
157
157
  )
158
158
 
159
- // Add pause or resume action based on current state
159
+ // Add only one pause/resume action based on current state
160
160
  if (isPaused.get()) {
161
161
  notificationBuilder.addAction(
162
162
  R.drawable.ic_play,
@@ -171,20 +171,23 @@ class AudioNotificationManager private constructor(context: Context) {
171
171
  )
172
172
  }
173
173
 
174
- // Add configured custom actions
174
+ // Add configured custom actions (only if they don't already exist)
175
+ val existingActions = mutableSetOf<String>()
175
176
  recordingConfig.notification.actions.forEach { action ->
176
- val intent = Intent(context, RecordingActionReceiver::class.java).apply {
177
- this.action = action.intentAction
177
+ if (existingActions.add(action.intentAction)) { // Only add if action is unique
178
+ val intent = Intent(context, RecordingActionReceiver::class.java).apply {
179
+ this.action = action.intentAction
180
+ }
181
+ val pendingIntent = PendingIntent.getBroadcast(
182
+ context,
183
+ action.intentAction.hashCode(),
184
+ intent,
185
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
186
+ )
187
+ val actionIconResId = action.icon?.let { getResourceIdByName(it) }
188
+ ?: R.drawable.ic_default_action_icon
189
+ notificationBuilder.addAction(actionIconResId, action.title, pendingIntent)
178
190
  }
179
- val pendingIntent = PendingIntent.getBroadcast(
180
- context,
181
- action.intentAction.hashCode(),
182
- intent,
183
- PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
184
- )
185
- val actionIconResId = action.icon?.let { getResourceIdByName(it) }
186
- ?: R.drawable.ic_default_action_icon
187
- notificationBuilder.addAction(actionIconResId, action.title, pendingIntent)
188
191
  }
189
192
  }
190
193
 
@@ -232,22 +235,43 @@ class AudioNotificationManager private constructor(context: Context) {
232
235
 
233
236
  try {
234
237
  val currentTime = SystemClock.elapsedRealtime()
238
+
239
+ // Calculate current notification state
240
+ val recordingDuration = if (isPaused.get()) {
241
+ lastPauseTime - recordingStartTime - pausedDuration
242
+ } else {
243
+ System.currentTimeMillis() - recordingStartTime - pausedDuration
244
+ }
245
+
246
+ // Create a hash of the current notification state
247
+ val currentHash = Objects.hash(
248
+ recordingConfig.notification.title,
249
+ recordingConfig.notification.text,
250
+ formatDuration(recordingDuration),
251
+ isPaused.get()
252
+ )
253
+
235
254
  val needsRemoteViewsRefresh = currentTime - lastRemoteViewsUpdate >= remoteViewsRefreshInterval ||
236
255
  consecutiveUpdateFailures >= maxUpdateFailures
237
256
 
257
+ // Only update if content changed or refresh needed
258
+ if (currentHash == lastNotificationHash && !needsRemoteViewsRefresh) {
259
+ // Update waveform only if needed
260
+ if (shouldUpdateWaveform(audioData, currentTime)) {
261
+ updateWaveformOnly(audioData)
262
+ }
263
+ return
264
+ }
265
+
266
+ lastNotificationHash = currentHash
267
+
268
+ // Only recreate RemoteViews periodically or after failures
238
269
  if (needsRemoteViewsRefresh) {
239
- // Only recreate RemoteViews periodically or after failures
240
270
  remoteViews = RemoteViews(context.packageName, R.layout.notification_recording)
241
271
  lastRemoteViewsUpdate = currentTime
242
272
  consecutiveUpdateFailures = 0
243
273
  }
244
274
 
245
- val recordingDuration = if (isPaused.get()) {
246
- lastPauseTime - recordingStartTime - pausedDuration
247
- } else {
248
- System.currentTimeMillis() - recordingStartTime - pausedDuration
249
- }
250
-
251
275
  // Update RemoteViews content
252
276
  remoteViews.apply {
253
277
  setTextViewText(R.id.notification_title, recordingConfig.notification.title)
@@ -280,11 +304,13 @@ class AudioNotificationManager private constructor(context: Context) {
280
304
  addNotificationActions(context)
281
305
  }
282
306
 
283
- // Update the notification without triggering a new alert
307
+ // Update the notification with disabled alerts
284
308
  notificationManager.notify(
285
309
  recordingConfig.notification.notificationId,
286
310
  notificationBuilder
287
311
  .setOnlyAlertOnce(true)
312
+ .setVibrate(null)
313
+ .setDefaults(0)
288
314
  .build()
289
315
  )
290
316
 
@@ -300,6 +326,37 @@ class AudioNotificationManager private constructor(context: Context) {
300
326
  }
301
327
  }
302
328
  }
329
+
330
+ private fun shouldUpdateWaveform(audioData: FloatArray?, currentTime: Long): Boolean {
331
+ return recordingConfig.showWaveformInNotification &&
332
+ audioData != null &&
333
+ audioData.isNotEmpty() &&
334
+ currentTime - lastWaveformUpdate >= WAVEFORM_UPDATE_INTERVAL
335
+ }
336
+
337
+ private fun updateWaveformOnly(audioData: FloatArray?) {
338
+ if (audioData == null) return
339
+
340
+ try {
341
+ val waveformBitmap = waveformRenderer.generateWaveform(audioData, recordingConfig.notification.waveform)
342
+ remoteViews.setImageViewBitmap(R.id.notification_waveform, waveformBitmap)
343
+ lastWaveformUpdate = SystemClock.elapsedRealtime()
344
+
345
+ notificationManager.notify(
346
+ recordingConfig.notification.notificationId,
347
+ notificationBuilder
348
+ .setCustomContentView(remoteViews)
349
+ .setCustomBigContentView(remoteViews)
350
+ .setOnlyAlertOnce(true)
351
+ .setVibrate(null)
352
+ .setDefaults(0)
353
+ .build()
354
+ )
355
+ } catch (e: Exception) {
356
+ Log.e(Constants.TAG, "Error updating waveform", e)
357
+ }
358
+ }
359
+
303
360
  private fun reinitializeNotification() {
304
361
  try {
305
362
  val context = contextRef.get() ?: return
@@ -322,7 +379,6 @@ class AudioNotificationManager private constructor(context: Context) {
322
379
  }
323
380
  }
324
381
 
325
-
326
382
  fun getNotification(): Notification = notificationBuilder.build()
327
383
 
328
384
  fun pauseUpdates() {
@@ -539,7 +539,11 @@ class AudioRecorderManager(
539
539
  acquireWakeLock()
540
540
  pausedDuration += System.currentTimeMillis() - lastPauseTime
541
541
  isPaused.set(false)
542
+
543
+ // Add these lines to resume both recordings
542
544
  audioRecord?.startRecording()
545
+ compressedRecorder?.resume()
546
+
543
547
  promise.resolve("Recording resumed")
544
548
  } catch (e: Exception) {
545
549
  releaseWakeLock()
@@ -549,7 +553,10 @@ class AudioRecorderManager(
549
553
 
550
554
  fun pauseRecording(promise: Promise) {
551
555
  if (isRecording.get() && !isPaused.get()) {
556
+ // Add these lines to pause both recordings
552
557
  audioRecord?.stop()
558
+ compressedRecorder?.pause()
559
+
553
560
  lastPauseTime = System.currentTimeMillis()
554
561
  isPaused.set(true)
555
562
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@siteed/expo-audio-stream",
3
- "version": "1.9.2",
3
+ "version": "1.10.0",
4
4
  "description": "stream audio crossplatform",
5
5
  "license": "MIT",
6
6
  "main": "build/index.js",