@capgo/native-audio 8.2.15 → 8.3.1
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.
|
@@ -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.
|
|
15
|
+
private let pluginVersion: String = "8.3.1"
|
|
16
16
|
public let identifier = "NativeAudio"
|
|
17
17
|
public let jsName = "NativeAudio"
|
|
18
18
|
public let pluginMethods: [CAPPluginMethod] = [
|
|
@@ -49,6 +49,8 @@ public class NativeAudio: CAPPlugin, AVAudioPlayerDelegate, CAPBridgedPlugin {
|
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
private let queueKey = DispatchSpecificKey<Bool>()
|
|
52
|
+
/// Set while executing a block on the audio queue so getAudioAsset/endSession can avoid reentrant sync (deadlock).
|
|
53
|
+
private let audioQueueContextKey = DispatchSpecificKey<Bool?>()
|
|
52
54
|
var session = AVAudioSession.sharedInstance()
|
|
53
55
|
|
|
54
56
|
// Track if audio session has been initialized
|
|
@@ -187,7 +189,7 @@ public class NativeAudio: CAPPlugin, AVAudioPlayerDelegate, CAPBridgedPlugin {
|
|
|
187
189
|
|
|
188
190
|
if !asset.isPlaying() {
|
|
189
191
|
asset.resume()
|
|
190
|
-
self.
|
|
192
|
+
self.updateNowPlayingInfo(audioId: assetId, audioAsset: asset)
|
|
191
193
|
}
|
|
192
194
|
}
|
|
193
195
|
return .success
|
|
@@ -222,9 +224,7 @@ public class NativeAudio: CAPPlugin, AVAudioPlayerDelegate, CAPBridgedPlugin {
|
|
|
222
224
|
}
|
|
223
225
|
|
|
224
226
|
asset.stop()
|
|
225
|
-
self.clearNowPlayingInfo()
|
|
226
227
|
self.currentlyPlayingAssetId = nil
|
|
227
|
-
self.updatePlaybackState(isPlaying: false)
|
|
228
228
|
}
|
|
229
229
|
return .success
|
|
230
230
|
}
|
|
@@ -245,7 +245,7 @@ public class NativeAudio: CAPPlugin, AVAudioPlayerDelegate, CAPBridgedPlugin {
|
|
|
245
245
|
self.updatePlaybackState(isPlaying: false)
|
|
246
246
|
} else {
|
|
247
247
|
asset.resume()
|
|
248
|
-
self.
|
|
248
|
+
self.updateNowPlayingInfo(audioId: assetId, audioAsset: asset)
|
|
249
249
|
}
|
|
250
250
|
}
|
|
251
251
|
return .success
|
|
@@ -273,7 +273,11 @@ public class NativeAudio: CAPPlugin, AVAudioPlayerDelegate, CAPBridgedPlugin {
|
|
|
273
273
|
let focus = call.getBool(Constant.FocusAudio) ?? false
|
|
274
274
|
let background = call.getBool(Constant.Background) ?? false
|
|
275
275
|
let ignoreSilent = call.getBool(Constant.IgnoreSilent) ?? true
|
|
276
|
-
|
|
276
|
+
// Only update showNotification when explicitly provided so repeated configure() calls
|
|
277
|
+
// (e.g. when switching assets) don't reset it to false and break Now Playing for the next play
|
|
278
|
+
if let showNotification = call.getBool(Constant.ShowNotification) {
|
|
279
|
+
self.showNotification = showNotification
|
|
280
|
+
}
|
|
277
281
|
|
|
278
282
|
logger.info("Configuring audio session with focus=%@ background=%@ ignoreSilent=%@", "\(focus)", "\(background)", "\(ignoreSilent)")
|
|
279
283
|
|
|
@@ -415,9 +419,8 @@ public class NativeAudio: CAPPlugin, AVAudioPlayerDelegate, CAPBridgedPlugin {
|
|
|
415
419
|
self.playOnceAssets.remove(assetId)
|
|
416
420
|
self.notificationMetadataMap.removeValue(forKey: assetId)
|
|
417
421
|
|
|
418
|
-
//
|
|
422
|
+
// Reset current track if this was the currently playing asset (next play will overwrite Now Playing)
|
|
419
423
|
if self.currentlyPlayingAssetId == assetId {
|
|
420
|
-
self.clearNowPlayingInfo()
|
|
421
424
|
self.currentlyPlayingAssetId = nil
|
|
422
425
|
}
|
|
423
426
|
|
|
@@ -605,14 +608,24 @@ public class NativeAudio: CAPPlugin, AVAudioPlayerDelegate, CAPBridgedPlugin {
|
|
|
605
608
|
|
|
606
609
|
func endSession() {
|
|
607
610
|
do {
|
|
608
|
-
//
|
|
609
|
-
let hasPlayingAssets
|
|
610
|
-
|
|
611
|
+
// Avoid reentrant sync when already on audio queue (e.g. from pause()) to prevent deadlock
|
|
612
|
+
let hasPlayingAssets: Bool
|
|
613
|
+
if DispatchQueue.getSpecific(key: audioQueueContextKey) == true {
|
|
614
|
+
hasPlayingAssets = self.audioList.values.contains { asset in
|
|
611
615
|
if let audioAsset = asset as? AudioAsset {
|
|
612
616
|
return audioAsset.isPlaying()
|
|
613
617
|
}
|
|
614
618
|
return false
|
|
615
619
|
}
|
|
620
|
+
} else {
|
|
621
|
+
hasPlayingAssets = audioQueue.sync {
|
|
622
|
+
return self.audioList.values.contains { asset in
|
|
623
|
+
if let audioAsset = asset as? AudioAsset {
|
|
624
|
+
return audioAsset.isPlaying()
|
|
625
|
+
}
|
|
626
|
+
return false
|
|
627
|
+
}
|
|
628
|
+
}
|
|
616
629
|
}
|
|
617
630
|
|
|
618
631
|
// Only deactivate if no assets are playing AND no other audio is active
|
|
@@ -627,10 +640,21 @@ public class NativeAudio: CAPPlugin, AVAudioPlayerDelegate, CAPBridgedPlugin {
|
|
|
627
640
|
|
|
628
641
|
public func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
|
|
629
642
|
// Don't immediately end the session here, as other players might still be active
|
|
630
|
-
// Instead, check if all players are done
|
|
643
|
+
// Instead, check if all players are done and clear Now Playing if this asset was current
|
|
631
644
|
audioQueue.async { [weak self] in
|
|
632
645
|
guard let self = self else { return }
|
|
633
646
|
|
|
647
|
+
// Find which asset this player belongs to; if it was the currently playing one, clear notification
|
|
648
|
+
for (audioId, asset) in self.audioList {
|
|
649
|
+
if let audioAsset = asset as? AudioAsset, audioAsset.channels.contains(player) {
|
|
650
|
+
if self.currentlyPlayingAssetId == audioId {
|
|
651
|
+
self.currentlyPlayingAssetId = nil
|
|
652
|
+
self.clearNowPlayingInfo()
|
|
653
|
+
}
|
|
654
|
+
break
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
634
658
|
// Avoid recursive calls by checking if the asset is still in the list
|
|
635
659
|
let hasPlayingAssets = self.audioList.values.contains { asset in
|
|
636
660
|
if let audioAsset = asset as? AudioAsset {
|
|
@@ -642,6 +666,11 @@ public class NativeAudio: CAPPlugin, AVAudioPlayerDelegate, CAPBridgedPlugin {
|
|
|
642
666
|
|
|
643
667
|
// Only end the session if no more assets are playing
|
|
644
668
|
if !hasPlayingAssets {
|
|
669
|
+
// If we didn't find the asset above (e.g. playOnce already removed it), clear notification when nothing is playing
|
|
670
|
+
if self.currentlyPlayingAssetId != nil {
|
|
671
|
+
self.currentlyPlayingAssetId = nil
|
|
672
|
+
self.clearNowPlayingInfo()
|
|
673
|
+
}
|
|
645
674
|
self.endSession()
|
|
646
675
|
}
|
|
647
676
|
}
|
|
@@ -727,16 +756,21 @@ public class NativeAudio: CAPPlugin, AVAudioPlayerDelegate, CAPBridgedPlugin {
|
|
|
727
756
|
}
|
|
728
757
|
|
|
729
758
|
@objc private func getAudioAsset(_ call: CAPPluginCall) -> AudioAsset? {
|
|
759
|
+
// Avoid reentrant sync when already on audio queue (e.g. from pause()) to prevent deadlock
|
|
760
|
+
if DispatchQueue.getSpecific(key: audioQueueContextKey) == true {
|
|
761
|
+
return self.audioList[call.getString(Constant.AssetIdKey) ?? ""] as? AudioAsset
|
|
762
|
+
}
|
|
730
763
|
var asset: AudioAsset?
|
|
731
|
-
audioQueue.sync {
|
|
764
|
+
audioQueue.sync {
|
|
732
765
|
asset = self.audioList[call.getString(Constant.AssetIdKey) ?? ""] as? AudioAsset
|
|
733
766
|
}
|
|
734
767
|
return asset
|
|
735
768
|
}
|
|
736
769
|
|
|
737
770
|
@objc func setCurrentTime(_ call: CAPPluginCall) {
|
|
738
|
-
// Consistent use of audioQueue.sync for all operations
|
|
739
771
|
audioQueue.sync {
|
|
772
|
+
self.audioQueue.setSpecific(key: self.audioQueueContextKey, value: true)
|
|
773
|
+
defer { self.audioQueue.setSpecific(key: self.audioQueueContextKey, value: nil) }
|
|
740
774
|
guard let audioAsset: AudioAsset = self.getAudioAsset(call) else {
|
|
741
775
|
call.reject("Failed to get audio asset")
|
|
742
776
|
return
|
|
@@ -752,6 +786,8 @@ public class NativeAudio: CAPPlugin, AVAudioPlayerDelegate, CAPBridgedPlugin {
|
|
|
752
786
|
|
|
753
787
|
@objc func getDuration(_ call: CAPPluginCall) {
|
|
754
788
|
audioQueue.sync {
|
|
789
|
+
self.audioQueue.setSpecific(key: self.audioQueueContextKey, value: true)
|
|
790
|
+
defer { self.audioQueue.setSpecific(key: self.audioQueueContextKey, value: nil) }
|
|
755
791
|
guard let audioAsset: AudioAsset = self.getAudioAsset(call) else {
|
|
756
792
|
call.reject("Failed to get audio asset")
|
|
757
793
|
return
|
|
@@ -765,6 +801,8 @@ public class NativeAudio: CAPPlugin, AVAudioPlayerDelegate, CAPBridgedPlugin {
|
|
|
765
801
|
|
|
766
802
|
@objc func getCurrentTime(_ call: CAPPluginCall) {
|
|
767
803
|
audioQueue.sync {
|
|
804
|
+
self.audioQueue.setSpecific(key: self.audioQueueContextKey, value: true)
|
|
805
|
+
defer { self.audioQueue.setSpecific(key: self.audioQueueContextKey, value: nil) }
|
|
768
806
|
guard let audioAsset: AudioAsset = self.getAudioAsset(call) else {
|
|
769
807
|
call.reject("Failed to get audio asset")
|
|
770
808
|
return
|
|
@@ -777,7 +815,10 @@ public class NativeAudio: CAPPlugin, AVAudioPlayerDelegate, CAPBridgedPlugin {
|
|
|
777
815
|
}
|
|
778
816
|
|
|
779
817
|
@objc func resume(_ call: CAPPluginCall) {
|
|
818
|
+
let audioId = call.getString(Constant.AssetIdKey) ?? ""
|
|
780
819
|
audioQueue.sync {
|
|
820
|
+
self.audioQueue.setSpecific(key: self.audioQueueContextKey, value: true)
|
|
821
|
+
defer { self.audioQueue.setSpecific(key: self.audioQueueContextKey, value: nil) }
|
|
781
822
|
guard let audioAsset: AudioAsset = self.getAudioAsset(call) else {
|
|
782
823
|
call.reject("Failed to get audio asset")
|
|
783
824
|
return
|
|
@@ -807,7 +848,7 @@ public class NativeAudio: CAPPlugin, AVAudioPlayerDelegate, CAPBridgedPlugin {
|
|
|
807
848
|
|
|
808
849
|
// Update notification when resumed
|
|
809
850
|
if self.showNotification {
|
|
810
|
-
self.
|
|
851
|
+
self.updateNowPlayingInfo(audioId: audioId, audioAsset: audioAsset)
|
|
811
852
|
}
|
|
812
853
|
|
|
813
854
|
call.resolve()
|
|
@@ -816,6 +857,8 @@ public class NativeAudio: CAPPlugin, AVAudioPlayerDelegate, CAPBridgedPlugin {
|
|
|
816
857
|
|
|
817
858
|
@objc func pause(_ call: CAPPluginCall) {
|
|
818
859
|
audioQueue.sync {
|
|
860
|
+
self.audioQueue.setSpecific(key: self.audioQueueContextKey, value: true)
|
|
861
|
+
defer { self.audioQueue.setSpecific(key: self.audioQueueContextKey, value: nil) }
|
|
819
862
|
guard let audioAsset: AudioAsset = self.getAudioAsset(call) else {
|
|
820
863
|
call.reject("Failed to get audio asset")
|
|
821
864
|
return
|
|
@@ -853,6 +896,8 @@ public class NativeAudio: CAPPlugin, AVAudioPlayerDelegate, CAPBridgedPlugin {
|
|
|
853
896
|
let fadeOutDuration = call.getDouble(Constant.FadeOutDuration) ?? Double(Constant.DefaultFadeDuration)
|
|
854
897
|
|
|
855
898
|
audioQueue.sync {
|
|
899
|
+
self.audioQueue.setSpecific(key: self.audioQueueContextKey, value: true)
|
|
900
|
+
defer { self.audioQueue.setSpecific(key: self.audioQueueContextKey, value: nil) }
|
|
856
901
|
guard !self.audioList.isEmpty else {
|
|
857
902
|
call.reject("Audio list is empty")
|
|
858
903
|
return
|
|
@@ -861,9 +906,8 @@ public class NativeAudio: CAPPlugin, AVAudioPlayerDelegate, CAPBridgedPlugin {
|
|
|
861
906
|
do {
|
|
862
907
|
try self.stopAudio(audioId: audioId, fadeOut: fadeOut, fadeOutDuration: fadeOutDuration)
|
|
863
908
|
|
|
864
|
-
//
|
|
865
|
-
if self.
|
|
866
|
-
self.clearNowPlayingInfo()
|
|
909
|
+
// Reset current track when stopping the asset that was playing (internal state, not just for notifications)
|
|
910
|
+
if self.currentlyPlayingAssetId == audioId {
|
|
867
911
|
self.currentlyPlayingAssetId = nil
|
|
868
912
|
}
|
|
869
913
|
|
|
@@ -883,6 +927,8 @@ public class NativeAudio: CAPPlugin, AVAudioPlayerDelegate, CAPBridgedPlugin {
|
|
|
883
927
|
|
|
884
928
|
@objc func loop(_ call: CAPPluginCall) {
|
|
885
929
|
audioQueue.sync {
|
|
930
|
+
self.audioQueue.setSpecific(key: self.audioQueueContextKey, value: true)
|
|
931
|
+
defer { self.audioQueue.setSpecific(key: self.audioQueueContextKey, value: nil) }
|
|
886
932
|
guard let audioAsset: AudioAsset = self.getAudioAsset(call) else {
|
|
887
933
|
call.reject("Failed to get audio asset")
|
|
888
934
|
return
|
|
@@ -909,6 +955,11 @@ public class NativeAudio: CAPPlugin, AVAudioPlayerDelegate, CAPBridgedPlugin {
|
|
|
909
955
|
asset.unload()
|
|
910
956
|
self.audioList[audioId] = nil
|
|
911
957
|
|
|
958
|
+
// Reset current track if this was the currently playing asset (internal state tracking)
|
|
959
|
+
if self.currentlyPlayingAssetId == audioId {
|
|
960
|
+
self.currentlyPlayingAssetId = nil
|
|
961
|
+
}
|
|
962
|
+
|
|
912
963
|
// Clean up playOnce tracking if this was a playOnce asset
|
|
913
964
|
if self.playOnceAssets.contains(audioId) {
|
|
914
965
|
self.playOnceAssets.remove(audioId)
|
|
@@ -936,6 +987,8 @@ public class NativeAudio: CAPPlugin, AVAudioPlayerDelegate, CAPBridgedPlugin {
|
|
|
936
987
|
|
|
937
988
|
@objc func setVolume(_ call: CAPPluginCall) {
|
|
938
989
|
audioQueue.sync {
|
|
990
|
+
self.audioQueue.setSpecific(key: self.audioQueueContextKey, value: true)
|
|
991
|
+
defer { self.audioQueue.setSpecific(key: self.audioQueueContextKey, value: nil) }
|
|
939
992
|
guard let audioAsset: AudioAsset = self.getAudioAsset(call) else {
|
|
940
993
|
call.reject("Failed to get audio asset")
|
|
941
994
|
return
|
|
@@ -950,6 +1003,8 @@ public class NativeAudio: CAPPlugin, AVAudioPlayerDelegate, CAPBridgedPlugin {
|
|
|
950
1003
|
|
|
951
1004
|
@objc func setRate(_ call: CAPPluginCall) {
|
|
952
1005
|
audioQueue.sync {
|
|
1006
|
+
self.audioQueue.setSpecific(key: self.audioQueueContextKey, value: true)
|
|
1007
|
+
defer { self.audioQueue.setSpecific(key: self.audioQueueContextKey, value: nil) }
|
|
953
1008
|
guard let audioAsset: AudioAsset = self.getAudioAsset(call) else {
|
|
954
1009
|
call.reject("Failed to get audio asset")
|
|
955
1010
|
return
|
|
@@ -963,6 +1018,8 @@ public class NativeAudio: CAPPlugin, AVAudioPlayerDelegate, CAPBridgedPlugin {
|
|
|
963
1018
|
|
|
964
1019
|
@objc func isPlaying(_ call: CAPPluginCall) {
|
|
965
1020
|
audioQueue.sync {
|
|
1021
|
+
self.audioQueue.setSpecific(key: self.audioQueueContextKey, value: true)
|
|
1022
|
+
defer { self.audioQueue.setSpecific(key: self.audioQueueContextKey, value: nil) }
|
|
966
1023
|
guard let audioAsset: AudioAsset = self.getAudioAsset(call) else {
|
|
967
1024
|
call.reject("Failed to get audio asset")
|
|
968
1025
|
return
|
|
@@ -1320,6 +1377,8 @@ public class NativeAudio: CAPPlugin, AVAudioPlayerDelegate, CAPBridgedPlugin {
|
|
|
1320
1377
|
}
|
|
1321
1378
|
}
|
|
1322
1379
|
|
|
1380
|
+
/// Clears the Now Playing info. Only used when tearing down (deinit); stop/unload do not clear
|
|
1381
|
+
/// so that the next play can overwrite the notification without a race.
|
|
1323
1382
|
private func clearNowPlayingInfo() {
|
|
1324
1383
|
DispatchQueue.main.async {
|
|
1325
1384
|
MPNowPlayingInfoCenter.default().nowPlayingInfo = nil
|