我一直在尝试在 React-Native 应用程序中进行视频通话。目前正在使用react-native-webrtc
,这是此类项目的主流库。
我对此很陌生,但是基于 p2p 视频的最小示例(在此处找到),我编写了一个代码,试图让它们与不同的网络一起工作。该示例在一个流媒体与一个接收者之间创建了一个连接,但两者都在同一页面执行、同一网络、相同的所有内容上。
在我的情况下,我需要两个用户从不同的网络流式传输视频和接收视频。问题是,我找不到合适的地方来阅读和理解谈判在这种情况下的实际运作方式。
代码示例:
/**
* @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%',
},
});