rns-recplay 1.3.6 → 1.3.7

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/README.md CHANGED
@@ -130,12 +130,14 @@ Status Meanings:
130
130
 
131
131
  ### 🎙️ Recording
132
132
 
133
- #### `startRecording({ fileName?, shouldStopPlayback?, onSecondsUpdate? })`
133
+ #### `startRecording({ fileName?, shouldStopPlayback?, duck?, mixWithOthers?, onSecondsUpdate? })`
134
134
 
135
135
  | Parameter | Type | Default | Description |
136
136
  |-----------|------|---------|-------------|
137
137
  | `fileName` | `string` | `null` | Custom `.m4a` file name |
138
138
  | `shouldStopPlayback` | `boolean` | `true` | Stops any playing audio |
139
+ | `duck` | `boolean` | `true` | Reduce volume of other audio when recording |
140
+ | `mixWithOthers` | `boolean` | `true` | Mix recording with device playing audio |
139
141
  | `onSecondsUpdate` | `function` | `null` | Called every second |
140
142
 
141
143
  **Returns:** `Promise<string>` (file name)
@@ -200,6 +202,7 @@ Toggles between play and pause.
200
202
  - `BUFFERING`
201
203
  - `PLAYING`
202
204
  - `PAUSED`
205
+ - `ERROR`
203
206
  - `ENDED`
204
207
  - `IDLE`
205
208
 
@@ -217,5 +220,4 @@ Toggles between play and pause.
217
220
 
218
221
  ## 📄 License
219
222
 
220
- MIT License
221
-
223
+ MIT License
@@ -17,6 +17,7 @@ import com.facebook.react.modules.core.PermissionListener
17
17
  import com.google.android.exoplayer2.ExoPlayer
18
18
  import com.google.android.exoplayer2.MediaItem
19
19
  import com.google.android.exoplayer2.Player
20
+ import com.google.android.exoplayer2.audio.AudioAttributes
20
21
  import java.io.File
21
22
 
22
23
  class RecPlayModule(
@@ -214,23 +215,62 @@ class RecPlayModule(
214
215
  ) {
215
216
  handler.post {
216
217
  try {
217
- val finalUri =
218
- when {
219
- uriString.startsWith("http://") || uriString.startsWith("https://") -> {
220
- Uri.parse(uriString)
221
- }
222
-
223
- uriString.startsWith("file://") || uriString.startsWith("content://") -> {
224
- Uri.parse(uriString)
225
- }
226
-
227
- else -> {
228
- Uri.fromFile(File(uriString))
229
- }
230
- }
231
-
232
218
  if (player == null) {
233
219
  player = ExoPlayer.Builder(reactContext).build()
220
+
221
+ // ✅ RESTORE PLAYBACK STATUS EVENTS
222
+ player?.addListener(
223
+ object : Player.Listener {
224
+ override fun onPlaybackStateChanged(state: Int) {
225
+ val params = Arguments.createMap()
226
+ val status =
227
+ when (state) {
228
+ Player.STATE_BUFFERING -> "BUFFERING"
229
+ Player.STATE_READY -> if (player?.isPlaying == true) "PLAYING" else "PAUSED"
230
+ Player.STATE_ENDED -> "ENDED"
231
+ else -> "IDLE"
232
+ }
233
+
234
+ params.putString("status", status)
235
+ reactContext
236
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
237
+ .emit("onPlaybackStatus", params)
238
+
239
+ if (state == Player.STATE_ENDED) {
240
+ playbackHandler.removeCallbacks(playbackRunnable)
241
+ val finishParams = Arguments.createMap()
242
+ finishParams.putBoolean("finished", true)
243
+ reactContext
244
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
245
+ .emit("onPlaybackFinished", finishParams)
246
+ }
247
+ }
248
+
249
+ override fun onIsPlayingChanged(isPlaying: Boolean) {
250
+ val params = Arguments.createMap()
251
+ params.putString("status", if (isPlaying) "PLAYING" else "PAUSED")
252
+ reactContext
253
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
254
+ .emit("onPlaybackStatus", params)
255
+
256
+ if (isPlaying) {
257
+ playbackHandler.post(playbackRunnable)
258
+ } else {
259
+ playbackHandler.removeCallbacks(playbackRunnable)
260
+ }
261
+ }
262
+
263
+ override fun onPlayerError(error: com.google.android.exoplayer2.ExoPlaybackException) {
264
+ super.onPlayerError(error)
265
+ val params = Arguments.createMap()
266
+ params.putString("status", "ERROR")
267
+ params.putString("message", error.localizedMessage)
268
+ reactContext
269
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
270
+ .emit("onPlaybackStatus", params)
271
+ }
272
+ },
273
+ )
234
274
  }
235
275
 
236
276
  player?.apply {
@@ -239,22 +279,23 @@ class RecPlayModule(
239
279
  clearMediaItems()
240
280
  }
241
281
 
282
+ val usage =
283
+ when {
284
+ duck -> com.google.android.exoplayer2.C.USAGE_ASSISTANCE_SONIFICATION
285
+ mixWithOthers -> com.google.android.exoplayer2.C.USAGE_MEDIA
286
+ else -> com.google.android.exoplayer2.C.USAGE_MEDIA
287
+ }
288
+
242
289
  val audioAttributes =
243
- com.google.android.exoplayer2.audio.AudioAttributes
290
+ AudioAttributes
244
291
  .Builder()
245
- .setUsage(com.google.android.exoplayer2.C.USAGE_MEDIA)
292
+ .setUsage(usage)
246
293
  .setContentType(com.google.android.exoplayer2.C.AUDIO_CONTENT_TYPE_MUSIC)
247
294
  .build()
248
295
 
249
- setAudioAttributes(audioAttributes, true)
296
+ setAudioAttributes(audioAttributes, !mixWithOthers)
250
297
 
251
- val mediaItem =
252
- MediaItem
253
- .Builder()
254
- .setUri(finalUri)
255
- // REQUIRED for remote .m4a from PHP
256
- .setMimeType("audio/mp4")
257
- .build()
298
+ val mediaItem = MediaItem.fromUri(Uri.parse(uriString))
258
299
 
259
300
  repeatMode =
260
301
  if (loop) Player.REPEAT_MODE_ONE else Player.REPEAT_MODE_OFF
@@ -262,12 +303,17 @@ class RecPlayModule(
262
303
  setMediaItem(mediaItem)
263
304
  prepare()
264
305
  playWhenReady = true
265
-
266
- playbackHandler.removeCallbacks(playbackRunnable)
267
- playbackHandler.post(playbackRunnable)
268
306
  }
269
307
  } catch (e: Exception) {
270
308
  Log.e(TAG, "Playback Error", e)
309
+
310
+ // ✅ Emit ERROR event to JS
311
+ val params = Arguments.createMap()
312
+ params.putString("status", "ERROR")
313
+ params.putString("message", e.localizedMessage)
314
+ reactContext
315
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
316
+ .emit("onPlaybackStatus", params)
271
317
  }
272
318
  }
273
319
  }
@@ -19,7 +19,10 @@ class RecPlayModule: RCTEventEmitter {
19
19
  }
20
20
 
21
21
  override func supportedEvents() -> [String]! {
22
- return ["onTimerUpdate", "onPlaybackStatus", "onPlaybackProgress", "onPlaybackFinished", "onAudioInterruption"]
22
+ return [
23
+ "onTimerUpdate", "onPlaybackStatus", "onPlaybackProgress", "onPlaybackFinished",
24
+ "onAudioInterruption",
25
+ ]
23
26
  }
24
27
 
25
28
  override init() {
@@ -44,76 +47,76 @@ class RecPlayModule: RCTEventEmitter {
44
47
 
45
48
  // MARK: - Recording Logic
46
49
  @objc(startRecording:shouldStopPlayback:duck:mixWithOthers:resolver:rejecter:)
47
- func startRecording(
48
- fileName: String?,
49
- shouldStopPlayback: Bool,
50
- duck: Bool,
51
- mixWithOthers: Bool,
52
- resolve: @escaping RCTPromiseResolveBlock,
53
- reject: @escaping RCTPromiseRejectBlock
54
- ) {
55
-
56
- if shouldStopPlayback {
57
- stopPlaybackInternal()
58
- }
59
-
60
- let session = AVAudioSession.sharedInstance()
61
-
62
- do {
63
- // Build options dynamically based on new params
64
- var options: AVAudioSession.CategoryOptions = [.defaultToSpeaker]
50
+ func startRecording(
51
+ fileName: String?,
52
+ shouldStopPlayback: Bool,
53
+ duck: Bool,
54
+ mixWithOthers: Bool,
55
+ resolve: @escaping RCTPromiseResolveBlock,
56
+ reject: @escaping RCTPromiseRejectBlock
57
+ ) {
65
58
 
66
- if duck {
67
- options.insert(.duckOthers)
68
- }
69
- if mixWithOthers {
70
- options.insert(.mixWithOthers)
59
+ if shouldStopPlayback {
60
+ stopPlaybackInternal()
71
61
  }
72
62
 
73
- // Reset session if previously active
74
- try? session.setActive(false)
75
- try session.setCategory(.playAndRecord, mode: .default, options: options)
76
- try session.setActive(true)
77
-
78
- let name = fileName ?? "rec_\(Int(Date().timeIntervalSince1970))"
79
- let fileURL = FileManager.default.temporaryDirectory
80
- .appendingPathComponent("\(name).m4a")
81
-
82
- let settings: [String: Any] = [
83
- AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
84
- AVSampleRateKey: 44100,
85
- AVNumberOfChannelsKey: 1,
86
- AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue,
87
- AVEncoderBitRateKey: 128000
88
- ]
63
+ let session = AVAudioSession.sharedInstance()
89
64
 
90
- audioRecorder = try AVAudioRecorder(url: fileURL, settings: settings)
91
- audioRecorder?.prepareToRecord()
92
- audioRecorder?.record()
65
+ do {
66
+ // Build options dynamically based on new params
67
+ var options: AVAudioSession.CategoryOptions = [.defaultToSpeaker]
93
68
 
94
- secondsElapsed = 0
95
- isPaused = false
69
+ if duck {
70
+ options.insert(.duckOthers)
71
+ }
72
+ if mixWithOthers {
73
+ options.insert(.mixWithOthers)
74
+ }
75
+
76
+ // Reset session if previously active
77
+ try? session.setActive(false)
78
+ try session.setCategory(.playAndRecord, mode: .default, options: options)
79
+ try session.setActive(true)
96
80
 
97
- DispatchQueue.main.async {
98
- self.recordingTimer?.invalidate()
99
- self.recordingTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
100
- if !self.isPaused {
101
- self.sendEvent(
102
- withName: "onTimerUpdate",
103
- body: ["seconds": self.secondsElapsed]
104
- )
105
- self.secondsElapsed += 1
81
+ let name = fileName ?? "rec_\(Int(Date().timeIntervalSince1970))"
82
+ let fileURL = FileManager.default.temporaryDirectory
83
+ .appendingPathComponent("\(name).m4a")
84
+
85
+ let settings: [String: Any] = [
86
+ AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
87
+ AVSampleRateKey: 44100,
88
+ AVNumberOfChannelsKey: 1,
89
+ AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue,
90
+ AVEncoderBitRateKey: 128000,
91
+ ]
92
+
93
+ audioRecorder = try AVAudioRecorder(url: fileURL, settings: settings)
94
+ audioRecorder?.prepareToRecord()
95
+ audioRecorder?.record()
96
+
97
+ secondsElapsed = 0
98
+ isPaused = false
99
+
100
+ DispatchQueue.main.async {
101
+ self.recordingTimer?.invalidate()
102
+ self.recordingTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) {
103
+ _ in
104
+ if !self.isPaused {
105
+ self.sendEvent(
106
+ withName: "onTimerUpdate",
107
+ body: ["seconds": self.secondsElapsed]
108
+ )
109
+ self.secondsElapsed += 1
110
+ }
106
111
  }
107
112
  }
108
- }
109
113
 
110
- resolve(name)
114
+ resolve(name)
111
115
 
112
- } catch {
113
- reject("REC_ERROR", "Failed to start recording", error)
116
+ } catch {
117
+ reject("REC_ERROR", "Failed to start recording", error)
118
+ }
114
119
  }
115
- }
116
-
117
120
 
118
121
  @objc(stopRecording:rejecter:)
119
122
  func stopRecording(
@@ -130,7 +133,8 @@ func startRecording(
130
133
 
131
134
  // notify other audio that we deactivated so they can resume
132
135
  do {
133
- try AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
136
+ try AVAudioSession.sharedInstance().setActive(
137
+ false, options: .notifyOthersOnDeactivation)
134
138
  } catch {
135
139
  // non-fatal
136
140
  print("⚠️ setActive(false) failed: \(error.localizedDescription)")
@@ -196,69 +200,136 @@ func startRecording(
196
200
  uri: String,
197
201
  shouldStopPrevious: Bool,
198
202
  loop: Bool,
199
- mixWithOthers: Bool,
203
+ mixWithOthers: Bool,
200
204
  duckOthers: Bool
201
- ) {DispatchQueue.main.async { [weak self] in
202
- guard let self = self else { return }
205
+ ) {
206
+ DispatchQueue.main.async { [weak self] in
207
+ guard let self = self else { return }
203
208
 
204
- if shouldStopPrevious {
205
- self.stopPlaybackInternal()
206
- }
209
+ if shouldStopPrevious {
210
+ self.stopPlaybackInternal()
211
+ }
207
212
 
208
- let session = AVAudioSession.sharedInstance()
213
+ let session = AVAudioSession.sharedInstance()
214
+ do {
215
+ try session.setActive(false)
209
216
 
210
- do {
211
- try session.setActive(false)
217
+ var options: AVAudioSession.CategoryOptions = []
212
218
 
213
- var options: AVAudioSession.CategoryOptions = []
219
+ if mixWithOthers {
220
+ options.insert(.mixWithOthers)
221
+ } else if duckOthers {
222
+ options.insert(.duckOthers)
223
+ }
214
224
 
215
- if mixWithOthers {
216
- options.insert(.mixWithOthers)
217
- } else if duckOthers {
218
- options.insert(.duckOthers)
225
+ try session.setCategory(.playback, mode: .default, options: options)
226
+ try session.setActive(true)
227
+
228
+ } catch {
229
+ print("⚠️ Audio Session Error: \(error.localizedDescription)")
230
+ self.sendEvent(withName: "onPlaybackStatus", body: ["status": "ERROR"])
231
+ return
219
232
  }
220
233
 
221
- try session.setCategory(.playback, mode: .default, options: options)
222
- try session.setActive(true)
234
+ let url: URL
235
+ if uri.hasPrefix("http") || uri.hasPrefix("file://") {
236
+ guard let u = URL(string: uri) else {
237
+ self.sendEvent(withName: "onPlaybackStatus", body: ["status": "ERROR"])
238
+ return
239
+ }
240
+ url = u
241
+ } else {
242
+ url = URL(fileURLWithPath: uri)
243
+ }
223
244
 
224
- } catch {
225
- print("⚠️ Audio Session Error: \(error.localizedDescription)")
226
- return
227
- }
245
+ let asset = AVURLAsset(url: url)
246
+ self.playerItem = AVPlayerItem(asset: asset)
247
+ self.isLooping = loop
228
248
 
229
- let url: URL
230
- if uri.hasPrefix("http") || uri.hasPrefix("file://") {
231
- guard let u = URL(string: uri) else { return }
232
- url = u
233
- } else {
234
- url = URL(fileURLWithPath: uri)
235
- }
249
+ // Add observers for buffering & ready-to-play
250
+ self.playerItem?.addObserver(self, forKeyPath: "status", options: [.new], context: nil)
251
+ self.playerItem?.addObserver(
252
+ self, forKeyPath: "playbackBufferEmpty", options: [.new], context: nil)
253
+ self.playerItem?.addObserver(
254
+ self, forKeyPath: "playbackLikelyToKeepUp", options: [.new], context: nil)
236
255
 
237
- let asset = AVURLAsset(url: url)
238
- self.playerItem = AVPlayerItem(asset: asset)
239
- self.isLooping = loop
256
+ NotificationCenter.default.addObserver(
257
+ self,
258
+ selector: #selector(self.playerDidFinishPlaying),
259
+ name: .AVPlayerItemDidPlayToEndTime,
260
+ object: self.playerItem
261
+ )
240
262
 
241
- self.playerItem?.addObserver(self, forKeyPath: "status", options: [.new], context: nil)
263
+ if self.audioPlayer == nil {
264
+ self.audioPlayer = AVPlayer(playerItem: self.playerItem)
265
+ } else {
266
+ self.audioPlayer?.replaceCurrentItem(with: self.playerItem)
267
+ }
242
268
 
243
- NotificationCenter.default.addObserver(
244
- self,
245
- selector: #selector(self.playerDidFinishPlaying),
246
- name: .AVPlayerItemDidPlayToEndTime,
247
- object: self.playerItem
248
- )
269
+ self.setupProgressObserver()
270
+ self.audioPlayer?.play()
249
271
 
250
- if self.audioPlayer == nil {
251
- self.audioPlayer = AVPlayer(playerItem: self.playerItem)
252
- } else {
253
- self.audioPlayer?.replaceCurrentItem(with: self.playerItem)
272
+ self.sendEvent(withName: "onPlaybackStatus", body: ["status": "PLAYING"])
254
273
  }
274
+ }
255
275
 
256
- self.setupProgressObserver()
257
- self.audioPlayer?.play()
276
+ // MARK: - Observers for Status / Buffering
277
+ override func observeValue(
278
+ forKeyPath keyPath: String?,
279
+ of object: Any?,
280
+ change: [NSKeyValueChangeKey: Any]?,
281
+ context: UnsafeMutableRawPointer?
282
+ ) {
283
+ guard let item = object as? AVPlayerItem else { return }
284
+
285
+ switch keyPath {
286
+ case "status":
287
+ switch item.status {
288
+ case .readyToPlay:
289
+ let isPlaying = self.audioPlayer?.timeControlStatus == .playing
290
+ self.sendEvent(
291
+ withName: "onPlaybackStatus", body: ["status": isPlaying ? "PLAYING" : "PAUSED"]
292
+ )
293
+ case .failed:
294
+ self.sendEvent(withName: "onPlaybackStatus", body: ["status": "ERROR"])
295
+ default:
296
+ self.sendEvent(withName: "onPlaybackStatus", body: ["status": "IDLE"])
297
+ }
258
298
 
259
- self.sendEvent(withName: "onPlaybackStatus", body: ["status": "PLAYING"])
260
- }}
299
+ case "playbackBufferEmpty":
300
+ if item.isPlaybackBufferEmpty {
301
+ self.sendEvent(withName: "onPlaybackStatus", body: ["status": "BUFFERING"])
302
+ }
303
+
304
+ case "playbackLikelyToKeepUp":
305
+ if item.isPlaybackLikelyToKeepUp {
306
+ let isPlaying = self.audioPlayer?.timeControlStatus == .playing
307
+ self.sendEvent(
308
+ withName: "onPlaybackStatus", body: ["status": isPlaying ? "PLAYING" : "PAUSED"]
309
+ )
310
+ }
261
311
 
312
+ default:
313
+ break
314
+ }
315
+ }
316
+
317
+ @objc func playerDidFinishPlaying(note: NSNotification) {
318
+ if isLooping {
319
+ audioPlayer?.seek(to: .zero)
320
+ audioPlayer?.play()
321
+ self.sendEvent(withName: "onPlaybackStatus", body: ["status": "PLAYING"])
322
+ } else {
323
+ sendEvent(withName: "onPlaybackFinished", body: ["finished": true])
324
+ sendEvent(withName: "onPlaybackStatus", body: ["status": "ENDED"])
325
+ do {
326
+ try AVAudioSession.sharedInstance().setActive(
327
+ false, options: .notifyOthersOnDeactivation)
328
+ } catch {
329
+ print("⚠️ setActive(false) failed: \(error.localizedDescription)")
330
+ }
331
+ }
332
+ }
262
333
 
263
334
  private func setupProgressObserver() {
264
335
 
@@ -283,7 +354,7 @@ func startRecording(
283
354
  withName: "onPlaybackProgress",
284
355
  body: [
285
356
  "currentPosition": time.seconds,
286
- "duration": duration
357
+ "duration": duration,
287
358
  ]
288
359
  )
289
360
  }
@@ -294,7 +365,8 @@ func startRecording(
294
365
  stopPlaybackInternal()
295
366
  // notify others so they can resume
296
367
  do {
297
- try AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
368
+ try AVAudioSession.sharedInstance().setActive(
369
+ false, options: .notifyOthersOnDeactivation)
298
370
  } catch {
299
371
  print("⚠️ setActive(false) failed: \(error.localizedDescription)")
300
372
  }
@@ -338,46 +410,13 @@ func startRecording(
338
410
  }
339
411
  }
340
412
 
341
- override func observeValue(
342
- forKeyPath keyPath: String?,
343
- of object: Any?,
344
- change: [NSKeyValueChangeKey: Any]?,
345
- context: UnsafeMutableRawPointer?
346
- ) {
347
-
348
- if keyPath == "status", let item = object as? AVPlayerItem {
349
- if item.status == .failed {
350
- sendEvent(withName: "onPlaybackStatus", body: ["status": "ERROR"])
351
- } else if item.status == .readyToPlay {
352
- // ready, but we don't force other players to resume here
353
- print("DEBUG: Audio Ready")
354
- }
355
- }
356
- }
357
-
358
- @objc func playerDidFinishPlaying(note: NSNotification) {
359
-
360
- if isLooping {
361
- audioPlayer?.seek(to: .zero)
362
- audioPlayer?.play()
363
- } else {
364
- sendEvent(withName: "onPlaybackFinished", body: ["finished": true])
365
- sendEvent(withName: "onPlaybackStatus", body: ["status": "ENDED"])
366
- // deactivate so others can resume
367
- do {
368
- try AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
369
- } catch {
370
- print("⚠️ setActive(false) failed: \(error.localizedDescription)")
371
- }
372
- }
373
- }
374
-
375
413
  // MARK: - Interruption & Route Handling
376
414
 
377
415
  @objc private func handleAudioSessionInterruption(_ notification: Notification) {
378
416
  guard let info = notification.userInfo,
379
- let typeValue = info[AVAudioSessionInterruptionTypeKey] as? UInt,
380
- let type = AVAudioSession.InterruptionType(rawValue: typeValue) else {
417
+ let typeValue = info[AVAudioSessionInterruptionTypeKey] as? UInt,
418
+ let type = AVAudioSession.InterruptionType(rawValue: typeValue)
419
+ else {
381
420
  return
382
421
  }
383
422
 
@@ -389,15 +428,19 @@ func startRecording(
389
428
  case .ended:
390
429
  // interruption ended — optionally reactivate
391
430
  let optionsValue = info[AVAudioSessionInterruptionOptionKey] as? UInt
392
- let shouldResume = (optionsValue ?? 0) & AVAudioSession.InterruptionOptions.shouldResume.rawValue != 0
393
- sendEvent(withName: "onAudioInterruption", body: ["type": "ended", "shouldResume": shouldResume])
431
+ let shouldResume =
432
+ (optionsValue ?? 0) & AVAudioSession.InterruptionOptions.shouldResume.rawValue != 0
433
+ sendEvent(
434
+ withName: "onAudioInterruption",
435
+ body: ["type": "ended", "shouldResume": shouldResume])
394
436
  if shouldResume {
395
437
  // try to reactivate and resume playback if appropriate
396
438
  do {
397
439
  try AVAudioSession.sharedInstance().setActive(true)
398
440
  audioPlayer?.play()
399
441
  } catch {
400
- print("⚠️ Failed to reactivate after interruption: \(error.localizedDescription)")
442
+ print(
443
+ "⚠️ Failed to reactivate after interruption: \(error.localizedDescription)")
401
444
  }
402
445
  }
403
446
  print("🔊 Audio interruption ended. shouldResume: \(shouldResume)")
@@ -408,8 +451,9 @@ func startRecording(
408
451
 
409
452
  @objc private func handleRouteChange(_ notification: Notification) {
410
453
  guard let userInfo = notification.userInfo,
411
- let reasonValue = userInfo[AVAudioSessionRouteChangeReasonKey] as? UInt,
412
- let reason = AVAudioSession.RouteChangeReason(rawValue: reasonValue) else { return }
454
+ let reasonValue = userInfo[AVAudioSessionRouteChangeReasonKey] as? UInt,
455
+ let reason = AVAudioSession.RouteChangeReason(rawValue: reasonValue)
456
+ else { return }
413
457
 
414
458
  switch reason {
415
459
  case .oldDeviceUnavailable:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rns-recplay",
3
- "version": "1.3.6",
3
+ "version": "1.3.7",
4
4
  "description": "High-performance React Native module for audio recording and audio playback on Android and iOS.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",