@capgo/native-audio
Advanced tools
@@ -15,3 +15,3 @@ import AVFoundation | ||
| public class NativeAudio: CAPPlugin, AVAudioPlayerDelegate, CAPBridgedPlugin { | ||
| private let pluginVersion: String = "8.2.15" | ||
| private let pluginVersion: String = "8.3.0" | ||
| public let identifier = "NativeAudio" | ||
@@ -53,2 +53,4 @@ public let jsName = "NativeAudio" | ||
| private let queueKey = DispatchSpecificKey<Bool>() | ||
| /// Set while executing a block on the audio queue so getAudioAsset/endSession can avoid reentrant sync (deadlock). | ||
| private let audioQueueContextKey = DispatchSpecificKey<Bool?>() | ||
| var session = AVAudioSession.sharedInstance() | ||
@@ -192,3 +194,3 @@ | ||
| asset.resume() | ||
| self.updatePlaybackState(isPlaying: true) | ||
| self.updateNowPlayingInfo(audioId: assetId, audioAsset: asset) | ||
| } | ||
@@ -228,5 +230,3 @@ } | ||
| asset.stop() | ||
| self.clearNowPlayingInfo() | ||
| self.currentlyPlayingAssetId = nil | ||
| self.updatePlaybackState(isPlaying: false) | ||
| } | ||
@@ -252,3 +252,3 @@ return .success | ||
| asset.resume() | ||
| self.updatePlaybackState(isPlaying: true) | ||
| self.updateNowPlayingInfo(audioId: assetId, audioAsset: asset) | ||
| } | ||
@@ -281,3 +281,7 @@ } | ||
| let ignoreSilent = call.getBool(Constant.IgnoreSilent) ?? true | ||
| self.showNotification = call.getBool(Constant.ShowNotification) ?? false | ||
| // Only update showNotification when explicitly provided so repeated configure() calls | ||
| // (e.g. when switching assets) don't reset it to false and break Now Playing for the next play | ||
| if let showNotification = call.getBool(Constant.ShowNotification) { | ||
| self.showNotification = showNotification | ||
| } | ||
@@ -424,5 +428,4 @@ logger.info("Configuring audio session with focus=%@ background=%@ ignoreSilent=%@", "\(focus)", "\(background)", "\(ignoreSilent)") | ||
| // Clear notification if this was the currently playing asset | ||
| // Reset current track if this was the currently playing asset (next play will overwrite Now Playing) | ||
| if self.currentlyPlayingAssetId == assetId { | ||
| self.clearNowPlayingInfo() | ||
| self.currentlyPlayingAssetId = nil | ||
@@ -615,5 +618,6 @@ } | ||
| do { | ||
| // Check if any audio assets are still playing before deactivating | ||
| let hasPlayingAssets = audioQueue.sync { | ||
| return self.audioList.values.contains { asset in | ||
| // Avoid reentrant sync when already on audio queue (e.g. from pause()) to prevent deadlock | ||
| let hasPlayingAssets: Bool | ||
| if DispatchQueue.getSpecific(key: audioQueueContextKey) == true { | ||
| hasPlayingAssets = self.audioList.values.contains { asset in | ||
| if let audioAsset = asset as? AudioAsset { | ||
@@ -624,2 +628,11 @@ return audioAsset.isPlaying() | ||
| } | ||
| } else { | ||
| hasPlayingAssets = audioQueue.sync { | ||
| return self.audioList.values.contains { asset in | ||
| if let audioAsset = asset as? AudioAsset { | ||
| return audioAsset.isPlaying() | ||
| } | ||
| return false | ||
| } | ||
| } | ||
| } | ||
@@ -739,4 +752,8 @@ | ||
| @objc private func getAudioAsset(_ call: CAPPluginCall) -> AudioAsset? { | ||
| // Avoid reentrant sync when already on audio queue (e.g. from pause()) to prevent deadlock | ||
| if DispatchQueue.getSpecific(key: audioQueueContextKey) == true { | ||
| return self.audioList[call.getString(Constant.AssetIdKey) ?? ""] as? AudioAsset | ||
| } | ||
| var asset: AudioAsset? | ||
| audioQueue.sync { // Read operations should use sync | ||
| audioQueue.sync { | ||
| asset = self.audioList[call.getString(Constant.AssetIdKey) ?? ""] as? AudioAsset | ||
@@ -748,4 +765,5 @@ } | ||
| @objc func setCurrentTime(_ call: CAPPluginCall) { | ||
| // Consistent use of audioQueue.sync for all operations | ||
| audioQueue.sync { | ||
| self.audioQueue.setSpecific(key: self.audioQueueContextKey, value: true) | ||
| defer { self.audioQueue.setSpecific(key: self.audioQueueContextKey, value: nil) } | ||
| guard let audioAsset: AudioAsset = self.getAudioAsset(call) else { | ||
@@ -766,2 +784,4 @@ call.reject("Failed to get audio asset") | ||
| audioQueue.sync { | ||
| self.audioQueue.setSpecific(key: self.audioQueueContextKey, value: true) | ||
| defer { self.audioQueue.setSpecific(key: self.audioQueueContextKey, value: nil) } | ||
| guard let audioAsset: AudioAsset = self.getAudioAsset(call) else { | ||
@@ -780,2 +800,4 @@ call.reject("Failed to get audio asset") | ||
| audioQueue.sync { | ||
| self.audioQueue.setSpecific(key: self.audioQueueContextKey, value: true) | ||
| defer { self.audioQueue.setSpecific(key: self.audioQueueContextKey, value: nil) } | ||
| guard let audioAsset: AudioAsset = self.getAudioAsset(call) else { | ||
@@ -793,3 +815,6 @@ call.reject("Failed to get audio asset") | ||
| @objc func resume(_ call: CAPPluginCall) { | ||
| let audioId = call.getString(Constant.AssetIdKey) ?? "" | ||
| audioQueue.sync { | ||
| self.audioQueue.setSpecific(key: self.audioQueueContextKey, value: true) | ||
| defer { self.audioQueue.setSpecific(key: self.audioQueueContextKey, value: nil) } | ||
| guard let audioAsset: AudioAsset = self.getAudioAsset(call) else { | ||
@@ -824,3 +849,3 @@ call.reject("Failed to get audio asset") | ||
| if self.showNotification { | ||
| self.updatePlaybackState(isPlaying: true) | ||
| self.updateNowPlayingInfo(audioId: audioId, audioAsset: audioAsset) | ||
| } | ||
@@ -834,2 +859,4 @@ | ||
| audioQueue.sync { | ||
| self.audioQueue.setSpecific(key: self.audioQueueContextKey, value: true) | ||
| defer { self.audioQueue.setSpecific(key: self.audioQueueContextKey, value: nil) } | ||
| guard let audioAsset: AudioAsset = self.getAudioAsset(call) else { | ||
@@ -872,2 +899,4 @@ call.reject("Failed to get audio asset") | ||
| audioQueue.sync { | ||
| self.audioQueue.setSpecific(key: self.audioQueueContextKey, value: true) | ||
| defer { self.audioQueue.setSpecific(key: self.audioQueueContextKey, value: nil) } | ||
| guard !self.audioList.isEmpty else { | ||
@@ -881,5 +910,4 @@ call.reject("Audio list is empty") | ||
| // Clear notification when stopped | ||
| if self.showNotification { | ||
| self.clearNowPlayingInfo() | ||
| // Reset current track when stopping the asset that was playing (internal state, not just for notifications) | ||
| if self.currentlyPlayingAssetId == audioId { | ||
| self.currentlyPlayingAssetId = nil | ||
@@ -904,2 +932,4 @@ } | ||
| audioQueue.sync { | ||
| self.audioQueue.setSpecific(key: self.audioQueueContextKey, value: true) | ||
| defer { self.audioQueue.setSpecific(key: self.audioQueueContextKey, value: nil) } | ||
| guard let audioAsset: AudioAsset = self.getAudioAsset(call) else { | ||
@@ -931,2 +961,7 @@ call.reject("Failed to get audio asset") | ||
| // Reset current track if this was the currently playing asset (internal state tracking) | ||
| if self.currentlyPlayingAssetId == audioId { | ||
| self.currentlyPlayingAssetId = nil | ||
| } | ||
| // Clean up playOnce tracking if this was a playOnce asset | ||
@@ -959,2 +994,4 @@ if self.playOnceAssets.contains(audioId) { | ||
| audioQueue.sync { | ||
| self.audioQueue.setSpecific(key: self.audioQueueContextKey, value: true) | ||
| defer { self.audioQueue.setSpecific(key: self.audioQueueContextKey, value: nil) } | ||
| guard let audioAsset: AudioAsset = self.getAudioAsset(call) else { | ||
@@ -974,2 +1011,4 @@ call.reject("Failed to get audio asset") | ||
| audioQueue.sync { | ||
| self.audioQueue.setSpecific(key: self.audioQueueContextKey, value: true) | ||
| defer { self.audioQueue.setSpecific(key: self.audioQueueContextKey, value: nil) } | ||
| guard let audioAsset: AudioAsset = self.getAudioAsset(call) else { | ||
@@ -988,2 +1027,4 @@ call.reject("Failed to get audio asset") | ||
| audioQueue.sync { | ||
| self.audioQueue.setSpecific(key: self.audioQueueContextKey, value: true) | ||
| defer { self.audioQueue.setSpecific(key: self.audioQueueContextKey, value: nil) } | ||
| guard let audioAsset: AudioAsset = self.getAudioAsset(call) else { | ||
@@ -1346,2 +1387,4 @@ call.reject("Failed to get audio asset") | ||
| /// Clears the Now Playing info. Only used when tearing down (deinit); stop/unload do not clear | ||
| /// so that the next play can overwrite the notification without a race. | ||
| private func clearNowPlayingInfo() { | ||
@@ -1348,0 +1391,0 @@ DispatchQueue.main.async { |
+1
-1
| { | ||
| "name": "@capgo/native-audio", | ||
| "version": "8.2.15", | ||
| "version": "8.3.0", | ||
| "description": "A native plugin for native audio engine", | ||
@@ -5,0 +5,0 @@ "license": "MPL-2.0", |
667737
0.51%