Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Oct 25, 2025

Implementing audio focus handling for Android and iOS to pause audio during phone calls and other audio interruptions.

Plan:

  • Analyze Android AudioPlayer implementation for audio focus support
  • Analyze iOS AudioPlayer implementation for audio interruption handling
  • Implement Android audio focus request/abandon in AudioPlayer.android.cs
  • Implement iOS audio interruption handling in AudioPlayer.macios.cs
  • Add documentation about audio focus behavior
  • Address code review feedback
  • Final code review and security check - PASSED
  • Make behavior configurable through AudioPlayerOptions
  • Fix build errors with correct API names
  • Fix naming conflict with fully qualified AudioManager type
  • Address Copilot PR review comments (round 1)
  • Address Copilot PR review comments (round 2)
  • Address Copilot PR review comments (round 3)
  • Address Copilot PR review comments (round 4 - documentation)

Recent Changes:

Fourth Round Code Review Improvements:

Android (AudioPlayer.android.cs):

  • Added XML documentation to AudioFocusChangeListener class explaining its purpose

iOS/macOS (AudioPlayer.macios.cs):

  • Added XML documentation to GetInterruptionType() method explaining parameter, return value, and default behavior
  • Added XML documentation to GetInterruptionOptions() method explaining parameter and return value

Note: Regarding the RequestAudioFocus() return value suggestion - the current behavior of returning false when disabled is intentional. The caller uses this to log a warning but continues playing for backward compatibility. Changing it to return true when disabled would be misleading as it would imply focus was successfully acquired.

Previous Code Review Improvements:

Third Round:

  • Android: Extracted PlayInternal() helper, added clarifying comments
  • iOS: Changed to use public Play() method for consistent state management

Second Round:

  • iOS: Added clarifying comment in GetInterruptionType() explaining default behavior
  • Android: Extracted PauseInternal() helper method to eliminate code duplication

First Round:

  • iOS: Extracted GetInterruptionType() and GetInterruptionOptions() helper methods
  • Android: Extracted DuckingVolumeMultiplier constant, fixed volume restoration logic

Build Error Fixes:

  • Android: Fully qualified all AudioManager references to Android.Media.AudioManager
  • iOS/macOS: Used string literals for AVAudioSession keys
  • Android: Fully qualified IOnAudioFocusChangeListener interface

Configuration Options:

Android (AudioPlayerOptions.android.cs):

  • Added ManageAudioFocus property (default: true)
  • When true: Automatically requests/abandons audio focus and responds to focus changes
  • When false: No audio focus management, giving developers full manual control

iOS/macOS (AudioPlayerOptions.macios.cs):

  • Added HandleAudioInterruptions property (default: true)
  • When true: Automatically pauses/resumes on interruptions
  • When false: No automatic interruption handling, giving developers full manual control

Android Changes (AudioPlayer.android.cs):

✅ Added Android.Media.AudioManager and AudioFocusRequestClass fields for managing audio focus
✅ Implemented audio focus request/abandon using both modern (API 26+) and legacy APIs
✅ Created AudioFocusChangeListener to handle focus changes
✅ Play() now requests audio focus before starting (if enabled)
✅ Pause() and Stop() now abandon audio focus (if enabled)
✅ Proper cleanup in Dispose()
✅ Audio focus only initialized and used if ManageAudioFocus = true
✅ Improved volume ducking with proper restoration
✅ Extracted PauseInternal() and PlayInternal() helpers to eliminate code duplication
✅ Comprehensive XML documentation

iOS Changes (AudioPlayer.macios.cs):

✅ Added NSObject interruptionObserver for AVAudioSession interruption notifications
✅ Registered for AVAudioSession.InterruptionNotification in PreparePlayer()
✅ Implemented HandleAudioSessionInterruption() to respond to interruptions
✅ Proper cleanup of observers in Dispose()
✅ Updated SetSource() to re-register interruption observers
✅ Interruptions only registered if HandleAudioInterruptions = true
✅ Safer casting with helper methods
✅ Uses public Play() method for consistent state management
✅ Comprehensive XML documentation

Documentation:

✅ Added new section "Audio Focus and Interruption Handling" in docs/audio-player.md
✅ Clarified "audio ducking" terminology
✅ Explained that behaviors are automatic but manual control is still available
✅ Added note about backward compatibility
✅ Links to official platform documentation
✅ Added configuration examples for both Android and iOS
✅ Documented the new ManageAudioFocus and HandleAudioInterruptions options

This implementation follows the official Android Audio Focus and iOS Audio Interruption guidelines and provides developers with full control over the behavior.

Original prompt

This section details on the original issue you should resolve

<issue_title>[Request] Audio Should Pause When Phone Call Received</issue_title>
<issue_description>Currently the plugin supports playing audio. But if new audio is started in another app or a phone call is made/received, the audio does not stop playing.

The opposite is also true - if another audio source is playing audio, and audio is started with Plugin.Maui.Audio, it will not broadcast this to other apps. Therefore you get overlapping audio
</issue_description>

<agent_instructions>Check the suggested solutions in the links in #42 (comment)</agent_instructions>

Comments on the Issue (you are @copilot in this section)

@jfversluis On what OS is this? iOS? Android? Both?

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

@jfversluis
Copy link
Owner

@copilot can we make this behavior configurable through an option?

Copilot AI changed the title [WIP] Fix audio playback to pause on incoming phone calls Add audio focus and interruption handling for Android and iOS Oct 25, 2025
Copilot AI requested a review from jfversluis October 25, 2025 11:37
Copilot finished work on behalf of jfversluis October 25, 2025 11:37
Copy link
Contributor Author

Copilot AI commented Oct 25, 2025

@copilot can we make this behavior configurable through an option?

Yes! I've made the behavior configurable. Added:

Android: ManageAudioFocus property (default: true)
iOS/macOS: HandleAudioInterruptions property (default: true)

Example usage:

var audioPlayer = audioManager.CreatePlayer(
    await FileSystem.OpenAppPackageFileAsync("audio.mp3"),
    new AudioPlayerOptions
    {
#if ANDROID
        ManageAudioFocus = false  // Disable on Android
#elif IOS || MACCATALYST
        HandleAudioInterruptions = false  // Disable on iOS/macOS
#endif
    });

Both default to true for automatic behavior, but can be set to false for full manual control. Documentation updated with examples.

Commit: 42ae44f

Copilot AI changed the title Add audio focus and interruption handling for Android and iOS Add configurable audio focus and interruption handling for Android and iOS Oct 25, 2025
Copilot finished work on behalf of jfversluis October 25, 2025 11:43
@jfversluis
Copy link
Owner

@copilot build errors:

D:\a\Plugin.Maui.Audio\Plugin.Maui.Audio\src\Plugin.Maui.Audio\AudioPlayer\AudioPlayer.macios.cs(275,102): error CS0117: 'AVAudioSession' does not contain a definition for 'InterruptionTypeKey' [D:\a\Plugin.Maui.Audio\Plugin.Maui.Audio\src\Plugin.Maui.Audio\Plugin.Maui.Audio.csproj::TargetFramework=net9.0-maccatalyst]
D:\a\Plugin.Maui.Audio\Plugin.Maui.Audio\src\Plugin.Maui.Audio\AudioPlayer\AudioPlayer.macios.cs(289,109): error CS0117: 'AVAudioSession' does not contain a definition for 'InterruptionOptionKey' [D:\a\Plugin.Maui.Audio\Plugin.Maui.Audio\src\Plugin.Maui.Audio\Plugin.Maui.Audio.csproj::TargetFramework=net9.0-maccatalyst]
D:\a\Plugin.Maui.Audio\Plugin.Maui.Audio\src\Plugin.Maui.Audio\AudioPlayer\AudioPlayer.macios.cs(275,102): error CS0117: 'AVAudioSession' does not contain a definition for 'InterruptionTypeKey' [D:\a\Plugin.Maui.Audio\Plugin.Maui.Audio\src\Plugin.Maui.Audio\Plugin.Maui.Audio.csproj::TargetFramework=net9.0-ios]
D:\a\Plugin.Maui.Audio\Plugin.Maui.Audio\src\Plugin.Maui.Audio\AudioPlayer\AudioPlayer.macios.cs(289,109): error CS0117: 'AVAudioSession' does not contain a definition for 'InterruptionOptionKey' [D:\a\Plugin.Maui.Audio\Plugin.Maui.Audio\src\Plugin.Maui.Audio\Plugin.Maui.Audio.csproj::TargetFramework=net9.0-ios]
D:\a\Plugin.Maui.Audio\Plugin.Maui.Audio\src\Plugin.Maui.Audio\AudioPlayer\AudioPlayer.android.cs(582,66): error CS0426: The type name 'IOnAudioFocusChangeListener' does not exist in the type 'AudioManager' [D:\a\Plugin.Maui.Audio\Plugin.Maui.Audio\src\Plugin.Maui.Audio\Plugin.Maui.Audio.csproj::TargetFramework=net9.0-android]

Copy link
Contributor Author

Copilot AI commented Oct 25, 2025

@copilot build errors:

D:\a\Plugin.Maui.Audio\Plugin.Maui.Audio\src\Plugin.Maui.Audio\AudioPlayer\AudioPlayer.macios.cs(275,102): error CS0117: 'AVAudioSession' does not contain a definition for 'I...

Fixed the build errors:

iOS/macOS: Used string literal keys "AVAudioSessionInterruptionTypeKey" and "AVAudioSessionInterruptionOptionKey" instead of static properties (correct API usage)

Android: Fully qualified the interface as Android.Media.AudioManager.IOnAudioFocusChangeListener

Commit: 01684a9

Copilot finished work on behalf of jfversluis October 25, 2025 19:50
@jfversluis
Copy link
Owner

@copilot more build errors

D:\a\Plugin.Maui.Audio\Plugin.Maui.Audio\src\Plugin.Maui.Audio\AudioPlayer\AudioPlayer.android.cs(197,19): error CS0030: Cannot convert type 'Java.Lang.Object' to 'Plugin.Maui.Audio.AudioManager' [D:\a\Plugin.Maui.Audio\Plugin.Maui.Audio\src\Plugin.Maui.Audio\Plugin.Maui.Audio.csproj::TargetFramework=net9.0-android]
D:\a\Plugin.Maui.Audio\Plugin.Maui.Audio\src\Plugin.Maui.Audio\AudioPlayer\AudioPlayer.android.cs(289,19): error CS0030: Cannot convert type 'Java.Lang.Object' to 'Plugin.Maui.Audio.AudioManager' [D:\a\Plugin.Maui.Audio\Plugin.Maui.Audio\src\Plugin.Maui.Audio\Plugin.Maui.Audio.csproj::TargetFramework=net9.0-android]
D:\a\Plugin.Maui.Audio\Plugin.Maui.Audio\src\Plugin.Maui.Audio\AudioPlayer\AudioPlayer.android.cs(331,19): error CS0030: Cannot convert type 'Java.Lang.Object' to 'Plugin.Maui.Audio.AudioManager' [D:\a\Plugin.Maui.Audio\Plugin.Maui.Audio\src\Plugin.Maui.Audio\Plugin.Maui.Audio.csproj::TargetFramework=net9.0-android]
D:\a\Plugin.Maui.Audio\Plugin.Maui.Audio\src\Plugin.Maui.Audio\AudioPlayer\AudioPlayer.android.cs(470,26): error CS1061: 'AudioManager' does not contain a definition for 'RequestAudioFocus' and no accessible extension method 'RequestAudioFocus' accepting a first argument of type 'AudioManager' could be found (are you missing a using directive or an assembly reference?) [D:\a\Plugin.Maui.Audio\Plugin.Maui.Audio\src\Plugin.Maui.Audio\Plugin.Maui.Audio.csproj::TargetFramework=net9.0-android]
D:\a\Plugin.Maui.Audio\Plugin.Maui.Audio\src\Plugin.Maui.Audio\AudioPlayer\AudioPlayer.android.cs(476,26): error CS1061: 'AudioManager' does not contain a definition for 'RequestAudioFocus' and no accessible extension method 'RequestAudioFocus' accepting a first argument of type 'AudioManager' could be found (are you missing a using directive or an assembly reference?) [D:\a\Plugin.Maui.Audio\Plugin.Maui.Audio\src\Plugin.Maui.Audio\Plugin.Maui.Audio.csproj::TargetFramework=net9.0-android]
D:\a\Plugin.Maui.Audio\Plugin.Maui.Audio\src\Plugin.Maui.Audio\AudioPlayer\AudioPlayer.android.cs(496,17): error CS1061: 'AudioManager' does not contain a definition for 'AbandonAudioFocusRequest' and no accessible extension method 'AbandonAudioFocusRequest' accepting a first argument of type 'AudioManager' could be found (are you missing a using directive or an assembly reference?) [D:\a\Plugin.Maui.Audio\Plugin.Maui.Audio\src\Plugin.Maui.Audio\Plugin.Maui.Audio.csproj::TargetFramework=net9.0-android]
D:\a\Plugin.Maui.Audio\Plugin.Maui.Audio\src\Plugin.Maui.Audio\AudioPlayer\AudioPlayer.android.cs(502,17): error CS1061: 'AudioManager' does not contain a definition for 'AbandonAudioFocus' and no accessible extension method 'AbandonAudioFocus' accepting a first argument of type 'AudioManager' could be found (are you missing a using directive or an assembly reference?) [D:\a\Plugin.Maui.Audio\Plugin.Maui.Audio\src\Plugin.Maui.Audio\Plugin.Maui.Audio.csproj::TargetFramework=net9.0-android]

Copy link
Contributor Author

Copilot AI commented Oct 25, 2025

@copilot more build errors

D:\a\Plugin.Maui.Audio\Plugin.Maui.Audio\src\Plugin.Maui.Audio\AudioPlayer\AudioPlayer.android.cs(197,19): error CS0030: Cannot convert type 'Java.Lang.Object' to 'Plugin...

Fixed the naming conflict. Changed all AudioManager references to Android.Media.AudioManager to avoid conflict with Plugin.Maui.Audio.AudioManager class.

Updated:

  • Field declaration: Android.Media.AudioManager? audioManager
  • All three constructor casts to use fully qualified type

Commit: 20bee77

Copilot finished work on behalf of jfversluis October 25, 2025 20:02
@jfversluis jfversluis marked this pull request as ready for review October 25, 2025 20:35
@jfversluis jfversluis requested a review from Copilot October 25, 2025 20:36
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR implements automatic audio focus handling for Android and iOS to properly manage audio playback during phone calls and other audio interruptions. The implementation follows platform best practices and provides developers with configurable options to control the behavior.

Key Changes:

  • Added automatic audio focus management for Android with configurable ManageAudioFocus option
  • Added automatic audio interruption handling for iOS/macOS with configurable HandleAudioInterruptions option
  • Both features are enabled by default but can be disabled for manual control

Reviewed Changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
AudioPlayerOptions.android.cs Added ManageAudioFocus property to enable/disable automatic audio focus management
AudioPlayerOptions.macios.cs Added HandleAudioInterruptions property to enable/disable automatic interruption handling
AudioPlayer.android.cs Implemented audio focus request/abandon logic and focus change listener
AudioPlayer.macios.cs Implemented audio session interruption notification handling
audio-player.md Documented the new audio focus and interruption handling features

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@jfversluis
Copy link
Owner

@copilot address comments

Copy link
Contributor Author

Copilot AI commented Oct 25, 2025

@copilot address comments

Addressed all code review comments:

iOS/macOS:

  • Extracted GetInterruptionType() and GetInterruptionOptions() helper methods for safer casting
  • Added null checks before casting NSNumber values

Android:

  • Extracted magic number to DuckingVolumeMultiplier constant
  • Added volumeBeforeDucking field to properly track volume state
  • Fixed volume restoration to restore the actual ducked volume instead of the field value
  • Added comment clarifying audio focus behavior during transient loss

Commit: 53ad368

Copilot finished work on behalf of jfversluis October 25, 2025 20:48
@jfversluis jfversluis requested a review from Copilot October 25, 2025 20:53
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@jfversluis
Copy link
Owner

@copilot address comments

Copy link
Contributor Author

Copilot AI commented Oct 25, 2025

@copilot address comments

Addressed code review comments:

iOS/macOS:

  • Added comment explaining why defaulting to Began is safer when interruption type cannot be determined

Android:

  • Extracted PauseInternal() helper method to eliminate code duplication
  • Pause() calls PauseInternal() then abandons audio focus
  • HandleAudioFocusChange() uses PauseInternal() for transient loss without abandoning focus

Note: The RequestAudioFocus() returning false for both "disabled" and "failed" is intentional - the caller only needs to know if focus was granted, and continues playing anyway for backward compatibility.

Commit: e653d18

Copilot finished work on behalf of jfversluis October 25, 2025 21:00
@jfversluis jfversluis requested a review from Copilot October 25, 2025 21:57
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@jfversluis
Copy link
Owner

@copilot address comments

Copy link
Contributor Author

Copilot AI commented Oct 25, 2025

@copilot address comments

Addressed all code review comments:

Android:

  • Extracted PlayInternal() helper method for core play logic
  • Play() calls PlayInternal() after requesting audio focus
  • HandleAudioFocusChange() uses PlayInternal() when resuming from transient loss (already have focus)
  • Added comment clarifying why state is reset before Stop() in permanent focus loss

iOS/macOS:

  • Changed to use public Play() method instead of player.Play() for consistent state management

Commit: 2d67a19

@jfversluis jfversluis requested a review from Copilot October 25, 2025 22:14
Copilot finished work on behalf of jfversluis October 25, 2025 22:15
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@jfversluis
Copy link
Owner

@copilot address comments

Copy link
Contributor Author

Copilot AI commented Oct 25, 2025

@copilot address comments

Addressed documentation comments:

Android:

  • Added XML documentation to AudioFocusChangeListener class

iOS/macOS:

  • Added XML documentation to GetInterruptionType() method with parameter, return value, and default behavior explanation
  • Added XML documentation to GetInterruptionOptions() method

Note: The RequestAudioFocus() return value is intentionally false when disabled. The caller logs a warning but continues playing for backward compatibility. Returning true would be misleading as it would imply focus was successfully acquired.

Commit: d03d365

Copilot finished work on behalf of jfversluis October 25, 2025 22:19
@jfversluis jfversluis requested a review from Copilot October 25, 2025 22:23
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

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.

[Request] Audio Should Pause When Phone Call Received

2 participants