어떻게 React Native를 사용하여 WebRTC 화상 통화를 구축합니까?

18494 단어 javascript
React-Native: React-Native는 RN이라고도 부르며 자바스크립트 기반의 모바일 응용 프로그램 프레임워크로 널리 사용되고 있다.React를 사용하면 로컬 개발자가 iOS와 안드로이드에 로컬로 구현된 모바일 애플리케이션을 구축할 수 있습니다.같은 코드 라이브러리를 사용하면 개발자는 React Native framework의 도움을 받아 다양한 플랫폼에 애플리케이션을 만들 수 있습니다.
React 갈고리: React 버전 16.8에 갈고리를 도입하여 다른 React 특성을 추가하는 수단으로 생명주기 방법과 같이 클래스를 작성할 필요가 없다.갈고리가 있으면 개발자는 함수를 사용할 수 있으며 함수, 클래스, 고급 구성 요소, 렌더링 도구 등을 끊임없이 전환할 필요가 없다.

이 설명서에서는 다음을 작성합니다.


·안드로이드+IOS 영상통화 앱
· 패키지 "react native-WebRTC"를 사용하여 실현webrtc video chat
· 웹 소켓을 사용하여 신호 보내기
· "react native paper"의 UI 구성 요소 사용

React Native를 사용하여 WebRTC 화상 통화를 구축하려면


WebRTC란?
WebRTC는 오디오, 비디오 및 데이터를 전송할 수 있는 오픈 소스 프로젝트입니다.WebRTC는 웹 브라우저와 모바일 응용 프로그램 사이에 점대점 통신을 제공하는 기술이다.또한 사용자가 브로커를 필요로 하지 않고 브라우저 간에 임의의 데이터를 교환할 수 있도록 한다.
단계:
  • 우선, React Native를 위한 응집력 있는 개발 환경이 필요합니다.이것은 개발자로서, 당신의React 원본 응용 프로그램을 설치하고 구축해야 한다는 것을 의미한다.여기 따라https://reactnative.dev/docs/environment-setup
  • 프리젠테이션 애플리케이션을 성공적으로 실행하고 UI 및 탐색을 위한 React 라이브러리 설치
    패키지를 설치합니다.json 종속성은 다음과 같습니다.
  • "dependencies": {
    "@react-native-community/async-storage": "^1.10.0",
    "@react-native-community/masked-view": "^0.1.10",
    "@react-navigation/native": "^5.2.3",
    "@react-navigation/stack": "^5.2.18",
    "react": "16.11.0",
    "react-native": "0.62.2",
    "react-native-gesture-handler": "^1.6.1",
    "react-native-incall-manager": "^3.2.7",
    "react-native-paper": "^3.9.0",
    "react-native-reanimated": "^1.8.0",
    "react-native-safe-area-context": "^0.7.3",
    "react-native-screens": "^2.7.0",
    "react-native-vector-icons": "^6.6.0",
    "react-native-webrtc": "^1.75.3" 
    }
    
    3. 프로젝트 의존 항목 목록의react 내비게이션을 사용하여 내비게이션을 구축합니다.
    응용 프로그램.js
    import React from 'react';
    import {NavigationContainer} from '@react-navigation/native';
    import {createStackNavigator} from '@react-navigation/stack';
    
    import LoginScreen from './screens/LoginScreen';
    import CallScreen from './screens/CallScreen';
    import {SafeAreaView} from 'react-native-safe-area-context';
    
    const Stack = createStackNavigator();
    
    const App = () => {
      return (
        <NavigationContainer>
        <Stack.Navigator>
            <Stack.Screen
            name="Login"
            component={LoginScreen}
            options={{headerShown: false}}
            />
            <Stack.Screen name="Call" component={CallScreen} />
        </Stack.Navigator>
        </NavigationContainer>
      );
    };
    
    export default App;
    
    
    4. 응용 프로그램 구성 요소에서 로그인 및 호출 화면을 가져오고 로그인 화면을 만듭니다.
    로그인 화면.js
    import React, {useState} from 'react';
    import {View, StyleSheet} from 'react-native';
    import {Text} from 'react-native-paper';
    import {TextInput} from 'react-native-paper';
    import AsyncStorage from '@react-native-community/async-storage';
    import {Button} from 'react-native-paper';
    
    export default function LoginScreen(props) {
      const [userId, setUserId] = useState('');
      const [loading, setLoading] = useState(false);
    
      const onLogin = async () => {
        setLoading(true);
        try {
        await AsyncStorage.setItem('userId', userId);
        setLoading(false);
        props.navigation.push('Call');
        } catch (err) {
        console.log('Error', err);
        setLoading(false);
        }
      };
    
      return (
        <View style={styles.root}>
        <View style={styles.content}>
            <Text style={styles.heading}>Enter your id</Text>
            <TextInput
            label="Your  ID"
            onChangeText={text => setUserId(text)}
            mode="outlined"
            style={styles.input}
            />
    
            <Button
            mode="contained"
            onPress={onLogin}
            loading={loading}
            style={styles.btn}
            contentStyle={styles.btnContent}
            disabled={userId.length === 0}>
            Login
            </Button>
        </View>
        </View>
      );
    }
    
    const styles = StyleSheet.create({
      root: {
        backgroundColor: '#fff',
        flex: 1,
        // alignItems: 'center',
        justifyContent: 'center',
      },
      content: {
        // alignSelf: 'center',
        paddingHorizontal: 20,
        justifyContent: 'center',
      },
      heading: {
        fontSize: 18,
        marginBottom: 10,
        fontWeight: '600',
      },
      input: {
        height: 60,
        marginBottom: 10,
      },
      btn: {
        height: 60,
        alignItems: 'stretch',
        justifyContent: 'center',
        fontSize: 18,
      },
      btnContent: {
        alignItems: 'center',
        justifyContent: 'center',
        height: 60,
      },
    });
    
    
    위의 파일에서 유일한 사용자 ID는 사용자를 대표하고 다른 연결된 사용자에게 인용될 수 있으며 개발자는 이 단계에서 모든 사용자에게 유일한 ID를 할당할 수 있습니다.

    WEBRTC의 주요 코드 구현:


    5. 호출 화면 코드:
    import React, {useEffect, useState, useCallback} from 'react';
    import {View, StyleSheet, Alert} from 'react-native';
    import {Text} from 'react-native-paper';
    import {Button} from 'react-native-paper';
    import AsyncStorage from '@react-native-community/async-storage';
    import {TextInput} from 'react-native-paper';
    
    import {useFocusEffect} from '@react-navigation/native';
    
    import InCallManager from 'react-native-incall-manager';
    
    import {
      RTCPeerConnection,
      RTCIceCandidate,
      RTCSessionDescription,
      RTCView,
      MediaStream,
      MediaStreamTrack,
      mediaDevices,
      registerGlobals,
    } from 'react-native-webrtc';
    
    export default function CallScreen({navigation, ...props}) {
      let name;
      let connectedUser;
      const [userId, setUserId] = useState('');
      const [socketActive, setSocketActive] = useState(false);
      const [calling, setCalling] = useState(false);
      // Video Scrs
      const [localStream, setLocalStream] = useState({toURL: () => null});
      const [remoteStream, setRemoteStream] = useState({toURL: () => null});
      const [conn, setConn] = useState(new WebSocket('ws://3.20.188.26:8080'));
      const [yourConn, setYourConn] = useState(
        //change the config as you need
        new RTCPeerConnection({
        iceServers: [
            {
            urls: 'stun:stun.l.google.com:19302', 
            }, {
            urls: 'stun:stun1.l.google.com:19302',  
            }, {
            urls: 'stun:stun2.l.google.com:19302',  
            }
    
        ],
        }),
      );
    
      const [offer, setOffer] = useState(null);
    
      const [callToUsername, setCallToUsername] = useState(null);
    
      useFocusEffect(
        useCallback(() => {
        AsyncStorage.getItem('userId').then(id => {
            console.log(id);
            if (id) {
            setUserId(id);
            } else {
            setUserId('');
            navigation.push('Login');
            }
        });
        }, [userId]),
      );
    
      useEffect(() => {
        navigation.setOptions({
        title: 'Your ID - ' + userId,
        headerRight: () => (
            <Button mode="text" onPress={onLogout} style={{paddingRight: 10}}>
            Logout
            </Button>
        ),
        });
      }, [userId]);
    
      /**
       * Calling Stuff
       */
    
      useEffect(() => {
        if (socketActive && userId.length > 0) {
        try {
            InCallManager.start({media: 'audio'});
            InCallManager.setForceSpeakerphoneOn(true);
            InCallManager.setSpeakerphoneOn(true);
        } catch (err) {
            console.log('InApp Caller ---------------------->', err);
        }
    
        console.log(InCallManager);
    
        send({
            type: 'login',
            name: userId,
        });
        }
      }, [socketActive, userId]);
    
      const onLogin = () => {};
    
      useEffect(() => {
        /**
        *
        * Sockets Signalling
        */
        conn.onopen = () => {
        console.log('Connected to the signaling server');
        setSocketActive(true);
        };
        //when we got a message from a signaling server
        conn.onmessage = msg => {
        let data;
        if (msg.data === 'Hello world') {
            data = {};
        } else {
            data = JSON.parse(msg.data);
            console.log('Data --------------------->', data);
            switch (data.type) {
            case 'login':
                console.log('Login');
                break;
            //when somebody wants to call us
            case 'offer':
                handleOffer(data.offer, data.name);
                console.log('Offer');
                break;
            case 'answer':
                handleAnswer(data.answer);
                console.log('Answer');
                break;
            //when a remote peer sends an ice candidate to us
            case 'candidate':
                handleCandidate(data.candidate);
                console.log('Candidate');
                break;
            case 'leave':
                handleLeave();
                console.log('Leave');
                break;
            default:
                break;
            }
        }
        };
        conn.onerror = function(err) {
        console.log('Got error', err);
        };
        /**
        * Socjket Signalling Ends
        */
    
        let isFront = false;
        mediaDevices.enumerateDevices().then(sourceInfos => {
        let videoSourceId;
        for (let i = 0; i < sourceInfos.length; i++) {
            const sourceInfo = sourceInfos[i];
            if (
            sourceInfo.kind == 'videoinput' &&
            sourceInfo.facing == (isFront ? 'front' : 'environment')
            ) {
            videoSourceId = sourceInfo.deviceId;
            }
        }
        mediaDevices
            .getUserMedia({
            audio: true,
              video: {
                mandatory: {
                minWidth: 500, // Provide your own width, height and frame rate here
                minHeight: 300,
                minFrameRate: 30,
                },
                facingMode: isFront ? 'user' : 'environment',
                optional: videoSourceId ? [{sourceId: videoSourceId}] : [],
            },
            })
            .then(stream => {
            // Got stream!
            setLocalStream(stream);
    
            // setup stream listening
            yourConn.addStream(stream);
            })
            .catch(error => {
            // Log error
            });
        });
    
        yourConn.onaddstream = event => {
        console.log('On Add Stream', event);
        setRemoteStream(event.stream);
        };
    
        // Setup ice handling
        yourConn.onicecandidate = event => {
        if (event.candidate) {
            send({
            type: 'candidate',
            candidate: event.candidate,
            });
        }
        };
      }, []);
    
      const send = message => {
        //attach the other peer username to our messages
        if (connectedUser) {
        message.name = connectedUser;
        console.log('Connected iser in end----------', message);
        }
    
        conn.send(JSON.stringify(message));
      };
    
      const onCall = () => {
        setCalling(true);
    
        connectedUser = callToUsername;
        console.log('Caling to', callToUsername);
        // create an offer
    
        yourConn.createOffer().then(offer => {
          yourConn.setLocalDescription(offer).then(() => {
            console.log('Sending Ofer');
            console.log(offer);
            send({
            type: 'offer',
            offer: offer,
            });
            // Send pc.localDescription to peer
        });
        });
      };
    
      //when somebody sends us an offer
      const handleOffer = async (offer, name) => {
        console.log(name + ' is calling you.');
    
        console.log('Accepting Call===========>', offer);
        connectedUser = name;
    
        try {
        await yourConn.setRemoteDescription(new RTCSessionDescription(offer));
    
        const answer = await yourConn.createAnswer();
    
        await yourConn.setLocalDescription(answer);
        send({
            type: 'answer',
            answer: answer,
        });
        } catch (err) {
        console.log('Offerr Error', err);
        }
      };
    
      //when we got an answer from a remote user
      const handleAnswer = answer => {
        yourConn.setRemoteDescription(new RTCSessionDescription(answer));
      };
    
      //when we got an ice candidate from a remote user
      const handleCandidate = candidate => {
        setCalling(false);
        console.log('Candidate ----------------->', candidate);
        yourConn.addIceCandidate(new RTCIceCandidate(candidate));
      };
    
      //hang up
      const hangUp = () => {
        send({
        type: 'leave',
        });
    
        handleLeave();
      };
    
      const handleLeave = () => {
        connectedUser = null;
        setRemoteStream({toURL: () => null});
    
        yourConn.close();
        // yourConn.onicecandidate = null;
        // yourConn.onaddstream = null;
      };
    
      const onLogout = () => {
        // hangUp();
    
        AsyncStorage.removeItem('userId').then(res => {
        navigation.push('Login');
        });
      };
    
      const acceptCall = async () => {
        console.log('Accepting Call===========>', offer);
        connectedUser = offer.name;
    
        try {
        await yourConn.setRemoteDescription(new RTCSessionDescription(offer));
    
        const answer = await yourConn.createAnswer();
    
        await yourConn.setLocalDescription(answer);
    
        send({
            type: 'answer',
            answer: answer,
        });
        } catch (err) {
        console.log('Offerr Error', err);
        }
      };
      const rejectCall = async () => {
        send({
        type: 'leave',
        });
        ``;
        setOffer(null);
    
        handleLeave();
      };
    
      /**
       * Calling Stuff Ends
       */
    
      return (
        <View style={styles.root}>
        <View style={styles.inputField}>
            <TextInput
            label="Enter Friends Id"
            mode="outlined"
            style={{marginBottom: 7}}
            onChangeText={text => setCallToUsername(text)}
            />
            <Button
            mode="contained"
            onPress={onCall}
            loading={calling}
            //   style={styles.btn}
            contentStyle={styles.btnContent}
            disabled={!(socketActive && userId.length > 0)}>
            Call
            </Button>
        </View>
    
        <View style={styles.videoContainer}>
            <View style={[styles.videos, styles.localVideos]}>
            <Text>Your Video</Text>
            <RTCView streamURL={localStream.toURL()} style={styles.localVideo} />
            </View>
            <View style={[styles.videos, styles.remoteVideos]}>
            <Text>Friends Video</Text>
            <RTCView
                streamURL={remoteStream.toURL()}
                style={styles.remoteVideo}
            />
            </View>
        </View>
        </View>
      );
    }
    
    const styles = StyleSheet.create({
      root: {
        backgroundColor: '#fff',
        flex: 1,
        padding: 20,
      },
      inputField: {
        marginBottom: 10,
        flexDirection: 'column',
      },
      videoContainer: {
        flex: 1,
        minHeight: 450,
      },
      videos: {
        width: '100%',
        flex: 1,
        position: 'relative',
        overflow: 'hidden',
    
        borderRadius: 6,
      },
      localVideos: {
        height: 100,
        marginBottom: 10,
      },
      remoteVideos: {
        height: 400,
      },
      localVideo: {
        backgroundColor: '#f2f2f2',
        height: '100%',
        width: '100%',
      },
      remoteVideo: {
        backgroundColor: '#f2f2f2',
        height: '100%',
        width: '100%',
      },
    });
    
    
    

    상기 코드에서 설명해야 할 주요 부분:



    settinguserID:현재 로그인한 사용자
    connectedUser: 다른 사용자의 ID를 사용하여 다른 사용자를 호출할 때 이 변수는 사용자 ID에 할당됩니다.
    비디오/오디오 스트림:
    localStream: 사용자의 모바일 카메라로 로컬 비디오 스트림에 액세스
    remoteStream: 호출을 연결할 때 수신기 영상 흐름이 이 흐름 변수에 추가됩니다
    비디오 스트림에 액세스하려면 안드로이드 및 iOS 파일에 일부 권한을 설정해야 합니다.사용 권한을 확인하려면 여기를 클릭하십시오.
    안드로이드https://github.com/react-native-webrtc/react-native-webrtc/blob/master/Documentation/AndroidInstallation.md
    IOS-https://github.com/react-native-webrtc/react-native-webrtc/blob/master/Documentation/iOSInstallation.md
    강은:이게 너의 웹소켓 연결을 만들었어.
    yourConn: RTPeerConnection이 설정됩니다. 이 연결은 로컬/원격 설명과 혜택을 설정하는 데 사용될 것입니다.

    소켓 이벤트:


    onmessage: 메시지를 받을 때 자극
    메시지 유형 및 프로세서:
    쿼트: 이것은 단지 한 사용자가 다른 사용자와 연결을 시도하는 과정일 뿐입니다.발송자는 요약을 작성하고 발송하며 수신자는 요약을 확인하고 수신한다.이것은handle Offer라고 부른다.
    후보: 수신자는 후보로 불린다.손잡이 후보자가 신호를 촉발하면 전화가 계속 울리거나 응답을 기다리고 있음을 나타낸다.
    답: 수신자가 받은 견적은 수락, 거절 또는 이후 주소로 저장할 수 있습니다.견적에 대한 답안을 작성하거나 거부/퇴장할 수 있습니다.이 제안이 받아들여지면 발송자가 동영상 흐름을 볼 수 있도록 로컬 연결 흐름에 추가되어 동영상 흐름에 대한 답을 만들고 발송할 수 있습니다.
    응답 처리: 수신자가 우리에게 응답을 보낸 후에 호출이 현재 받아들여지고 발송자의 영상이 보인다는 것을 의미한다.
    Out-Off: Call Lock은 연결된 Call 또는 수신된 쿼트를 종료하고 모든 변수를 RTPeerConnection 해제하는 것을 의미합니다.
    절차:
    · 로그인: 메시지를 소켓에 전송하여 로그인
    · 로컬 스트림: 로컬 사용자로부터 비디오 스트림 가져오기
    ·ice 후보 업데이트를 위한 이벤트 탐지기 추가
    · Remote User가 이 RTPeerConnection에 스트림을 추가할 때 이벤트 탐지기 Ontrack을 트리거하고 이를 표시합니다.
    배달:
    React Nativebuild webrtc video chat app 기능을 사용하는 방법을 알고 있는 이상 시작할 수 있습니다!그러나 만약 당신이 어렵다고 생각한다면, 간단한 방법은 MirrorFly가 프로그래밍 가능한 동영상을 선택해서 API를 호출하는 것이다.React Native나 WebRTC뿐만 아니라 MirrorFly의 전체적인 API를 통해 개발자들은 전분에서 다른 모든 것을 구축하는 데 시간을 들이지 않고 응용 개발의 다른 요소에 쉽게 전념할 수 있다.전문가와 상담하거나 현장 프레젠테이션을 하려면 아래 링크를 클릭하십시오https://www.mirrorfly.com/webrtc-video-chat.php.

    좋은 웹페이지 즐겨찾기