@capgo/native-audio 8.3.15 → 8.3.17

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.
@@ -78,7 +78,7 @@ dependencies {
78
78
  implementation 'androidx.media3:media3-exoplayer-hls:1.10.0'
79
79
  }
80
80
 
81
- implementation 'androidx.media3:media3-session:1.9.2'
81
+ implementation 'androidx.media3:media3-session:1.10.0'
82
82
  implementation 'androidx.media3:media3-transformer:1.9.2'
83
83
  implementation 'androidx.media3:media3-ui:1.9.2'
84
84
  implementation 'androidx.media3:media3-database:1.10.0'
@@ -34,14 +34,17 @@ extension AudioAsset {
34
34
  scheduleLocalFadeOutPauseOnMain(audio: audio, beforePause: beforePause)
35
35
  }
36
36
 
37
- fileprivate func scheduleLocalFadeOutStopOnMain(audio: AVAudioPlayer) {
37
+ fileprivate func scheduleLocalFadeOutStopOnMain(audio: AVAudioPlayer, beforeStop: ((TimeInterval, TimeInterval) -> Void)?) {
38
38
  DispatchQueue.main.async { [weak self] in
39
39
  guard let self else { return }
40
- self.performLocalFadeOutStopOnMain(audio: audio)
40
+ self.performLocalFadeOutStopOnMain(audio: audio, beforeStop: beforeStop)
41
41
  }
42
42
  }
43
43
 
44
- fileprivate func performLocalFadeOutStopOnMain(audio: AVAudioPlayer) {
44
+ fileprivate func performLocalFadeOutStopOnMain(audio: AVAudioPlayer, beforeStop: ((TimeInterval, TimeInterval) -> Void)?) {
45
+ let elapsed = audio.currentTime
46
+ let duration = audio.duration.isFinite ? audio.duration : 0
47
+ beforeStop?(elapsed, duration)
45
48
  audio.stop()
46
49
  dispatchComplete()
47
50
  }
@@ -71,12 +74,16 @@ extension AudioAsset {
71
74
  }
72
75
  }
73
76
 
77
+ /// - Parameter beforeStop: Called on the main queue immediately before `stop()` when `toPause` is false,
78
+ /// so the plugin can refresh Now Playing (rate 0) at the final elapsed time.
74
79
  /// - Parameter beforePause: Called on the main queue immediately before `pause()` when `toPause` is true,
75
80
  /// so the plugin can persist `timeBeforePause` and update Now Playing at the actual stop position.
81
+ /// Kept last so call sites can use a trailing closure for fade-out-to-pause.
76
82
  func fadeOut(
77
83
  audio: AVAudioPlayer,
78
84
  fadeOutDuration: TimeInterval,
79
85
  toPause: Bool = false,
86
+ beforeStop: ((TimeInterval, TimeInterval) -> Void)? = nil,
80
87
  beforePause: ((TimeInterval, TimeInterval) -> Void)? = nil
81
88
  ) {
82
89
  cancelFade()
@@ -85,7 +92,7 @@ extension AudioAsset {
85
92
  if toPause {
86
93
  scheduleLocalFadeOutPauseOnMain(audio: audio, beforePause: beforePause)
87
94
  } else {
88
- scheduleLocalFadeOutStopOnMain(audio: audio)
95
+ scheduleLocalFadeOutStopOnMain(audio: audio, beforeStop: beforeStop)
89
96
  }
90
97
  return
91
98
  }
@@ -108,7 +115,7 @@ extension AudioAsset {
108
115
  if toPause {
109
116
  self.performLocalFadeOutPauseOnMain(audio: audio, beforePause: beforePause)
110
117
  } else {
111
- self.performLocalFadeOutStopOnMain(audio: audio)
118
+ self.performLocalFadeOutStopOnMain(audio: audio, beforeStop: beforeStop)
112
119
  }
113
120
  }
114
121
  }
@@ -261,10 +261,10 @@ public class AudioAsset: NSObject, AVAudioPlayerDelegate {
261
261
  if player.isPlaying {
262
262
  if toPause {
263
263
  if player.volume > 0 {
264
- fadeOut(audio: player, fadeOutDuration: fadeOutDuration, toPause: true) { [weak self] elapsed, duration in
264
+ fadeOut(audio: player, fadeOutDuration: fadeOutDuration, toPause: true, beforePause: { [weak self] elapsed, duration in
265
265
  guard let self, let owner = self.owner else { return }
266
266
  owner.recordPausePositionAfterFade(assetId: self.assetId, elapsedTime: elapsed, duration: duration)
267
- }
267
+ })
268
268
  } else {
269
269
  cancelFade()
270
270
  schedulePauseWithPositionRecording(audio: player) { [weak self] elapsed, duration in
@@ -273,9 +273,15 @@ public class AudioAsset: NSObject, AVAudioPlayerDelegate {
273
273
  }
274
274
  }
275
275
  } else if player.volume > 0 {
276
- fadeOut(audio: player, fadeOutDuration: fadeOutDuration, toPause: false)
276
+ fadeOut(audio: player, fadeOutDuration: fadeOutDuration, toPause: false, beforeStop: { [weak self] elapsed, duration in
277
+ guard let self, let owner = self.owner else { return }
278
+ owner.recordStoppedPlaybackStateAfterFade(assetId: self.assetId, elapsedTime: elapsed, duration: duration)
279
+ })
277
280
  } else {
281
+ let elapsed = player.currentTime
282
+ let duration = player.duration.isFinite ? player.duration : 0
278
283
  stop()
284
+ owner?.recordStoppedPlaybackStateAfterFade(assetId: assetId, elapsedTime: elapsed, duration: duration)
279
285
  }
280
286
  } else if !toPause {
281
287
  stop()
@@ -12,7 +12,7 @@ enum MyError: Error {
12
12
  @objc(NativeAudio)
13
13
  // swiftlint:disable:next type_body_length
14
14
  public class NativeAudio: CAPPlugin, AVAudioPlayerDelegate, CAPBridgedPlugin {
15
- private let pluginVersion: String = "8.3.15"
15
+ private let pluginVersion: String = "8.3.17"
16
16
  public let identifier = "NativeAudio"
17
17
  public let jsName = "NativeAudio"
18
18
  public let pluginMethods: [CAPPluginMethod] = [
@@ -266,8 +266,20 @@ public class NativeAudio: CAPPlugin, AVAudioPlayerDelegate, CAPBridgedPlugin {
266
266
  return
267
267
  }
268
268
 
269
+ // Sample before `stop()` — `AudioAsset.stop()` resets every channel's `currentTime` to 0.
270
+ let elapsedTime = asset.getCurrentTime()
271
+ let duration = asset.getDuration()
269
272
  asset.stop()
270
- self.currentlyPlayingAssetId = nil
273
+ // Keep `currentlyPlayingAssetId` and Now Playing metadata so the lock screen card
274
+ // stays until `unload()` (or natural completion / another `play()` replaces it).
275
+ if self.showNotification,
276
+ self.currentlyPlayingAssetId == assetId {
277
+ self.updatePlaybackState(
278
+ isPlaying: false,
279
+ elapsedTime: elapsedTime,
280
+ duration: duration
281
+ )
282
+ }
271
283
  }
272
284
  return .success
273
285
  }
@@ -974,7 +986,7 @@ public class NativeAudio: CAPPlugin, AVAudioPlayerDelegate, CAPBridgedPlugin {
974
986
 
975
987
  /// Stops playback of the audio asset identified by `assetId` from the plugin call and performs related cleanup.
976
988
  ///
977
- /// The `assetId` is read from the call using `Constant.AssetIdKey`. If the asset is currently playing it will be stopped; if `showNotification` is enabled the Now Playing info is cleared and `currentlyPlayingAssetId` is reset. If the asset was created by `playOnce`, it is removed from `playOnceAssets` and its notification metadata is removed. The audio session is ended if appropriate. The call is resolved on success or rejected with an error message on failure.
989
+ /// The `assetId` is read from the call using `Constant.AssetIdKey`. If the asset is currently playing it will be stopped. When `showNotification` is enabled and this asset owns Now Playing, playback state is updated to stopped but the Now Playing card is left in place until `unload()` or natural completion. If the asset was created by `playOnce`, it is removed from `playOnceAssets` and its notification metadata is removed. The audio session is ended if appropriate. The call is resolved on success or rejected with an error message on failure.
978
990
  @objc func stop(_ call: CAPPluginCall) {
979
991
  let audioId = call.getString(Constant.AssetIdKey) ?? ""
980
992
  let fadeOut = call.getBool(Constant.FadeOut) ?? false
@@ -989,11 +1001,32 @@ public class NativeAudio: CAPPlugin, AVAudioPlayerDelegate, CAPBridgedPlugin {
989
1001
  }
990
1002
 
991
1003
  do {
1004
+ // Sample before `stopAudio` — non-fade `AudioAsset.stop()` resets `currentTime` to 0.
1005
+ var preStopNowPlayingElapsed: TimeInterval?
1006
+ var preStopNowPlayingDuration: TimeInterval?
1007
+ if !fadeOut,
1008
+ self.showNotification,
1009
+ self.currentlyPlayingAssetId == audioId,
1010
+ let preStopAsset = self.audioList[audioId] as? AudioAsset {
1011
+ preStopNowPlayingElapsed = preStopAsset.getCurrentTime()
1012
+ preStopNowPlayingDuration = preStopAsset.getDuration()
1013
+ }
1014
+
992
1015
  try self.stopAudio(audioId: audioId, fadeOut: fadeOut, fadeOutDuration: fadeOutDuration)
993
1016
 
994
- // Reset current track when stopping the asset that was playing (internal state, not just for notifications)
995
- if self.currentlyPlayingAssetId == audioId {
996
- self.currentlyPlayingAssetId = nil
1017
+ // Keep `currentlyPlayingAssetId` so lock screen / Control Center stays tied to this asset
1018
+ // until `unload()` clears it; refresh Now Playing to a stopped state (rate 0).
1019
+ // Skip when fading out to stop: `recordStoppedPlaybackStateAfterFade` runs when the fade finishes
1020
+ // (and for zero-volume immediate stop inside `stopWithFade`).
1021
+ if let elapsed = preStopNowPlayingElapsed,
1022
+ let duration = preStopNowPlayingDuration,
1023
+ self.showNotification,
1024
+ self.currentlyPlayingAssetId == audioId {
1025
+ self.updatePlaybackState(
1026
+ isPlaying: false,
1027
+ elapsedTime: elapsed,
1028
+ duration: duration
1029
+ )
997
1030
  }
998
1031
 
999
1032
  // Clean up playOnce tracking if this was a playOnce asset
@@ -1517,6 +1550,16 @@ public class NativeAudio: CAPPlugin, AVAudioPlayerDelegate, CAPBridgedPlugin {
1517
1550
  }
1518
1551
  }
1519
1552
 
1553
+ /// Refreshes Now Playing to a stopped state after fade-out-to-stop completes (or zero-volume stop-with-fade).
1554
+ internal func recordStoppedPlaybackStateAfterFade(assetId: String, elapsedTime: TimeInterval, duration: TimeInterval) {
1555
+ audioQueue.async { [weak self] in
1556
+ guard let self else { return }
1557
+ if self.showNotification && self.currentlyPlayingAssetId == assetId {
1558
+ self.updatePlaybackState(isPlaying: false, elapsedTime: elapsedTime, duration: duration)
1559
+ }
1560
+ }
1561
+ }
1562
+
1520
1563
  private func updatePlaybackState(isPlaying: Bool, elapsedTime: TimeInterval? = nil, duration: TimeInterval? = nil) {
1521
1564
  DispatchQueue.main.async {
1522
1565
  var nowPlayingInfo = MPNowPlayingInfoCenter.default().nowPlayingInfo ?? [String: Any]()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/native-audio",
3
- "version": "8.3.15",
3
+ "version": "8.3.17",
4
4
  "description": "A native plugin for native audio engine",
5
5
  "license": "MPL-2.0",
6
6
  "main": "dist/plugin.cjs.js",