-
-
Notifications
You must be signed in to change notification settings - Fork 89
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
[Question] How to set output audio device? #39
Comments
This plugin exposes the AVAudioSession API on iOS and the AudioManager API on Android (via the class |
I check couple SO posts, I read that it's not possible on iOS but it was an old answer. Anyway, I solve the iOS part by simple opening AirPlay. With this package flutter_to_airplay. Right now I'm looking to trigger to open this media view(native device picker view) on Android within the app. Since Back to the topic, for programmatically changing the audio route, I've checked this SO post. I was looking for simple API call, like |
Casting is another matter altogether, but you can take a look at audio_cast for the Android side of things. As for the traditional audio devices, there are various answers on S/O: |
I have not found any working option to list and change the audio output routes on iOS, apparently Apple does not want this to be modified programmatically. I wonder how Spotify can display their own customized view to pick the audio output. |
Some more info on iOS: https://developer.apple.com/forums/thread/62954 It looks like you can programmatically set a Bluetooth input device, but you can't programmatically set a Bluetooth output device (unless that device is ALSO an input device, e.g. headset with a mic). For an output-only device, it needs to be done through a UI widget, so someone else would need to create a package for that to handle that use case. |
On Android, the typically recommended solution involves deprecated methods. I think I may need to eventually implement the newer API (probably using the new JNIgen): https://developer.android.com/reference/androidx/mediarouter/media/package-summary |
Also suggestions from @Peng-Qian in #95 which I have folded into this issue. On the Android side, it seems to align with the usual S/O advice (and hence should probably be done with the newer |
Here is an implementation of the suggested solutions (completely untested): final _androidAudioManager =
!kIsWeb && Platform.isAndroid ? AndroidAudioManager() : null;
final _avAudioSession = !kIsWeb && Platform.isIOS ? AVAudioSession() : null;
Future<bool> switchToSpeaker() async {
if (_androidAudioManager != null) {
await _androidAudioManager!.setMode(AndroidAudioHardwareMode.normal);
await _androidAudioManager!.stopBluetoothSco();
await _androidAudioManager!.setBluetoothScoOn(false);
await _androidAudioManager!.setSpeakerphoneOn(true);
} else if (_avAudioSession != null) {
await _avAudioSession!
.overrideOutputAudioPort(AVAudioSessionPortOverride.speaker);
}
return true;
}
Future<bool> switchToReceiver() async {
if (_androidAudioManager != null) {
_androidAudioManager!.setMode(AndroidAudioHardwareMode.inCommunication);
_androidAudioManager!.stopBluetoothSco();
_androidAudioManager!.setBluetoothScoOn(false);
_androidAudioManager!.setSpeakerphoneOn(false);
return true;
} else if (_avAudioSession != null) {
return await _switchToAnyIosPortIn({AVAudioSessionPort.builtInMic});
}
return false;
}
Future<bool> switchToHeadphones() async {
if (_androidAudioManager != null) {
_androidAudioManager!.setMode(AndroidAudioHardwareMode.inCommunication);
_androidAudioManager!.stopBluetoothSco();
_androidAudioManager!.setBluetoothScoOn(false);
_androidAudioManager!.setSpeakerphoneOn(false);
return true;
} else if (_avAudioSession != null) {
return await _switchToAnyIosPortIn({AVAudioSessionPort.headsetMic});
}
return true;
}
Future<bool> switchToBluetooth() async {
if (_androidAudioManager != null) {
await _androidAudioManager!
.setMode(AndroidAudioHardwareMode.inCommunication);
await _androidAudioManager!.startBluetoothSco();
await _androidAudioManager!.setBluetoothScoOn(true);
return true;
} else if (_avAudioSession != null) {
return await _switchToAnyIosPortIn({
AVAudioSessionPort.bluetoothLe,
AVAudioSessionPort.bluetoothHfp,
AVAudioSessionPort.bluetoothA2dp,
});
}
return false;
}
Future<bool> _switchToAnyIosPortIn(Set<AVAudioSessionPort> ports) async {
if ((await _avAudioSession!.currentRoute)
.outputs
.any((r) => ports.contains(r.portType))) {
return true;
}
for (var input in await _avAudioSession!.availableInputs) {
if (ports.contains(input.portType)) {
await _avAudioSession!.setPreferredInput(input);
}
}
return false;
} On Android, I did not provide the |
Just a heads up that I'm getting OSStatus error -50 when attempting to use the code above (though it does compile and looks like it's trying to do the right thing). When I print out the list of devices on my iPhone I only get (id, name, type.name) the below (even though spotify is currently playing on a bluetooth speaker). I'm guessing the app has to break out to a native chooser to connect a bluetooth / airplay output to the current app or something, as suggested above. flutter: iPhone Microphone, Built-In Microphone, builtInMic |
@ryanheise I used |
Are you saying that it is routing to the correct hardware route but just that the audio is partial? I'm not sure if there is anything I can do in audio_session to address that. |
@ryanheise Kindly testing functions of audio_session with new android versions(Android 13, 14) if you can. Many thanks! |
I still don't understand what you mean since you didn't answer my question. But aside from that, I don't actually see there is anything I can do in audio_session to address that. After all, the API you are using simply passes through to the operating system. The |
@ryanheise |
Also don't forget to await the call to await setMode(...);
await Future.delayed(Duration(seconds: 1)); // experiment as needed
await playSound(); |
|
This is the behaviour of the operating system. As suspected above, there isn't anything I can do to change the way the operating system works. |
this works for me on ios: Future<bool> switchToSpeaker() async {
if (_androidAudioManager != null) {
await _androidAudioManager.setMode(AndroidAudioHardwareMode.normal);
await _androidAudioManager.stopBluetoothSco();
await _androidAudioManager.setBluetoothScoOn(false);
await _androidAudioManager.setSpeakerphoneOn(true);
} else if (_avAudioSession != null) {
await _avAudioSession
.overrideOutputAudioPort(AVAudioSessionPortOverride.speaker);
}
return true;
}
Future<bool> switchToReceiver() async {
if (_androidAudioManager != null) {
await _androidAudioManager
.setMode(AndroidAudioHardwareMode.inCommunication);
await _androidAudioManager.stopBluetoothSco();
await _androidAudioManager.setBluetoothScoOn(false);
await _androidAudioManager.setSpeakerphoneOn(false);
return true;
} else if (_avAudioSession != null) {
await _avAudioSession
.overrideOutputAudioPort(AVAudioSessionPortOverride.none);
return _switchToAnyIosPortIn({AVAudioSessionPort.builtInMic});
}
return false;
}
Future<bool> switchToBluetooth() async {
if (_androidAudioManager != null) {
await _androidAudioManager
.setMode(AndroidAudioHardwareMode.inCommunication);
await _androidAudioManager.startBluetoothSco();
await _androidAudioManager.setBluetoothScoOn(true);
return true;
} else if (_avAudioSession != null) {
return _switchToAnyIosPortIn({
AVAudioSessionPort.bluetoothLe,
AVAudioSessionPort.bluetoothHfp,
AVAudioSessionPort.bluetoothA2dp,
});
}
return false;
}
Future<bool> _switchToAnyIosPortIn(Set<AVAudioSessionPort> ports) async {
for (final input in await _avAudioSession!.availableInputs) {
if (ports.contains(input.portType)) {
await _avAudioSession.setPreferredInput(input);
}
}
return false;
} tested with this config. await _avAudioSession?.setCategory(
AVAudioSessionCategory.playAndRecord,
AVAudioSessionCategoryOptions.allowBluetooth,
AVAudioSessionMode.spokenAudio,); We are planning to use this plugin for video calls. We are tried to use flutter-webrtc which is also includes audio output changing functionality but this works much better and i found this plugin more flexible. There are a lot of non-obvious things in the ios avaudiosession api. For example, in some cases to change the output device you need to set preffered input device etc. I will try Android part later and come back with feedback. Can we expect this functionality to be added to plugin if everything works? |
Awesome! I appreciate your experimentation here, and yes I would definitely consider something like this being integrated into the main |
The Android part also works. There are few cases that i haven't tested. These are different configs for ios, and usb/jack headphones, because i don't have such devices. I will let you know if I encounter any issues |
Has anyone tried the solution provided by @zombie6888 and @ryanheise (thank you!) and found that, on Android, it does indeed work, but something ends up changing the audio output behind the scenes? For example, I start out with "speaker" output, then change to "receiver", but after a short period (10 or 20 seconds, maybe?) it switches back to "speaker". Ideas on what might be going on? |
Do you have this permission in your AndroidManifest.xml? <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> |
No, we don't have that permission, and, when added, doesn't change the behavior. We do have FWIW, a workaround that seems to "fix" the issue is to always invoke the desired function to select output just before playing an audio file. |
@zombie6888 |
I am not an expert but i think it's impossible do at the same time with the same audiosession. When you switch to built-in mic, the output device will also be changed to built-in earpiece. As far as i know this is due to legacy avaudiosession api. When I consulted with iOS developer, he mentioned that there are more advanced and modern APIs for working with audio in iOS but i didn't try them. |
Is it possible to select the output device on web? |
Thanks to this package, we can get audio devices as a Future or Stream. I'm trying to cast audio to one of those devices. I couldn't figure out how I can do it. Is it possible with this package?
The text was updated successfully, but these errors were encountered: