mirror of
https://github.com/vector-im/element-call.git
synced 2026-02-11 04:27:03 +00:00
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:
@@ -320,16 +320,18 @@ export function useLivekit(
|
||||
|
||||
useEffect(() => {
|
||||
// Sync the requested devices with LiveKit's devices
|
||||
if (
|
||||
room !== undefined &&
|
||||
connectionState === ConnectionState.Connected &&
|
||||
!controlledAudioDevices
|
||||
) {
|
||||
if (room !== undefined && connectionState === ConnectionState.Connected) {
|
||||
const syncDevice = (
|
||||
kind: MediaDeviceKind,
|
||||
selected$: Observable<SelectedDevice | undefined>,
|
||||
): Subscription =>
|
||||
selected$.subscribe((device) => {
|
||||
logger.info(
|
||||
"[LivekitRoom] syncDevice room.getActiveDevice(kind) !== d.id :",
|
||||
room.getActiveDevice(kind),
|
||||
" !== ",
|
||||
device?.id,
|
||||
);
|
||||
if (
|
||||
device !== undefined &&
|
||||
room.getActiveDevice(kind) !== device.id
|
||||
@@ -344,7 +346,9 @@ export function useLivekit(
|
||||
|
||||
const subscriptions = [
|
||||
syncDevice("audioinput", devices.audioInput.selected$),
|
||||
syncDevice("audiooutput", devices.audioOutput.selected$),
|
||||
!controlledAudioDevices
|
||||
? syncDevice("audiooutput", devices.audioOutput.selected$)
|
||||
: undefined,
|
||||
syncDevice("videoinput", devices.videoInput.selected$),
|
||||
// Restart the audio input track whenever we detect that the active media
|
||||
// device has changed to refer to a different hardware device. We do this
|
||||
@@ -384,7 +388,7 @@ export function useLivekit(
|
||||
];
|
||||
|
||||
return (): void => {
|
||||
for (const s of subscriptions) s.unsubscribe();
|
||||
for (const s of subscriptions) s?.unsubscribe();
|
||||
};
|
||||
}
|
||||
}, [room, devices, connectionState, controlledAudioDevices]);
|
||||
|
||||
@@ -86,6 +86,14 @@ export function useMuteStates(isJoined: boolean): MuteStates {
|
||||
const audio = useMuteState(devices.audioInput, () => {
|
||||
return Config.get().media_devices.enable_audio && !skipLobby && !isJoined;
|
||||
});
|
||||
useEffect(() => {
|
||||
// If audio is enabled, we need to request the device names again,
|
||||
// because iOS will not be able to switch to the correct device after un-muting.
|
||||
// This is one of the main changes that makes iOS work with bluetooth audio devices.
|
||||
if (audio.enabled) {
|
||||
devices.requestDeviceNames();
|
||||
}
|
||||
}, [audio.enabled, devices]);
|
||||
const isEarpiece = useIsEarpiece();
|
||||
const video = useMuteState(
|
||||
devices.videoInput,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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> =
|
||||
|
||||
Reference in New Issue
Block a user