Skip to content

Commit 05caaee

Browse files
committed
feat: add deinitPlugin method to restore audio session settings
1 parent 543032e commit 05caaee

File tree

5 files changed

+140
-3
lines changed

5 files changed

+140
-3
lines changed

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,21 @@ Get the native Capacitor plugin version
572572
--------------------
573573

574574

575+
### deinitPlugin()
576+
577+
```typescript
578+
deinitPlugin() => Promise<void>
579+
```
580+
581+
Deinitialize the plugin and restore original audio session settings
582+
This method stops all playing audio and reverts any audio session changes made by the plugin
583+
Use this when you need to ensure compatibility with other audio plugins
584+
585+
**Since:** 7.7.0
586+
587+
--------------------
588+
589+
575590
### Interfaces
576591

577592

android/src/main/java/ee/forgr/audio/NativeAudio.java

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ public class NativeAudio extends Plugin implements AudioManager.OnAudioFocusChan
5959
private static ArrayList<AudioAsset> resumeList;
6060
private AudioManager audioManager;
6161
private boolean fadeMusic = false;
62+
private boolean audioFocusRequested = false;
63+
private int originalAudioMode = AudioManager.MODE_INVALID;
6264

6365
private final Map<String, PluginCall> pendingDurationCalls = new HashMap<>();
6466

@@ -69,6 +71,11 @@ public void load() {
6971
this.audioManager = (AudioManager) this.getActivity().getSystemService(Context.AUDIO_SERVICE);
7072

7173
audioAssetList = new HashMap<>();
74+
75+
// Store the original audio mode but don't request focus yet
76+
if (this.audioManager != null) {
77+
originalAudioMode = this.audioManager.getMode();
78+
}
7279
}
7380

7481
@Override
@@ -153,6 +160,11 @@ public void configure(PluginCall call) {
153160
return;
154161
}
155162

163+
// Save original audio mode if not already saved
164+
if (originalAudioMode == AudioManager.MODE_INVALID) {
165+
originalAudioMode = this.audioManager.getMode();
166+
}
167+
156168
boolean focus = call.getBoolean(OPT_FOCUS_AUDIO, false);
157169
boolean background = call.getBoolean("background", false);
158170
this.fadeMusic = call.getBoolean("fade", false);
@@ -165,8 +177,10 @@ public void configure(PluginCall call) {
165177
AudioManager.STREAM_MUSIC,
166178
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
167179
); // Allow other audio to play quietly
168-
} else {
180+
audioFocusRequested = true;
181+
} else if (audioFocusRequested) {
169182
this.audioManager.abandonAudioFocus(this);
183+
audioFocusRequested = false;
170184
}
171185

172186
if (background) {
@@ -719,4 +733,35 @@ public void getPluginVersion(final PluginCall call) {
719733
call.reject("Could not get plugin version", e);
720734
}
721735
}
736+
737+
@PluginMethod
738+
public void deinitPlugin(final PluginCall call) {
739+
try {
740+
// Stop all playing audio
741+
if (audioAssetList != null) {
742+
for (AudioAsset asset : audioAssetList.values()) {
743+
if (asset != null) {
744+
asset.stop();
745+
}
746+
}
747+
}
748+
749+
// Release audio focus if we requested it
750+
if (audioFocusRequested && this.audioManager != null) {
751+
this.audioManager.abandonAudioFocus(this);
752+
audioFocusRequested = false;
753+
}
754+
755+
// Restore original audio mode if we changed it
756+
if (originalAudioMode != AudioManager.MODE_INVALID && this.audioManager != null) {
757+
this.audioManager.setMode(originalAudioMode);
758+
originalAudioMode = AudioManager.MODE_INVALID;
759+
}
760+
761+
call.resolve();
762+
} catch (Exception e) {
763+
Log.e(TAG, "Error in deinitPlugin", e);
764+
call.reject("Error deinitializing plugin: " + e.getMessage());
765+
}
766+
}
722767
}

ios/Sources/NativeAudioPlugin/Plugin.swift

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ public class NativeAudio: CAPPlugin, AVAudioPlayerDelegate, CAPBridgedPlugin {
3131
CAPPluginMethod(name: "resume", returnType: CAPPluginReturnPromise),
3232
CAPPluginMethod(name: "setCurrentTime", returnType: CAPPluginReturnPromise),
3333
CAPPluginMethod(name: "clearCache", returnType: CAPPluginReturnPromise),
34-
CAPPluginMethod(name: "getPluginVersion", returnType: CAPPluginReturnPromise)
34+
CAPPluginMethod(name: "getPluginVersion", returnType: CAPPluginReturnPromise),
35+
CAPPluginMethod(name: "deinitPlugin", returnType: CAPPluginReturnPromise)
3536
]
3637
internal let audioQueue = DispatchQueue(label: "ee.forgr.audio.queue", qos: .userInitiated, attributes: .concurrent)
3738
private var audioList: [String: Any] = [:] {
@@ -44,6 +45,12 @@ public class NativeAudio: CAPPlugin, AVAudioPlayerDelegate, CAPBridgedPlugin {
4445
var fadeMusic = false
4546
var session = AVAudioSession.sharedInstance()
4647

48+
// Track if audio session has been initialized
49+
private var audioSessionInitialized = false
50+
// Store the original audio category to restore on deinit
51+
private var originalAudioCategory: AVAudioSession.Category?
52+
private var originalAudioOptions: AVAudioSession.CategoryOptions?
53+
4754
// Add observer for audio session interruptions
4855
private var interruptionObserver: Any?
4956

@@ -53,7 +60,8 @@ public class NativeAudio: CAPPlugin, AVAudioPlayerDelegate, CAPBridgedPlugin {
5360

5461
self.fadeMusic = false
5562

56-
setupAudioSession()
63+
// Don't setup audio session on load - defer until first use
64+
// setupAudioSession()
5765
setupInterruptionHandling()
5866

5967
NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: .main) { [weak self] _ in
@@ -84,6 +92,13 @@ public class NativeAudio: CAPPlugin, AVAudioPlayerDelegate, CAPBridgedPlugin {
8492
}
8593

8694
private func setupAudioSession() {
95+
// Save the original audio session category before making changes
96+
if !audioSessionInitialized {
97+
originalAudioCategory = session.category
98+
originalAudioOptions = session.categoryOptions
99+
audioSessionInitialized = true
100+
}
101+
87102
do {
88103
// Only set the category without immediately activating/deactivating
89104
try self.session.setCategory(AVAudioSession.Category.playback, options: .mixWithOthers)
@@ -127,6 +142,13 @@ public class NativeAudio: CAPPlugin, AVAudioPlayerDelegate, CAPBridgedPlugin {
127142
}
128143

129144
@objc func configure(_ call: CAPPluginCall) {
145+
// Save original category on first configure call
146+
if !audioSessionInitialized {
147+
originalAudioCategory = session.category
148+
originalAudioOptions = session.categoryOptions
149+
audioSessionInitialized = true
150+
}
151+
130152
if let fade = call.getBool(Constant.FadeKey) {
131153
self.fadeMusic = fade
132154
}
@@ -234,6 +256,11 @@ public class NativeAudio: CAPPlugin, AVAudioPlayerDelegate, CAPBridgedPlugin {
234256
let time = max(call.getDouble("time") ?? 0, 0) // Ensure non-negative time
235257
let delay = max(call.getDouble("delay") ?? 0, 0) // Ensure non-negative delay
236258

259+
// Ensure audio session is initialized before first play
260+
if !audioSessionInitialized {
261+
setupAudioSession()
262+
}
263+
237264
// Use sync for operations that need to be blocking
238265
audioQueue.sync {
239266
guard !audioList.isEmpty else {
@@ -608,4 +635,36 @@ public class NativeAudio: CAPPlugin, AVAudioPlayerDelegate, CAPBridgedPlugin {
608635
call.resolve(["version": self.PLUGIN_VERSION])
609636
}
610637

638+
@objc func deinitPlugin(_ call: CAPPluginCall) {
639+
// Stop all playing audio
640+
audioQueue.sync(flags: .barrier) {
641+
for (_, asset) in self.audioList {
642+
if let audioAsset = asset as? AudioAsset {
643+
audioAsset.stop()
644+
}
645+
}
646+
}
647+
648+
// Restore original audio session settings if we changed them
649+
if audioSessionInitialized, let originalCategory = originalAudioCategory {
650+
do {
651+
// Deactivate our audio session
652+
try self.session.setActive(false, options: .notifyOthersOnDeactivation)
653+
654+
// Restore original category and options
655+
if let originalOptions = originalAudioOptions {
656+
try self.session.setCategory(originalCategory, options: originalOptions)
657+
} else {
658+
try self.session.setCategory(originalCategory)
659+
}
660+
661+
audioSessionInitialized = false
662+
} catch {
663+
print("Failed to restore audio session: \(error)")
664+
}
665+
}
666+
667+
call.resolve()
668+
}
669+
611670
}

src/definitions.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,4 +249,14 @@ export interface NativeAudio {
249249
* @throws An error if the something went wrong
250250
*/
251251
getPluginVersion(): Promise<{ version: string }>;
252+
253+
/**
254+
* Deinitialize the plugin and restore original audio session settings
255+
* This method stops all playing audio and reverts any audio session changes made by the plugin
256+
* Use this when you need to ensure compatibility with other audio plugins
257+
*
258+
* @since 7.7.0
259+
* @returns {Promise<void>}
260+
*/
261+
deinitPlugin(): Promise<void>;
252262
}

src/web.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,14 @@ export class NativeAudioWeb extends WebPlugin implements NativeAudio {
164164
async getPluginVersion(): Promise<{ version: string }> {
165165
return { version: 'web' };
166166
}
167+
168+
async deinitPlugin(): Promise<void> {
169+
// Stop and unload all audio assets
170+
for (const [assetId] of NativeAudioWeb.AUDIO_ASSET_BY_ASSET_ID) {
171+
await this.unload({ assetId });
172+
}
173+
return;
174+
}
167175
}
168176

169177
const NativeAudio = new NativeAudioWeb();

0 commit comments

Comments
 (0)