Skip to content

Conversation

@Rabter1
Copy link

@Rabter1 Rabter1 commented Sep 21, 2025

🎵 Feature Overview

This PR introduces audio ducking functionality, allowing the plugin to lower the volume of other audio sources instead of completely stopping them when taking audio focus. This provides a much better user experience for apps that need to play audio alongside other media which are not supposed to be stopped.

🔧 Implementation Details

New AudioFocusMode Enum

export enum AudioFocusMode {
  NONE = 'none',             // Allow mixed audio (default)
  EXCLUSIVE = 'exclusive',   // Take exclusive focus, pause other audio
  DUCK = 'duck'              // Take focus but duck other audio volume
}

Updated Configuration API

// Before (old API)
NativeAudio.configure({
  fade: false,
  focus: true  // boolean flag
});

// After (new API)
NativeAudio.configure({
  fade: false,
  audioFocusMode: AudioFocusMode.DUCK  // enum value
});

Platform-Specific Implementations

iOS Implementation

  • NONE: AVAudioSession.Category.ambient
  • EXCLUSIVE: AVAudioSession.Category.playback
  • DUCK: AVAudioSession.Category.playback with .duckOthers option

Android Implementation

  • NONE: abandonAudioFocus()
  • EXCLUSIVE: requestAudioFocus() with AUDIOFOCUS_GAIN
  • DUCK: requestAudioFocus() with AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK

Web Implementation

  • No changes needed (configure method already throws for web platform)

⚠️ Breaking Changes

What Changed

The focus boolean parameter in ConfigureOptions has been replaced with audioFocusMode enum.

Migration Guide

// ❌ OLD - Will no longer work
NativeAudio.configure({
  fade: false,
  focus: true
});

NativeAudio.configure({
  fade: false,
  focus: false
});

// ✅ NEW - Updated API
NativeAudio.configure({
  fade: false,
  audioFocusMode: AudioFocusMode.EXCLUSIVE  // Equivalent to focus: true
});

NativeAudio.configure({
  fade: false,
  audioFocusMode: AudioFocusMode.NONE  // Equivalent to focus: false (default)
});

Impact Assessment

  • No impact if you never called configure() - default behavior unchanged
  • Breaking change only if you explicitly used focus: true/false parameter
  • Easy migration - direct 1:1 mapping available

🤔 Why Breaking Changes Were Necessary

1. Type Safety

The old boolean focus parameter allowed invalid states and didn't prevent misconfigurations. The enum approach makes invalid states impossible at compile-time:

// Old API - these invalid combinations were possible:
configure({ focus: true, ducking: true });   // ⚠️ Correct setup needs internal implementation knowledge
configure({ focus: false, ducking: true });  // ❌ Invalid - ducking requires focus

// New API - invalid states are impossible:
configure({ audioFocusMode: AudioFocusMode.DUCK }); // ✅ Clear and unambiguous

2. Future Extensibility

The enum approach allows easy addition of new audio focus modes without API changes:

// Future possibilities:
AudioFocusMode.DUCK_WITH_INTERRUPTED_SPEACH;

3. API Clarity

The enum makes the intent explicit and self-documenting:

// Old API - unclear what focus means
configure({ focus: true });

// New API - crystal clear intent
configure({ audioFocusMode: AudioFocusMode.DUCK });

4. Industry Standards

This approach aligns with platform conventions:

  • Android: Uses similar enum-like constants (AUDIOFOCUS_GAIN, AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK)
  • iOS: Uses category + options pattern that maps well to discrete modes

🎯 Most important use case

  1. Music Apps: Duck background music when playing sound effects or playing spoken instructions etc.

This feature allows e.g. a navigation app to play directions sounds and not stop playing music

🔄 Backward Compatibility Strategy

While this is technically a breaking change, the impact is minimal:

  1. Default behavior unchanged - apps not using configure() work identically
  2. Clear migration path - simple 1:1 mapping for existing focus usage
  3. Comprehensive documentation - examples show exact replacements needed
  4. Enhanced functionality - apps get better audio behavior after migration

Limitations

Audio Session Management

The current implementation uses a resource-based session management approach where audio focus is only released when the last asset is unloaded. This design decision was made to balance simplicity with common usage patterns.

Current Behavior:

  • Audio focus is configured in configure() call with EXCLUSIVE or DUCK modes.
  • Audio focus is requested on the first preload() call.
  • Focus remains active while any audio assets are loaded (even if not playing)
  • Focus is only released when the last asset is unloaded via unload()

Rationale:
This approach works well for the typical usage pattern of preload() → play() → unload() but may not be optimal for all use cases.

Alternative Session Management Strategies

  1. Playback-based management: Release focus when all audio stops playing (not just unloaded)

When Current Approach May Not Be Ideal

  • Long-lived assets: Apps that preload many assets but play them sporadically
  • Background apps: Apps that maintain loaded assets while backgrounded
  • Complex audio workflows: Apps with multiple simultaneous audio contexts

Future Considerations

The current implementation prioritizes simplicity and covers the most common usage patterns. More sophisticated session management could be added in future versions based on community feedback and real-world usage requirements.

Let me know if you are interested in this Feature and if I should improve the session state management.

@Rabter1 Rabter1 changed the title Add support for audio ducking when configuring the plugin. Add support for audio ducking in plugin configuration Sep 21, 2025
@Rabter1
Copy link
Author

Rabter1 commented Sep 22, 2025

I guess this PR is related or might even fix these two issues:
On iOS stops the background music
On iOS make the AVAudioSession.Category configurable to support the silent switch

@Rabter1
Copy link
Author

Rabter1 commented Sep 24, 2025

I just wanted to ask if you are interested in my suggested feature.
I am happy to improve the implementation further if needed.

It looks like the maintainer bazuka5801 is not active anymore since his GitHub activity is zero since July?
Thats why I am also pinging ryaa who made the most recent release of this plugin.

Thanks in advance.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants