首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >React WEBRTC - Peer to Peer -视频呼叫-似乎不起作用

React WEBRTC - Peer to Peer -视频呼叫-似乎不起作用
EN

Stack Overflow用户
提问于 2020-01-13 03:20:52
回答 1查看 2.7K关注 0票数 1

我一直在尝试在一个反应-本地应用程序中打一个视频电话。目前使用的是react-native-webrtc,这是这类项目的主流库。

我对此非常陌生,但基于p2p视频(在这里发现的)的最小示例,我编写了一个代码,试图使if在不同的网络中工作。该示例在一个流线与接收器之间创建一个连接,但都是在相同的页面执行、相同的网络、相同的所有内容上进行的。

在我的例子中,我需要两个用户从不同的网络传输视频和接收视频。问题是,我找不到一个合适的地方来阅读和理解谈判是如何在这种情况下进行的。

代码示例:

代码语言:javascript
复制
/**
 * @format
 * @flow
 */

import React, { useEffect } from 'react';
import firebase from '../firebase.config';
import { useSelector } from 'react-redux';
import {
    View,
    SafeAreaView,
    Button,
    StyleSheet,
    Dimensions,
    Text,
} from 'react-native';
import { RTCPeerConnection, RTCView, mediaDevices } from 'react-native-webrtc';
import store from '../redux/store';
import { Actions } from 'react-native-router-flux';
import { User } from 'models';
const oUserService = new User().getService(firebase);
const oCurrentReceivedStreamingService = new User().getService(
    firebase,
    store,
    'currentReceivedStreaming',
);

const viewport = Dimensions.get('window');

const Streaming = {
    call: (caller, receiver, localDescription) => {
        return {
            status: 'pending',
            users: {
                caller: {
                    uid: caller.uid,
                    localDescription,
                },
                receiver: {
                    uid: receiver.uid,
                    localDescription: '',
                },
            },
        };
    },
    answer: (receiver, localDescription) => {
        return {
            ...receiver.streaming,
            status: 'ongoing',
            users: {
                ...receiver.streaming.users,
                receiver: {
                    ...receiver.streaming.users.receiver,
                    localDescription,
                },
            },
        };
    },
    close: streaming => {
        return {
            ...streaming,
            status: 'closed',
        };
    },
};

const configuration = {
    iceServers: [
        { url: 'stun:stun.l.google.com:19302' },
        // { url: 'stun:stun1.l.google.com:19302' },
        // { url: 'stun:stun2.l.google.com:19302' },
        // { url: 'stun:stun3.l.google.com:19302' },
        // { url: 'stun:stun4.l.google.com:19302' },
        // { url: 'stun:stun.ekiga.net' },
        // { url: 'stun:stun.ideasip.com' },
        // { url: 'stun:stun.iptel.org' },
        // { url: 'stun:stun.rixtelecom.se' },
        // { url: 'stun:stun.schlund.de' },
        // { url: 'stun:stunserver.org' },
        // { url: 'stun:stun.softjoys.com' },
        // { url: 'stun:stun.voiparound.com' },
        // { url: 'stun:stun.voipbuster.com' },
        // { url: 'stun:stun.voipstunt.com' },
    ],
};

export default function App({ user, receiver, caller, session }) {
    const currentUserStore = useSelector(s => s.currentUserStore);
    const userStreamingStore = useSelector(s => s.userStreamingStore);
    const currentReceivedStreaming = useSelector(
        s => s.currentReceivedStreaming,
    );

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

    useEffect(() => {
        oCurrentReceivedStreamingService.get(caller.uid);
    }, [receiver, caller, user, session]);

    let localPC, remotePC;

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

        const facing = isFront ? 'front' : 'back';
        const videoSourceId = devices.find(
            device => device.kind === 'videoinput' && device.facing === facing,
        );
        const facingMode = isFront ? 'user' : 'environment';
        const constraints = {
            audio: true,
            video: {
                mandatory: {
                    minWidth: (viewport.height - 100) / 2,
                    minHeight: (viewport.height - 100) / 2,
                    minFrameRate: 30,
                },
                facingMode,
                optional: videoSourceId ? [{ sourceId: videoSourceId }] : [],
            },
        };
        const newStream = await mediaDevices.getUserMedia(constraints);
        setLocalStream(newStream);
        return Promise.resolve(newStream);
    };

    const startCall = async () => {
        try {
            let newStream = await startLocalStream();
            oCurrentReceivedStreamingService.get(session.user.uid);

            localPC = new RTCPeerConnection(configuration);
            remotePC = new RTCPeerConnection(configuration);

            localPC.onicecandidate = e => {
                try {
                    if (e.candidate) {
                        remotePC.addIceCandidate(e.candidate);
                    }
                } catch (err) {
                    console.error(`Error adding remotePC iceCandidate: ${err}`);
                }
            };
            remotePC.onicecandidate = e => {
                try {
                    if (e.candidate) {
                        localPC.addIceCandidate(e.candidate);
                    }
                } catch (err) {
                    console.error(`Error adding localPC iceCandidate: ${err}`);
                }
            };
            remotePC.onaddstream = e => {
                if (e.stream && remoteStream !== e.stream) {
                    setRemoteStream(e.stream);
                }
            };

            localPC.addStream(newStream);

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

            oUserService.patch(currentReceivedStreaming.current.uid, {
                streaming: Streaming.call(
                    currentReceivedStreaming.current,
                    user,
                    localPC.localDescription,
                ),
            });
        } catch (err) {
            console.error(err);
        }
        setCachedLocalPC(localPC);
        setCachedRemotePC(remotePC);
    };

    const answerCall = async (oUser, oCaller) => {
        try {
            let newStream = await startLocalStream();

            localPC = new RTCPeerConnection(configuration);
            remotePC = new RTCPeerConnection(configuration);

            localPC.onicecandidate = e => {
                try {
                    if (e.candidate) {
                        remotePC.addIceCandidate(e.candidate);
                    }
                } catch (err) {
                    console.error(`Error adding remotePC iceCandidate: ${err}`);
                }
            };
            remotePC.onicecandidate = e => {
                try {
                    if (e.candidate) {
                        localPC.addIceCandidate(e.candidate);
                    }
                } catch (err) {
                    console.error(`Error adding localPC iceCandidate: ${err}`);
                }
            };
            remotePC.onaddstream = e => {
                if (e.stream && remoteStream !== e.stream) {
                    setRemoteStream(e.stream);
                }
            };

            localPC.addStream(newStream);

            await remotePC.setRemoteDescription(oCaller.localDescription);

            let remoteStreams = remotePC.getRemoteStreams();
            remoteStreams.map(s => {
                console.log(s);
                setRemoteStream(s);
            });

            await localPC.setRemoteDescription(oCaller.localDescription);

            const offer = await localPC.createOffer();
            // const offer = await localPC.createAnswer();
            await localPC.setLocalDescription(offer);

            oUserService.patch(currentReceivedStreaming.current.uid, {
                streaming: Streaming.answer(
                    currentReceivedStreaming.current,
                    localPC.localDescription,
                ),
            });
        } catch (err) {
            console.error(err);
        }
        setCachedLocalPC(localPC);
        setCachedRemotePC(remotePC);
    };

    useEffect(() => {
        if (currentReceivedStreaming.current.uid) {
            let current = currentReceivedStreaming.current;
            if (current.streaming) {
                if (
                    current.streaming.status === 'closed' ||
                    current.streaming.status === 'rejected'
                ) {
                    // Actions.popTo('dashboard');
                }
                if (current.streaming.status === 'pending') {
                    if (
                        current.streaming.users.receiver.uid ===
                        session.user.uid
                    ) {
                        answerCall(current, current.streaming.users.caller);
                    }
                }
                if (current.streaming.status === 'ongoing' && remotePC) {
                    if (
                        current.streaming.users.caller.uid === session.user.uid
                    ) {
                        remotePC.setRemoteDescription(
                            current.streaming.receiver.localDescription,
                        );
                    }
                }
            }
        }
    }, [currentReceivedStreaming.current]);

    const closeStreams = () => {
        try {
            if (cachedLocalPC) {
                cachedLocalPC.removeStream(localStream);
                cachedLocalPC.close();
            }
            if (cachedRemotePC) {
                cachedRemotePC.removeStream(remoteStream);
                cachedRemotePC.close();
            }

            setLocalStream();
            setRemoteStream();
            setCachedRemotePC();
            setCachedLocalPC();

            oUserService
                .patch(currentReceivedStreaming.current.uid, {
                    streaming: {
                        ...currentReceivedStreaming.current.streaming,
                        status: 'closed',
                    },
                })
                .then(() => Actions.popTo('dashboard'));
        } catch (e) {
            console.log('ERROR', e);
        }
    };

    useEffect(() => {
        if (!localStream && caller.uid === session.user.uid) {
            startCall();
        }
    }, [currentUserStore.current.streaming]);

    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}
                />
            )} */}

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

const styles = StyleSheet.create({
    container: {
        backgroundColor: '#313131',
        justifyContent: 'space-between',
        alignItems: 'center',
        height: '100%',
        paddingVertical: 30,
    },
    text: {
        fontSize: 30,
    },
    rtcview: {
        justifyContent: 'center',
        alignItems: 'center',
        height: '40%',
        width: '80%',
        backgroundColor: 'black',
        borderRadius: 10,
    },
    rtc: {
        width: '80%',
        height: '100%',
    },
});
EN

回答 1

Stack Overflow用户

发布于 2020-01-13 15:47:57

简而言之,从开发人员的角度来看,两个浏览器之间的视频调用是什么样子的?

  1. 在第一个浏览器上初步准备和创建了必要的JavaScript对象之后,调用了WebRTC方法createOffer(),它返回一个SDP格式的文本包(如果API的oRTC版本获得“经典”对象,则返回一个JSON可序列化的对象)。这个包包含关于开发人员想要什么样的通信的信息:语音、视频或发送数据,哪些编解码器在那里--这整个故事。
  2. 现在-信号。开发人员必须以某种方式(实际上,它是在规范中编写的!)将此文本包提供给第二个浏览器。例如,在互联网上使用您自己的服务器,并在两种浏览器上使用WebSocket连接。
  3. 在第二个浏览器上收到建议后,开发人员使用setRemoteDescription()方法将其传递给setRemoteDescription。然后,他调用createAnswer()方法,该方法以SDP格式返回相同的文本包,但对于第二个浏览器,并考虑到从第一个浏览器接收到的数据包。
  4. 信令继续:开发人员将应答文本数据包传回给第一个浏览器。
  5. 在第一个浏览器上收到答案后,开发人员使用前面提到的setRemoteDescription()方法将其传递给setRemoteDescription(),之后这两个浏览器中的WebRTC之间的联系都是最小的。我能联系上吗?不幸的是没有。事实上,一切才刚刚开始..。
  6. 两种浏览器中的WebRTC都开始分析网络连接的状态(实际上,标准并不指示何时进行此操作,对于许多浏览器,WebRTC在创建相应对象后立即开始研究网络,以避免在连接时造成不必要的延迟)。当开发人员在第一步创建WebRTC对象时,他至少应该传递STUN服务器的地址。这是一个服务器,响应UDP数据包“什么是我的IP”,发送接收该数据包的IP地址。WebRTC使用STUN服务器获取“外部”IP地址,并将其与“内部”地址进行比较,并查看是否存在NAT。如果是,NAT使用哪些反向端口来路由UDP数据包?
  7. 有时,两个浏览器上的WebRTC都会调用onicecandidate回调,发送包含第二个连接参与者的信息的SIP数据包。此数据包包含有关内部和外部IP地址、连接尝试、NAT使用的端口等信息。开发人员使用信令在浏览器之间传输这些数据包。发送的数据包使用WebRTC ()方法发送到addIceCandidate。
  8. 过一段时间,WebRTC将建立一个对等连接.或者如果NAT会干涉的话。在这种情况下,开发人员可以发送转身服务器的地址,该地址将用作外部连接元素:两个浏览器都将通过它传输带有语音或视频的UDP数据包。如果可以免费找到STUN服务器(例如,google拥有它),那么您必须自己提高转身服务器。没有人有兴趣免费通过自己的视频流量超过兆字节。

总结:您将需要在最小眩晕服务器,作为最大转弯服务器。或者两者兼而有之,如果您不知道用户将使用哪种网络配置。您可以阅读此博客以获得更详细的信息。我觉得它很能提供信息。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/59710228

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档