Describe the bug
Hi team,
I would like to report an issue we're encountering when loading the room on iOS browsers - we can replicate this issue on an iPhone 15 PRO.
What i'm expecting
In iOS the phone selects the audio output device for me by default. I expect the audio sound to match the selected device - e.g. if the speaker is selected by default, i expect the sound to come from the speaker, not from the receiver.
What happens instead
On an iPhone 15 Pro the selected device as pointed out by the useMediaDeviceSelect react hook is "Default- Speaker". However the user hears the sound coming from the ear "Receiver". Being on iOS we have no option to manually select another audio output device using the livekit react hook mentioned above. Similarly on Safari browser on MacOS.
Worth mentioning:
- I started out with the sound coming out of the ear piece receiver as usual; the audio output device is always fixed to speaker
- I tried to disable the mic access in the browser, then the audio output switched to the correct audio device (the speaker at the bottom of the device)
- When i turned them mic access on again, it stayed to be the correct speaker.
So basically turning the mic access off and on in the browser fixes the problem somehow
I noticed there is no plan to support the Speaker Selection API for iOS 26 #1568 in the javascript client sdk. I would like to ask for your advice on mitigating this issue. Is there anything we can do about this on iOS browsers?
Thank you in advance for any potential input on this matter.
Reproduction
- Nodejs v24.13.0
- Dependencies: React Router in framework mode, livekit client sdk js, livekit react components
"dependencies": {
"@livekit/components-react": "2.9.20",
"livekit-client": "2.17.2",
"react-router": "7.13.1",
}
import { Room } from "livekit-client";
import { useLifecycles } from "react-use";
import * as React from "react";
// Hook to load the room
export function useLoadRoom({
serverUrl,
token,
}: {
serverUrl: string;
token: string;
}) {
const [isLoading, setIsLoading] = React.useState(true);
const isConnected = React.useRef(false);
const [room] = React.useState(
() =>
new Room({
adaptiveStream: true,
dynacast: true,
disconnectOnPageLeave: false,
}),
);
// Connect when mount and disconnect when unmounting, taking into account React Strict Mode behavior
useLifecycles(
() => {
const connect = async () => {
try {
await room.connect(serverUrl, token);
isConnected.current = true;
} catch (error) {
// error handling here
} finally {
setIsLoading(false);
}
};
if (!isConnected.current) {
connect();
}
},
() => {
if (isConnected.current) {
room.disconnect();
}
},
);
return { room, isLoading, error };
}
function isValidDeviceId(
deviceId: string | null | undefined,
): deviceId is string {
return !!deviceId && deviceId !== "default";
}
async function enableMedia(
room: Room,
{
cameraDeviceId,
microphoneDeviceId,
enableMicrophone = true,
}: {
cameraDeviceId: string | null;
microphoneDeviceId: string | null;
enableMicrophone?: boolean;
},
) {
const cameraOptions = isValidDeviceId(cameraDeviceId)
? { deviceId: cameraDeviceId }
: undefined;
const microphoneOptions = isValidDeviceId(microphoneDeviceId)
? { deviceId: microphoneDeviceId }
: undefined;
try {
await room.localParticipant.setCameraEnabled(true, cameraOptions);
} catch (error) {
...
}
if (enableMicrophone) {
try {
await room.localParticipant.setMicrophoneEnabled(true, microphoneOptions);
} catch (error) {
...
}
}
try {
await room.startAudio();
} catch (error) {
...
}
}
type LocalMediaState = {
status: "idle" | "pending" | "success" | "error";
error: Error | null;
};
/**
* Hook to manage local media (camera and microphone) enablement.
* Auto-enables when autoEnable is true (default). Use retry for manual re-enablement.
*/
export function useLocalMedia(
room: Room,
{ enableMicrophone = true, autoEnable = true } = {},
) {
const { selectedCameraId, selectedMicrophoneId } = useDeviceIds();
const { clearDeviceIds } = useInterviewFlowActions();
const [state, setState] = React.useState<LocalMediaState>({
status: "idle",
error: null,
});
const enable = React.useEffectEvent(async () => {
if (state.status === "pending") return;
setState({ status: "pending", error: null });
try {
await enableMedia(room, {
cameraDeviceId: selectedCameraId,
microphoneDeviceId: selectedMicrophoneId,
enableMicrophone,
});
setState({ status: "success", error: null });
} catch (err) {
// Only clear device IDs if the error indicates an invalid/unavailable device.
// Preserve device selection for non-device errors (e.g., permission denied, autoplay blocked).
if (isDeviceError(err)) {
clearDeviceIds();
}
setState({ status: "error", error: err instanceof Error ? err : null });
}
});
React.useEffect(() => {
if (autoEnable && state.status === "idle") {
enable();
}
}, [autoEnable, state.status]);
return { state: state.status, error: state.error, retry: enable };
}
Logs
System Info
iOS, iOS Safari, iOS Chrome, MacOS Safari
Severity
blocking an upgrade
Additional Information
No response
Describe the bug
Hi team,
I would like to report an issue we're encountering when loading the room on iOS browsers - we can replicate this issue on an iPhone 15 PRO.
What i'm expecting
In iOS the phone selects the audio output device for me by default. I expect the audio sound to match the selected device - e.g. if the speaker is selected by default, i expect the sound to come from the speaker, not from the receiver.
What happens instead
On an iPhone 15 Pro the selected device as pointed out by the
useMediaDeviceSelectreact hook is "Default- Speaker". However the user hears the sound coming from the ear "Receiver". Being on iOS we have no option to manually select another audio output device using the livekit react hook mentioned above. Similarly on Safari browser on MacOS.Worth mentioning:
So basically turning the mic access off and on in the browser fixes the problem somehow
I noticed there is no plan to support the Speaker Selection API for iOS 26 #1568 in the javascript client sdk. I would like to ask for your advice on mitigating this issue. Is there anything we can do about this on iOS browsers?
Thank you in advance for any potential input on this matter.
Reproduction
Logs
System Info
Severity
blocking an upgrade
Additional Information
No response