어떻게 React Native를 사용하여 WebRTC 화상 통화를 구축합니까?
18494 단어 javascript
React 갈고리: React 버전 16.8에 갈고리를 도입하여 다른 React 특성을 추가하는 수단으로 생명주기 방법과 같이 클래스를 작성할 필요가 없다.갈고리가 있으면 개발자는 함수를 사용할 수 있으며 함수, 클래스, 고급 구성 요소, 렌더링 도구 등을 끊임없이 전환할 필요가 없다.
이 설명서에서는 다음을 작성합니다.
·안드로이드+IOS 영상통화 앱
· 패키지 "react native-WebRTC"를 사용하여 실현webrtc video chat
· 웹 소켓을 사용하여 신호 보내기
· "react native paper"의 UI 구성 요소 사용
React Native를 사용하여 WebRTC 화상 통화를 구축하려면
WebRTC란?
WebRTC는 오디오, 비디오 및 데이터를 전송할 수 있는 오픈 소스 프로젝트입니다.WebRTC는 웹 브라우저와 모바일 응용 프로그램 사이에 점대점 통신을 제공하는 기술이다.또한 사용자가 브로커를 필요로 하지 않고 브라우저 간에 임의의 데이터를 교환할 수 있도록 한다.
단계:
패키지를 설치합니다.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.
Reference
이 문제에 관하여(어떻게 React Native를 사용하여 WebRTC 화상 통화를 구축합니까?), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/charalotteyog/how-to-build-webrtc-video-call-with-react-native-11ld텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)