@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.
|
|
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
|
|
71
|
+
NotificationManager.IMPORTANCE_LOW
|
|
70
72
|
).apply {
|
|
71
73
|
description = recordingConfig.notification.channelDescription
|
|
72
|
-
enableLights(
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
setShowBadge(
|
|
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
|
|
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
|
-
|
|
177
|
-
|
|
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
|
|
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
|
|