Fix bluetooth iOS issue (device auto switches from Bluetooth to speaker) (#3383)

* no onAudioDeviceSelect

Signed-off-by: Timo K <toger5@hotmail.de>

* dont restart audio track on ios

Signed-off-by: Timo K <toger5@hotmail.de>

* skip syncing audio input on ios

Signed-off-by: Timo K <toger5@hotmail.de>

* dont pass audio capture defaults when muting unmuting

Signed-off-by: Timo K <toger5@hotmail.de>

* logging on set sink id

Signed-off-by: Timo K <toger5@hotmail.de>

* dont use controlled on ios

Signed-off-by: Timo K <toger5@hotmail.de>

* always show native button on ios

Signed-off-by: Timo K <toger5@hotmail.de>

* revert "dont use controlled on ios"

Signed-off-by: Timo K <toger5@hotmail.de>

* use "" instead of undefined for audio output default and on syncDevice + logging

Signed-off-by: Timo K <toger5@hotmail.de>

* use logger warn

Signed-off-by: Timo K <toger5@hotmail.de>

* dont ignore all sync logic

Signed-off-by: Timo K <toger5@hotmail.de>

* use "default" instead of ""

Signed-off-by: Timo K <toger5@hotmail.de>

* use non controlled audio output again

Signed-off-by: Timo K <toger5@hotmail.de>

* controlled with syncing but no output syncing

Signed-off-by: Timo K <toger5@hotmail.de>

* subscribe to available raw output in controlled case

Signed-off-by: Timo K <toger5@hotmail.de>

* simplification but same solution

Signed-off-by: Timo K <toger5@hotmail.de>

* try "mute with audio capture defaults" again

Signed-off-by: Timo K <toger5@hotmail.de>

* use current device for mute unmute

Signed-off-by: Timo K <toger5@hotmail.de>

* add back restart

Signed-off-by: Timo K <toger5@hotmail.de>

* use default device for unmute again

Signed-off-by: Timo K <toger5@hotmail.de>

* no defaults at all

Signed-off-by: Timo K <toger5@hotmail.de>

* device names request after unmute

Signed-off-by: Timo K <toger5@hotmail.de>

* another approach for mute state request

Signed-off-by: Timo K <toger5@hotmail.de>

* more comment

Signed-off-by: Timo K <toger5@hotmail.de>

* reduce changes and switch input device on sync device

Signed-off-by: Timo K <toger5@hotmail.de>

* more cleanup

Signed-off-by: Timo K <toger5@hotmail.de>

* fix lints

Signed-off-by: Timo K <toger5@hotmail.de>

---------

Signed-off-by: Timo K <toger5@hotmail.de>
This commit is contained in:
Timo
2025-07-03 17:14:09 +02:00
committed by GitHub
parent ca06aa82b2
commit c3eb253059
4 changed files with 38 additions and 11 deletions

View File

@@ -1276,7 +1276,7 @@ export class CallViewModel extends ViewModel {
(available, selected) => {
const selectionType = selected && available.get(selected.id)?.type;
// If we are in any output mode other than spaeker switch to speaker.
// If we are in any output mode other than speaker switch to speaker.
const newSelectionType =
selectionType === "speaker" ? "earpiece" : "speaker";
const newSelection = [...available].find(

View File

@@ -264,6 +264,15 @@ class AudioOutput
class ControlledAudioOutput
implements MediaDevice<AudioOutputDeviceLabel, SelectedAudioOutputDevice>
{
// We need to subscribe to the raw devices so that the OS does update the input
// back to what it was before. otherwise we will switch back to the default
// whenever we allocate a new stream.
public readonly availableRaw$ = availableRawDevices$(
"audiooutput",
this.usingNames$,
this.scope,
);
public readonly available$ = combineLatest(
[controlledAvailableOutputDevices$.pipe(startWith([])), iosDeviceMenu$],
(availableRaw, iosDeviceMenu) => {
@@ -311,14 +320,17 @@ class ControlledAudioOutput
},
).pipe(this.scope.state());
public constructor(private readonly scope: ObservableScope) {
public constructor(
private readonly usingNames$: Observable<boolean>,
private readonly scope: ObservableScope,
) {
this.selected$.subscribe((device) => {
// Let the hosting application know which output device has been selected.
// This information is probably only of interest if the earpiece mode has
// been selected - for example, Element X iOS listens to this to determine
// whether it should enable the proximity sensor.
if (device !== undefined) {
logger.info("[controlled-output] setAudioDeviceSelect called:", device);
logger.info("[controlled-output] onAudioDeviceSelect called:", device);
window.controls.onAudioDeviceSelect?.(device.id);
// Also invoke the deprecated callback for backward compatibility
window.controls.onOutputDeviceSelect?.(device.id);
@@ -327,6 +339,9 @@ class ControlledAudioOutput
this.available$.subscribe((available) => {
logger.info("[controlled-output] available devices:", available);
});
this.availableRaw$.subscribe((availableRaw) => {
logger.info("[controlled-output] available raw devices:", availableRaw);
});
}
}
@@ -393,7 +408,7 @@ export class MediaDevices {
AudioOutputDeviceLabel,
SelectedAudioOutputDevice
> = getUrlParams().controlledAudioDevices
? new ControlledAudioOutput(this.scope)
? new ControlledAudioOutput(this.usingNames$, this.scope)
: new AudioOutput(this.usingNames$, this.scope);
public readonly videoInput: MediaDevice<DeviceLabel, SelectedDevice> =