WebRT C в реактивном режиме (зацепки), редуксе - PullRequest
0 голосов
/ 22 февраля 2020

Я занимаюсь разработкой собственного приложения, которое использует webRT C. Мне очень понравилась минимальная версия, которую я нашел здесь (слава baconcheese113!), И я решил реорганизовать ее, чтобы создать компонент реагирования.

Я настроил бэкэнд (DynamoDB, Appsyn *) 1050 *) и redux хранилище, которое позволяет мне:

  1. отправить действие sendCreateUserControlMsg, которое в конце строки вызывает конечную точку Appsyn c для создания новой ControlUserMsg
  2. подписаться на ControlUserMsg, установить флаг triggerWebrtcData и сохранить webrtcData в состоянии Redux

Следующий компонент (который на данный момент вызывает себя), иногда работает, но в основном не работает. Я чувствую, что проблема связана с JS Promises, но я не до конца понимаю, как мне следует проектировать компонент, чтобы избежать условий гонки.

import React, { useState, useEffect } from 'react';
import { View, SafeAreaView, Button, StyleSheet } from 'react-native';

import { RTCPeerConnection, RTCView, mediaDevices } from 'react-native-webrtc';

import { sendCreateUserControlMsg } from '../redux/actions/UserControlMsgActions';
import controlMsgActions from './../model/control_msg_actions';
import webrtcActionTypes from './../model/webrtc_action_types';
import { useDispatch, useSelector } from "react-redux";
import * as triggersMatch from '../redux/actions/TriggersMatchActions';

var IS_LOCAL_USER = true //manual flag I temporarily set

var localUserID = '00'; 
var localUser = 'localUser'
var remoteUserID = '01'; 
var remoteUser = 'remoteUser'

if (IS_LOCAL_USER) {
    var matchedUserId = remoteUserID
    var user_id = localUserID;
    var user = localUser
}
else {
    var matchedUserId = localUserID
    var user_id = remoteUserID;
    var user = remoteUser
}
export default function App() {
    const dispatch = useDispatch();
    var triggersMatchBool = useSelector(state => state.triggers_match)
    var webrtcData = useSelector(state => state.webrtc_description.webrtcData)

    const [localStream, setLocalStream] = useState();
    const [remoteStream, setRemoteStream] = useState();
    const [cachedLocalPC, setCachedLocalPC] = useState();
    const [cachedRemotePC, setCachedRemotePC] = useState();

    const sendICE = (candidate, isLocal) => {
        var type
        isLocal ? type = webrtcActionTypes["NEW_ICE_CANDIDATE_FROM_LOCAL"] : type = webrtcActionTypes["NEW_ICE_CANDIDATE_FROM_REMOTE"]
        var payload = JSON.stringify({
            type,
            candidate
        })
        console.log(`Sending ICE to  ${matchedUserId}`)
        dispatch(sendCreateUserControlMsg(matchedUserId, user_id, user, payload, controlMsgActions["WEBRTC_DATA"]));
    }
    const sendOffer = (offer) => {
        type = webrtcActionTypes["OFFER"]
        var payload = JSON.stringify({
            type,
            offer
        })
        console.log(`Sending Offer to  ${matchedUserId}`)
        dispatch(sendCreateUserControlMsg(matchedUserId, user_id, user, payload, controlMsgActions["WEBRTC_DATA"]));
    }
    const sendAnswer = (answer) => {
        type = webrtcActionTypes["ANSWER"]
        var payload = JSON.stringify({
            type,
            answer
        })
        console.log(`Sending answer to  ${matchedUserId}`)
        dispatch(sendCreateUserControlMsg(matchedUserId, user_id, user, payload, controlMsgActions["WEBRTC_DATA"]));
    }
    const [isMuted, setIsMuted] = useState(false);
    // START triggers
    async function triggerMatchWatcher() {
        if (triggersMatchBool.triggerWebrtcData) {
            dispatch(triggersMatch.endTriggerWebrtcData());
            switch (webrtcData.type) {
                case webrtcActionTypes["NEW_ICE_CANDIDATE_FROM_LOCAL"]:
                    try {
                        setCachedRemotePC(cachedRemotePC.addIceCandidate(webrtcData.candidate))
                    } catch (error) {
                        console.warn('ICE not added')
                    }
                    break;
                case webrtcActionTypes["NEW_ICE_CANDIDATE_FROM_REMOTE"]:
                    try {
                        setCachedLocalPC(cachedLocalPC.addIceCandidate(webrtcData.candidate))
                    } catch (error) {
                        console.warn('ICE not added')
                    }
                    break;
                case webrtcActionTypes["OFFER"]:
                    console.log('remotePC, setRemoteDescription');
                    try {
                        await cachedRemotePC.setRemoteDescription(webrtcData.offer);
                        console.log('RemotePC, createAnswer');
                        const answer = await cachedRemotePC.createAnswer();
                        setCachedRemotePC(cachedRemotePC)
                        sendAnswer(answer);
                    } catch (error) {
                        console.warn(`setRemoteDescription failed ${error}`);
                    }
                case webrtcActionTypes["ANSWER"]:
                    try {
                        console.log(`Answer from remotePC: ${webrtcData.answer.sdp}`);
                        console.log('remotePC, setLocalDescription');
                        await cachedRemotePC.setLocalDescription(webrtcData.answer);
                        setCachedRemotePC(cachedRemotePC)
                        console.log('localPC, setRemoteDescription');
                        await cachedLocalPC.setRemoteDescription(cachedRemotePC.localDescription);
                        setCachedLocalPC(cachedLocalPC)
                    } catch (error) {
                        console.warn(`setLocalDescription failed ${error}`);
                    }
            }
        }
    }
    useEffect(() => {
        triggerMatchWatcher()
    }
    );
    const startLocalStream = async () => {
        // isFront will determine if the initial camera should face user or environment
        const isFront = true;
        const devices = await mediaDevices.enumerateDevices();

        const facing = isFront ? 'front' : 'environment';
        const videoSourceId = devices.find(device => device.kind === 'videoinput' && device.facing === facing);
        const facingMode = isFront ? 'user' : 'environment';
        const constraints = {
            audio: true,
            video: {
                mandatory: {
                    minWidth: 500, // Provide your own width, height and frame rate here
                    minHeight: 300,
                    minFrameRate: 30,
                },
                facingMode,
                optional: videoSourceId ? [{ sourceId: videoSourceId }] : [],
            },
        };
        const newStream = await mediaDevices.getUserMedia(constraints);
        setLocalStream(newStream);
    };

    const startCall = async () => {
        const configuration = { iceServers: [{ url: 'stun:stun.l.google.com:19302' }] };
        const localPC = new RTCPeerConnection(configuration);
        const remotePC = new RTCPeerConnection(configuration);

        localPC.onicecandidate = e => {
            try {
                console.log('localPC icecandidate:', e.candidate);
                if (e.candidate) {
                    sendICE(e.candidate, true)
                }
            } catch (err) {
                console.error(`Error adding remotePC iceCandidate: ${err}`);
            }
        };
        remotePC.onicecandidate = e => {
            try {
                console.log('remotePC icecandidate:', e.candidate);
                if (e.candidate) {
                    sendICE(e.candidate, false)
                }
            } catch (err) {
                console.error(`Error adding localPC iceCandidate: ${err}`);
            }
        };
        remotePC.onaddstream = e => {
            console.log('remotePC tracking with ', e);
            if (e.stream && remoteStream !== e.stream) {
                console.log('RemotePC received the stream', e.stream);
                setRemoteStream(e.stream);
            }
        };

        localPC.addStream(localStream);
        // Not sure whether onnegotiationneeded is needed
        // localPC.onnegotiationneeded = async () => {
        //   try {
        //     const offer = await localPC.createOffer();
        //     console.log('Offer from localPC, setLocalDescription');
        //     await localPC.setLocalDescription(offer);
        //     sendOffer(localPC.localDescription)
        //   } catch (err) {
        //     console.error(err);
        //   }
        // };
        try {
            const offer = await localPC.createOffer();
            console.log('Offer from localPC, setLocalDescription');
            await localPC.setLocalDescription(offer);
            sendOffer(localPC.localDescription)
        } catch (err) {
            console.error(err);
        }
        setCachedLocalPC(localPC);
        setCachedRemotePC(remotePC);
    };
    const switchCamera = () => {
        localStream.getVideoTracks().forEach(track => track._switchCamera());
    };


    const closeStreams = () => {
        if (cachedLocalPC) {
            cachedLocalPC.removeStream(localStream);
            cachedLocalPC.close();
            })
        }
        if (cachedRemotePC) {
            cachedRemotePC.removeStream(localStream);
            cachedRemotePC.close();
            })
        }
        setLocalStream();
        setRemoteStream();
        setCachedRemotePC();
        setCachedLocalPC();
    };

    return (
        <SafeAreaView style={styles.container}>
            {!localStream && <Button title="Click to start stream" onPress={startLocalStream} />}
            {localStream && <Button title="Click to start call" onPress={startCall} disabled={!!remoteStream} />}

            {localStream && (
                <View style={styles.toggleButtons}>
                    <Button title="Switch camera" onPress={switchCamera} />
                </View>
            )}

            <View style={styles.rtcview}>
                {localStream && <RTCView style={styles.rtc} streamURL={localStream.toURL()} />}
            </View>
            <View style={styles.rtcview}>
                {remoteStream && <RTCView style={styles.rtc} streamURL={remoteStream.toURL()} />}
            </View>
            <Button title="Click to stop call" onPress={closeStreams} disabled={!remoteStream} />
        </SafeAreaView>
    );
}

const styles = StyleSheet.create({
    // omitted
});

Наиболее распространенные ошибки, которые я получаю:

Ошибка: не удалось добавить кандидата ICE Возможный необработанный отказ от обещания

и

setLocalDescription ошибка TypeError: Невозможно прочитать свойство 'sdp' из неопределенного

Если я console.log, я вижу, что это JS Promise, но, поскольку я не являюсь функцией, я не могу использовать .then().

Как я могу вызвать метод addIceCandidate или setLocalDescription метод без ошибок Unhandled Promise Rejection? Каковы лучшие практики для работы с WebRT C в реагирующем языке?

...