Skip to content

feat: trying to disable audio context #257

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Jun 28, 2025
Merged

feat: trying to disable audio context #257

merged 17 commits into from
Jun 28, 2025

Conversation

alnitak
Copy link
Owner

@alnitak alnitak commented Jun 23, 2025

Description

This PR disables the audio context and device routing of miniaudio, allowing developers to use a third-party plugin such as audio_session instead.

A new example has been added: example/lib/audio_context/audio_context.dart.

This has been tested and is working on Android. For example, after running the app and starting a YouTube video, the music is paused as expected. When an event arrives:

  • switching to the YouTube app and playing a video triggers an AudioInterruptionType.unknown event with event.begin=true, but no corresponding event.begin=false.
  • receiving a notification sound or a phone call triggers an AudioInterruptionType.duck event with event.begin=true.

The purpose of the unpause button in the example:

The YT app (and I think other music apps I tested on Android) takes ownership of the audio context when playing. The audio_session plugin, in this case, grabs the AudioInterruptionType.unknown which just pause the playback here in the example, but the event.begin=false of this unknown event is not fired. TL;DR the sound in the example remains paused (in the audio_session example, the author does nothing in that case).

This means that if you press the unpause button and you hear the sound, it is working!! Because, without the PR, in that case, you had to call again soloud.ini().


On Android, I wasn't able to trigger AudioInterruptionType.pause. YouTube Music, YouTube Video, and other music apps seem to only trigger AudioInterruptionType.unknown.

Unfortunately, I don’t have access to a real iOS device to test this. If anyone is able to try it out on iOS, I’d greatly appreciate your feedback.

For those who wish to try this out, the core part to look at is in miniaudio_init.

Fixes #104, fixes #245, fixes #249


For anyone who would like to help test in their app, please use the reference below in your pubspec.yaml:

  audio_flux:
    git:
      url: https://github.com/alnitak/flutter_soloud.git
      ref: audioContext

Testing by running the example, you could try:

  • play the asset
  • open a music app or YouTube and play something
  • the example app should pause with unknown state
  • by pressing the unpause button, the example sound must play again

or

  • play the asset
  • receive a notification (ie with WhatsApp) or a phone call
  • the example app should pause
  • after the notification sound or after the phone call, the asset must automatically unpause

Type of Change

  • ✨ New feature (non-breaking change which adds functionality)
  • 🛠️ Bug fix (non-breaking change which fixes an issue)
  • ❌ Breaking change (fix or feature that would cause existing functionality to change)
  • 🧹 Code refactor
  • ✅ Build configuration change
  • 📝 Documentation
  • 🗑️ Chore

@MinseokKang003
Copy link
Contributor

ScreenRecording_06-25-2025.16-58-14_1.MP4
ScreenRecording_06-25-2025.16-59-36_1.MP4

Hey @alnitak thanks for the clarification.
I've tested out two cases on my iPhone 13 Pro:
[play asset] -> [Interruption] -> [play asset] -> [unpause]
[play asset] -> [Interruption] -> [unpause]

But in both cases the audio does not regain after interruption.
Let me know if I need to do the other way.

@alnitak
Copy link
Owner Author

alnitak commented Jun 25, 2025

I have pushed a commit that may solve this. I think you will need to flutter clean your project.

On iOS, when unknown interruption is triggered, the device is stopped (not uninitialized). I have forced it to start again. This was working in my older tests in another PR, but I am unsure if it will now. I have also added await session.setActive(true); in the unpause button.

So, you can now try again to:
[play asset] -> [Interruption] -> [unpause]

Please, can you also try to check the duck event?

  • [play asset]
  • [notification sound] the asset volume should duck (the volume should be lowered until the notification sound ends).

Thanks!

@MinseokKang003
Copy link
Contributor

MinseokKang003 commented Jun 25, 2025

ScreenRecording_06-25-2025.17-58-04_1.1.mp4

I've run flutter clean and tested it again.
Still doesn't work, but if you see the recording I found when I background my app the audio starts playing again.
Maybe the interruption ended notification is not being called at the right time.

Logs:

flutter: audio_context: interruption begin: true
flutter: audio_context: interruption type: AudioInterruptionType.unknown
[App goes background]
flutter: audio_context: interruption begin: false
flutter: audio_context: interruption type: AudioInterruptionType.unknown

I'll try the duck event soon.
Thanks!

@alnitak
Copy link
Owner Author

alnitak commented Jun 25, 2025

I'm really sorry I didn't get the chance to try it out myself. Anyway, looking at your recording, I think the audio engine has not been disposed, hence it doesn't need to be initialized again as before this PR.

When you press the unpause button, it seems that the play starts where it was interrupted by the interruption. This made me think that the audio context is not the same as before. Maybe worth trying adding await session.setActive(false); after the soloud.setPause(soundHandle!, true); in the event.begin of the unknown event.

I am not sure, and I am not very familiar with audio_session and how it works, but maybe now we have to look for something on the Flutter side, like the app life cycle, where, for example, try putting an await session.setActive(true); in the app resumed.

Maybe @adventureisyou or @sbauly can give us some insights?

@MinseokKang003
Copy link
Contributor

MinseokKang003 commented Jun 26, 2025

@alnitak I'm not familiar with audio systems at all but as I understand, the problem is ma_device_start not being called properly on audio playback, if ma_device_stop has been called by miniaudio's AVAudioSessionInterruptionTypeBegan event.

The problem on iOS is that AVAudioSessionInterruptionTypeEnded event is not guaranteed to be called, so we can't rely on that to call ma_device_start.

It may work if there is some way we could check, every time on soloud.play or soloud.setPause, if the device has been stopped and if so, we call ma_device_start explicitly.

For my use case the PR's current state works great because I'm using SoLoud for sound effects which mix with other audios. It's great that I'm given explicit control over AVAudioSession using audio_session, which I'm using like:

await session.configure(
  const AudioSessionConfiguration(
    avAudioSessionCategory: AVAudioSessionCategory.playback,
    avAudioSessionCategoryOptions:
        AVAudioSessionCategoryOptions.mixWithOthers,
  ),
);

Please feel free to let me know if you need more tests.

@MinseokKang003 MinseokKang003 mentioned this pull request Jun 26, 2025
7 tasks
@MinseokKang003
Copy link
Contributor

@alnitak I've opened #260 which implements my suggestion in my last comment. Tested on iPhone 13 Pro. Feel free to check! (I don't have much experience in posting PRs so sorry if this is the wrong way).

@alnitak
Copy link
Owner Author

alnitak commented Jun 27, 2025

@MinseokKang003 I have merged your PR and updated the example a bit.

Could you please confirm that running the example and doing what is described in the first post under the "Testing by running the example" is working for you? Thank you very much!

@MinseokKang003
Copy link
Contributor

interruption-1.MP4
interruption-2.MP4
interruption-3.mp4

To test audio interruption in iOS, we need to set AVAudioSessionCategoryOptions.none for audio session configuration like below. If we use AVAudioSessionCategoryOptions.mixWithOthers we won't receive interruption notifications and the audio will always mix with other app's audio.

avAudioSessionCategory: AVAudioSessionCategory.playback,
avAudioSessionCategoryOptions: AVAudioSessionCategoryOptions.none,

Everything seems to work fine here.
Please let me know if you need more tests for other specific cases.

@alnitak
Copy link
Owner Author

alnitak commented Jun 27, 2025

@MinseokKang003 this is great!! I am very grateful, thank you!

I think that, for now, we are ready to go. All the audio context seems to be managed only by audio_session now. Hoping the best 🤞

@MinseokKang003
Copy link
Contributor

Yup no problems so far on my side. Feel free to merge! It's great to contribute.

@themailman05
Copy link
Contributor

+1 nice job

@themailman05
Copy link
Contributor

Yup no problems so far on my side. Feel free to merge! It's great to contribute.

were you able to get this to work on iOS side? I'm not able to get playback still when implementing audio_session for session management.

@themailman05
Copy link
Contributor

themailman05 commented Jun 27, 2025

I just tried to flutter clean and rebuild just in case and it still isn't working. @MinseokKang003 were you able to demonstrate recording + playback using flutter_recorder on iOS?

@alnitak alnitak marked this pull request as ready for review June 28, 2025 09:39
@adventureisyou
Copy link

adventureisyou commented Jun 28, 2025

Hi. Sorry for the silence on my part, it's been a busy week! Thanks for all your work on this.

I've just tried using this branch for our app, and it seems to accomplish two things:

  1. Avoid setting the audio category on launch.
  2. Avoid starting/stopping audio sessions.

The first solves my issue of spotify being interrupted on startup. I'm now able to set my own audio category to ambient and it doesn't get overridden by miniaudio. This means YouTube/Spotify don't get interrupted when opening the app.

INCORRECT SEE NEXT COMMENT
The second causes a bit of a headache. It works, and littering my code with all the manual _session.setActive(true) and _session.interruptionEventStream.listen calls necessary works, but feels like a lot of unnecessary work when it worked before. audio_session actually says that it expects the plugins to take care of starting the audio session when playing anything:

Each time you invoke an audio plugin to play audio, that plugin will activate your app's shared audio session to inform the operating system that your app is now actively playing audio.
...
You normally do not need to activate the audio session yourself, however if the audio plugin you use does not activate the audio session, you can activate it yourself...

I think this should only be merged if an option is exposed to control the start/stop session behaviour (or you do a major version bump with a breaking change notice). Separating out the first change and getting it merged would be my preference because it's a much less aggressive change. Users not managing audio category themselves will change from mediaPlayback to soloAmbient (the default), which isn't a huge change. Alternatively, this could be exposed as an option too.

Finally, I think I need to apologise for conflating my startup focus issue with the original reports of audio session interruptions causing problems. My experiece is that even without this change, I'm unable to repro any of the symptoms in the original ticket. Both Soloud.play() and Soloud.setPause(false) cause the audio session to be grabbed again after an interruption. Sorry!

Of course, this PR gives people the freedom to manage the audio session themselves, but like I say above it feels like a tool most users don't need and thus should probably be behind a setting.

@adventureisyou
Copy link

Gahh - maybe I'm talking rubbish. I tried this branch again without manual audio session management and it seems to work fine - though I don't understand how.

@adventureisyou
Copy link

OH - the noAudioSessionActivate only affects initialisation. I guess play() and setPause() still do their thing of grabbing the session. In which case, everything works!! Sorry for sowing confusion. So many combinations to test I think I just got myself confused.

@alnitak
Copy link
Owner Author

alnitak commented Jun 28, 2025

Your comment appeared just 2 seconds before I wanted to hit the "Merge" button 😄 !

Anyway, no problem at all, you are welcome to tell me your point of view and suggestions!!

Agreed with the fact that there are a lot of combinations to test and cannot be automated (I think), and I can't tell how many times I got confused too :)

If you need some more time to test your use cases, please tell me. I have no problem waiting to merge. I'd prefer to be sure that all is working fine. If you are just fine with this PR, I can merge and publish on pub.dev now. Take all the time!

@adventureisyou
Copy link

Hah, sorry. I was writing it while testing and then saw you merge master back in and thought I'd post it quickly to stop you merging. Then realised I was wrong!

I'm very happy, let's merge! Thanks again.

@alnitak alnitak merged commit 56b7c72 into main Jun 28, 2025
1 check passed
@alnitak alnitak deleted the audioContext branch June 28, 2025 11:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants