bluefireteam / audioplayers

A Flutter package to play multiple audio files simultaneously (Android/iOS/web/Linux/Windows/macOS)
https://pub.dartlang.org/packages/audioplayers
MIT License
2k stars 845 forks source link

iOS crash when setting duck #1491

Closed wujek-srujek closed 1 year ago

wujek-srujek commented 1 year ago

Checklist

Current bug behaviour

When the application wants to set ducking, it works on Android but crashes on iOS.

It seems like the category defaultToSpeaker is set by default, and setting the duck option in addition causes the crash.

Expected behaviour

The application doesn't crash on iOS and ducking works.

Steps to reproduce

Run the code included in the snippet.

Code sample

Code sample ```dart import 'dart:async'; import 'package:audioplayers/audioplayers.dart'; import 'package:flutter/material.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); await initPlayer(); runApp(Container()); } Future initPlayer() { return AudioPlayer.global.setAudioContext( AudioContextConfig(duckAudio: true).build(), ); } ```

Affected platforms

iOS

Platform details

iPhone 14, iOS 16.0 iPhone Xs, iOS 16.4.1 (a)

AudioPlayers Version

4.0.1

Build mode

debug, release

Audio Files/URLs/Sources

No need for sound files, the application crashes before that.

Screenshots

No response

Logs

Full Logs ``` 2023-05-06 14:51:54.377566+0200 Runner[717:47684] Metal API Validation Enabled 2023-05-06 14:51:54.534371+0200 Runner[717:47963] flutter: The Dart VM service is listening on http://127.0.0.1:54130/uL1ofWVhMDI=/ 2023-05-06 14:51:54.675303+0200 Runner[717:47684] [as_client] AVAudioSession_iOS.mm:2194 Error: category option 'defaultToSpeaker' is only applicable with category 'playAndRecord' 2023-05-06 14:51:54.675830+0200 Runner[717:47684] [as_client] AVAudioSession_iOS.mm:2370 Failed to set category, error: -50 2023-05-06 14:51:54.679279+0200 Runner[717:47945] [VERBOSE-2:dart_vm_initializer.cc(41)] Unhandled Exception: PlatformException(DarwinAudioError, Error configuring global audio session: Error Domain=NSOSStatusErrorDomain Code=-50 "(null)", null, null) #0 StandardMethodCodec.decodeEnvelope (package:flutter/src/services/message_codecs.dart:653:7) #1 MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:315:18) #2 initPlayer (package:time_it/main.dart:13:3) #3 main (package:time_it/main.dart:8:18) Message from debugger: Terminated due to signal 9 ```

Related issues / more information

No response

Working on PR

no way

wujek-srujek commented 1 year ago

Ok, the reason is that AudioContextConfig has forceSpeaker as the default, which is pretty surprising, as:

So, turning forceSpeakers to false fixes the crash, but it causes a different issue - when there is a BT speaker connected, sound from other app (Spotify in my case) gets quieter when my app plays its own sound, but then when my app is done, Spotify never goes back to the original volume. I can't make the combination BT speaker playing music from another app + my app playing sound over the speaker but ducking the other sound not work without side effects. Is this supported on iOS?

Gustl22 commented 1 year ago

Could you state a ios context configuration, that you would consider as default? See: https://pub.dev/documentation/audioplayers_platform_interface/latest/audioplayers_platform_interface/AudioContextIOS-class.html I expect something like this maybe:

AudioContextIOS(
      category: AVAudioSessionCategory.playAndRecord,
      options: [
          AVAudioSessionOptions.mixWithOthers, 
          AVAudioSessionOptions.defaultToSpeaker,
          AVAudioSessionOptions.allowBluetooth,
          AVAudioSessionOptions.allowBluetoothA2DP,
          AVAudioSessionOptions.allowAirPlay,
      ],
    )

Unfortunately I cannot really test any of these params, as I don't own a physical device.

wujek-srujek commented 1 year ago

The default for me would be the same exact behavior that we get when we don't specify any AudioContext in Dart at all. I don't know what it actually is, but the people who know the code should be able to quickly get the information. Maybe this means the platform default apply?

Currently, the constructors, when called without any parameters, result in different behavior, e.g. for Android they change the audioFocus to something other than null (see https://github.com/bluefireteam/audioplayers/issues/1495), and they are definitely not good on iOS as they cause a crash (due to setting the category to playback, but playAndRecord is needed). This makes it hard or even impossible to change the settings for one platform and keep the default for the other. This is definitely the case when I need to change them for iOS, as this forces me to set one for Android as well, and this in turn makes it impossible to revert to the default behavior (again, see https://github.com/bluefireteam/audioplayers/issues/1495).

Gustl22 commented 1 year ago

The default values for native ios are stated here: https://github.com/bluefireteam/audioplayers/blob/6cd5656c0c5deaab1fb4af78a5b7632402c3a1d3/packages/audioplayers_darwin/ios/Classes/AudioContext.swift#L9

The default values for ios context in dart are here: https://github.com/bluefireteam/audioplayers/blob/6cd5656c0c5deaab1fb4af78a5b7632402c3a1d3/packages/audioplayers_platform_interface/lib/src/api/audio_context.dart#L108

This should also be applied here: https://github.com/bluefireteam/audioplayers/blob/6cd5656c0c5deaab1fb4af78a5b7632402c3a1d3/packages/audioplayers_platform_interface/lib/src/api/audio_context_config.dart#L121

So the only difference would be changing AVAudioSessionCategory.playback to playAndRecord.

Gustl22 commented 1 year ago

Hmm the default for ios is playback and not playAndRecord. https://developer.apple.com/documentation/avfaudio/avaudiosession

We should maybe use playback as the default in the AudioContextIOS? I also could not reproduce the Error: category option 'defaultToSpeaker' is only applicable with category 'playAndRecord' although it would make sense. Maybe I need a real device so that it is thrown?

I could also imagine that defaultToSpeaker is only needed, because we also use playAndRecord, so we could just leave out both and it would not play from Earpiece, but don't actually know: https://developer.apple.com/documentation/avfaudio/avaudiosession/categoryoptions/1616462-defaulttospeaker

wujek-srujek commented 1 year ago

Yes you need a device. The sample starts just fine on the simulator but crashes on a real device 100% of the time.

We should maybe use playback as the default in the AudioContextIOS?

It currently is the default on in AudioContextIOS, but this doesn't work with the other default of enabling the defaultToSpeaker option, that's the whole issue here. The crash happens and it is documented that it is not allowed: https://developer.apple.com/documentation/avfaudio/avaudiosession/categoryoptions/1616462-defaulttospeaker#discussion (You can set this option only when using the playAndRecord category.) I'm guessing that's why the native default of audioplayers_ios is playAndRecord. All my PR does is it aligns the Dart default with it.

As for the defaults, AFAIK there are 3 of them:

  1. The platform defaults.
  2. The defaults that the native part of audioplayers uses when no other context is set. This one differs from the iOS defaults (playback vs playAndRecord and forcing speakers) and this must have been your decision at some point in the past.
  3. The Dart defaults, which differ from the iOS part, which is the issue here.

I think it is valid for your package to have other defaults than the platform (i.e. you chose to ignore 1. above), but they should be consistent between iOS and Dart, and this is violated. As far as what you want the defaults to be, you need to decide, your package.

Having playback as the default category, and NOT specifying defaultToSpeaker is exactly what I'm doing, and it's working fine. However, it has its own set of issues if you do this in the library:

As you can see, changing the default is a much more involved task than just making this consistent across iOS and Dart (my PR). But I am all for removing the default of forcing to speaker, I don't understand why you want this, and according to my tests it doesn't even work anyway (or maybe I don't understand what it should do?).

Gustl22 commented 1 year ago

Forcing to speaker fixes this issue: #1194, but don't know it would have been fixed by just using playback as default.