How to toggle/force speaker phone in react-native-webrtc?

91 Views Asked by At

I've looked through countless guides and articles about react-native-webrtc and, surprisingly, I was not able to find a solution to my problem. Everything else works just fine (audio calls and video calls), however for some reason, during video calls specifically, the audio is played back through the built-in earpiece, not the speaker.

I've found some other solutions for 3rd party libraries, such as setSpeakerPhoneOn or setForceSpeakerPhoneOn from react-native-incall-manager, but I don't want to install a whole separate library for just one feature. I've tried looking into their source code, but there was little that I could gather that would help me.

How can I toggle the speaker with only the react-native-webrtc library?

Here is how the caller's call is initiated:

const CallerVideoScreen = React.memo(() => {
    const room = useUnit($room);
    const iceCandidates = useUnit($iceCandidates) // empty array until target user gathers all candidates from their side;
    const turnCredentials = useUnit($turnCredentials) // generate on my own ICE server;

    const [localStream, setLocalStream] = React.useState(null);
    const [remoteStream, setRemoteStream] = React.useState(null);
    const [cachedLocalPC, setCachedLocalPC] = React.useState(null);

    React.useEffect(() => {
        setScreenHeaderComponent({
            payload: {
                screenHeaderConfig: {
                    component: null,
                    props: null,
                    previous: {
                        component: EScreenHeaderComponentName.ChatHeader,
                        props: {},
                    },
                },
            },
        });

        startLocalStream();
    }, []);

    React.useEffect(() => {
        if (localStream && room?.id && turnCredentials) {
            startCall();

            return () => {
                setLocalStream(null);
                setRemoteStream(null);
                setCachedLocalPC(null);
            };
        }
    }, [localStream, room?.id, turnCredentials]);

    const startLocalStream = async () => {
        const devices = await mediaDevices.enumerateDevices();

        const videoSourceId = devices.find(
            (device) => device.kind === 'videoinput' && device.facing === 'front',
        );

        if (videoSourceId) {
            const newStream = await mediaDevices.getUserMedia({
                audio: true,
                video: {
                    mandatory: {
                        minWidth: 500,
                        minHeight: 300,
                        minFrameRate: 30,
                    },
                    facingMode: 'user',
                    optional: videoSourceId ? [{ sourceId: videoSourceId }] : [],
                },
            });

            setLocalStream(newStream);
        }
    };

    const startCall = async () => {
        if (localStream) {
            const localPC = new RTCPeerConnection(
                iceServerConfig({ turnCredentials }),
            );

            localStream.getTracks().forEach((track) => {
                localPC.addTrack(track, localStream);
            });

            localPC.addEventListener('icecandidate', (e) => {
                if (!e.candidate) {
                    return;
                }

                // store candidate
                storeIceCandidate({ payload: e.candidate.toJSON() });
            });

            localPC.ontrack = (e) => {
                const newStream = new MediaStream();
                e.streams[0].getTracks().forEach((track) => {
                    newStream.addTrack(track);
                });

                setRemoteStream(newStream);
            };

            const offer = await localPC.createOffer({});
            await localPC.setLocalDescription(offer);

            // signal offer to target user
            updateCallRoomOffer({ payload: { offer } });

            setCachedLocalPC(localPC);
        }
    };

    React.useEffect(() => {
        if (cachedLocalPC && room?.answer) {
            if (!cachedLocalPC.currentRemoteDescription) {
                // intercept answer to offer and set description
                const rtcSessionDescription = new RTCSessionDescription(room.answer);
                cachedLocalPC.setRemoteDescription(rtcSessionDescription);
            } else {
                // log error
            }
        }
    }, [cachedLocalPC, room?.answer]);

    React.useEffect(() => {
        if (cachedLocalPC && iceCandidates.length) {
            iceCandidates.forEach(({ candidate, sdpMLineIndex, sdpMid }) => {
                cachedLocalPC.addIceCandidate(
                    new RTCIceCandidate({ candidate, sdpMLineIndex, sdpMid }),
                );
            });
        }
    }, [cachedLocalPC, iceCandidates]);

    return (
        <RTCView
            style={styles.rctView}
            streamURL={remoteStream?.toURL() ?? ''}
            objectFit={'cover'}
        />
    );
});
0

There are 0 best solutions below