javascript - JSON 类型 NSMutableDictionary 不能转换为 .sdp 不能为空
问题描述
我是 webRTC 的新手,我正在尝试使用本教程创建一个具有视频通话功能的本机应用程序,以遵循https://dipanshkhandelwal.medium.com/video-calling-using-firebase-and-webrtc-14cc2d4afceb
但是,我在 iOS 和 android 上不断收到此错误,一旦我尝试加入通话,该应用程序就会关闭。我在 iOS 上遇到的错误说:
JSON value '{
}' of type NSMutableDictionary cannot be converted to .sdp must not be null
+[RCTConvert(WebRTC) RTCSessionDescription:]
RCTConvert+WebRTC.m:22
__41-[RCTModuleMethod processMethodSignature]_block_invoke_16
-[RCTModuleMethod invokeWithBridge:module:arguments:]
facebook::react::invokeInner(RCTBridge*, RCTModuleData*, unsigned int, folly::dynamic const&, int, (anonymous namespace)::SchedulingContext)
facebook::react::RCTNativeModule::invoke(unsigned int, folly::dynamic&&, int)::$_0::operator()() const
invocation function for block in facebook::react::RCTNativeModule::invoke(unsigned int, folly::dynamic&&, int)
_dispatch_call_block_and_release
_dispatch_client_callout
_dispatch_lane_serial_drain
_dispatch_lane_invoke
_dispatch_workloop_worker_thread
_pthread_wqthread
start_wqthread
在地铁中,这是一个错误:
Possible Unhandled Promise Rejection (id: 0):
Object {
"message": "SessionDescription is NULL.",
"name": "SetRemoteDescriptionFailed",
}
下面是在相应房间的 firstore 中创建的报价示例。我怀疑 sdp 可能是无效的或什么的。
_sdp:
v=0o=- 765269967391877801 2 IN IP4 127.0.0.1s=-t=0 0a=group:BUNDLE audio videoa=msid-semantic: WMS a3a6a14c-980f-4052-a881-e53290c2e9a8m=video 100c=IN IP4 0.0.0.0a=rtcp:9 IN IP4 0.0.0.0a=ice-ufrag:qfEea=ice-pwd:aMtS7ouM3ykOCV6z7jx0hhlSa=ice-options:trickle renominationa=fingerprint:sha-256 01:13:79:2E:BA:E3:67:9A:77:66:90:00:D7:62:BA:31:2C:48:FC:EB:8C:21:22:7E:5B:9D:0E:71:82:5F:63:5Aa=setup:actpassa=mid:audioa=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-levela=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-timea=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01a=sendrecva=rtcp-muxa=rtpmap:111 opus/48000/2a=rtcp-fb:111 transport-cca=fmtp:111 minptime=10;useinbandfec=1a=rtpmap:103 ISAC/16000a=rtpmap:9 G722/8000a=rtpmap:102 ILBC/8000a=rtpmap:0 PCMU/8000a=rtpmap:8 PCMA/8000a=rtpmap:105 CN/16000a=rtpmap:13 CN/8000a=rtpmap:110 telephone-event/48000a=rtpmap:113 telephone-event/16000a=rtpmap:126 telephone-event/8000a=ssrc:866776128 cname:OXEDL0qxAsrhPmB7a=ssrc:866776128 msid:a3a6a14c-980f-4052-a881-e53290c2e9a8 256a9ac0-8163-45b5-ae52-0ece88651c51a=ssrc:866776128 mslabel:a3a6a14c-980f-4052-a881-e53290c2e9a8a=ssrc:866776128 label:256a9ac0-8163-45b5-ae52-0ece88651c51m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 127c=IN IP4 0.0.0.0a=rtcp:9 IN IP4 0.0.0.0a=ice-ufrag:qfEea=ice-pwd:aMtS7ouM3ykOCV6z7jx0hhlSa=ice-options:trickle renominationa=fingerprint:sha-256 01:13:79:2E:BA:E3:67:9A:77:66:90:00:D7:62:BA:31:2C:48:FC:EB:8C:21:22:7E:5B:9D:0E:71:82:5F:63:5Aa=setup:actpassa=mid:videoa=extmap:14 urn:ietf:params:rtp-hdrext:toffseta=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-timea=extmap:13 urn:3gpp:video-orientationa=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01a=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delaya=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-typea=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timinga=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-spacea=sendrecva=rtcp-muxa=rtcp-rsizea=rtpmap:96 VP8/90000a=rtcp-fb:96 goog-remba=rtcp-fb:96 transport-cca=rtcp-fb:96 ccm fira=rtcp-fb:96 nacka=rtcp-fb:96 nack plia=rtpmap:97 rtx/90000a=fmtp:97 apt=96a=rtpmap:98 VP9/90000a=rtcp-fb:98 goog-remba=rtcp-fb:98 transport-cca=rtcp-fb:98 ccm fira=rtcp-fb:98 nacka=rtcp-fb:98 nack plia=rtpmap:99 rtx/90000a=fmtp:99 apt=98a=rtpmap:100 red/90000a=rtpmap:101 rtx/90000a=fmtp:101 apt=100a=rtpmap:127 ulpfec/90000a=ssrc-group:FID 2850853975 3259115858a=ssrc:2850853975 cname:OXEDL0qxAsrhPmB7a=ssrc:2850853975 msid:a3a6a14c-980f-4052-a881-e53290c2e9a8 3f3952f6-cd3d-4d3e-a2dc-81eda77cf516a=ssrc:2850853975 mslabel:a3a6a14c-980f-4052-a881-e53290c2e9a8a=ssrc:2850853975 label:3f3952f6-cd3d-4d3e-a2dc-81eda77cf516a=ssrc:3259115858 cname:OXEDL0qxAsrhPmB7a=ssrc:3259115858 msid:a3a6a14c-980f-4052-a881-e53290c2e9a8 3f3952f6-cd3d-4d3e-a2dc-81eda77cf516a=ssrc:3259115858 mslabel:a3a6a14c-980f-4052-a881-e53290c2e9a8a=ssrc:3259115858 label:3f3952f6-cd3d-4d3e-a2dc-81eda77cf516
尝试了一切,但似乎无法弄清楚。被调用者集合也永远不会在 firestore 数据库的各个房间中创建。对此的任何帮助将不胜感激。
解决方案
我猜您正在尝试使用 firebase 作为信号媒介,并希望使用 react-native-webrtc 进行视频通话。
这是我使用最新库和 react-native 版本的相同解决方案的示例代码。
只需使用上面的链接设置 ios 和 android,然后使用下面的代码作为参考。
import React, {useEffect, useState} from 'react';
import {Modal, TextInput, ToastAndroid, View} from 'react-native';
import {ColoredButton} from '../app/common/commonViews';
import {
mediaDevices,
MediaStream,
RTCIceCandidate,
RTCPeerConnection,
RTCSessionDescription,
RTCView,
} from 'react-native-webrtc';
import firestore from '@react-native-firebase/firestore';
const Home = () => {
const [localStream, setLocalStream] = useState(null);
const [remoteStream, setRemoteStream] = useState(new MediaStream());
const [modalVisible, setModalVisible] = useState(false);
const [roomName, setRoomName] = useState('2277');
let peerConnection;
const configuration = {
iceServers: [
{urls: 'stun:stun.services.mozilla.com'},
{urls: 'stun:stun.l.google.com:19302'},
],
};
let isFront = true;
useEffect(() => {
mediaDevices.enumerateDevices().then((sourceInfos) => {
console.log(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: {
width: 640,
height: 480,
frameRate: 30,
facingMode: isFront ? 'user' : 'environment',
deviceId: videoSourceId,
},
})
.then((stream) => {
setLocalStream(stream);
// Got stream!
})
.catch((error) => {
// Log error
});
});
}, [isFront]);
async function createRoom() {
const roomRef = firestore().collection('rooms').doc(roomName);
console.log(roomRef);
console.log('Create PeerConnection with configuration: ', configuration);
peerConnection = new RTCPeerConnection(configuration);
peerConnection.addStream(localStream);
// Code for collecting ICE candidates below
const callerCandidatesCollection = roomRef.collection('callerCandidates');
// Code for collecting ICE candidates above
peerConnection.onicecandidate = function (event) {
if (!event.candidate) {
console.log('Got final candidate!');
return;
}
console.log('Got candidate: ', event.candidate);
callerCandidatesCollection.add(event.candidate.toJSON());
};
peerConnection.onicegatheringstatechange = () => {
console.log(
`ICE gathering state changed: ${peerConnection.iceGatheringState}`,
);
};
peerConnection.onconnectionstatechange = () => {
console.log(`Connection state change: ${peerConnection.connectionState}`);
};
peerConnection.onsignalingstatechange = () => {
console.log(`Signaling state change: ${peerConnection.signalingState}`);
};
peerConnection.oniceconnectionstatechange = () => {
console.log(
`ICE connection state change: ${peerConnection.iceConnectionState}`,
);
};
console.log('Created offer:', offer);
// Code for creating a room below
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);
const roomWithOffer = {
offer: {
type: offer.type,
sdp: offer.sdp,
},
};
await roomRef.set(roomWithOffer);
console.log(`New room created with SDP offer. Room ID: ${roomRef.id}`);
ToastAndroid.show(
`New room created with SDP offer. Room ID: ${roomRef.id}`,
ToastAndroid.SHORT,
);
// Code for creating a room above
peerConnection.onaddstream = (event) => {
setRemoteStream(event.stream);
};
// Listening for remote session description below
roomRef.onSnapshot(async (snapshot) => {
const data = snapshot.data();
if (!peerConnection.currentRemoteDescription && data && data.answer) {
console.log('Got remote description: ', data.answer);
const rtcSessionDescription = new RTCSessionDescription(data.answer);
await peerConnection.setRemoteDescription(rtcSessionDescription);
}
});
// Listening for remote session description above
// Listen for remote ICE candidates below
roomRef.collection('calleeCandidates').onSnapshot((snapshot) => {
snapshot.docChanges().forEach(async (change) => {
if (change.type === 'added') {
let data = change.doc.data();
console.log(`Got new remote ICE candidate: ${JSON.stringify(data)}`);
await peerConnection.addIceCandidate(new RTCIceCandidate(data));
}
});
});
// Listen for remote ICE candidates above
}
async function joinRoomById(roomId) {
const roomRef = firestore().collection('rooms').doc(`${roomId}`);
const roomSnapshot = await roomRef.get({
source: 'server',
});
console.log('Got room:', roomSnapshot.exists);
if (roomSnapshot.exists) {
console.log('Create PeerConnection with configuration: ', configuration);
peerConnection = new RTCPeerConnection(configuration);
peerConnection.addStream(localStream);
// Code for collecting ICE candidates below
const calleeCandidatesCollection = roomRef.collection('calleeCandidates');
peerConnection.onicecandidate = function (event) {
if (!event.candidate) {
console.log('Got final candidate!');
return;
}
console.log('Got candidate: ', event.candidate);
calleeCandidatesCollection.add(event.candidate.toJSON());
};
peerConnection.onicegatheringstatechange = () => {
console.log(
`ICE gathering state changed: ${peerConnection.iceGatheringState}`,
);
};
peerConnection.onconnectionstatechange = () => {
console.log(
`Connection state change: ${peerConnection.connectionState}`,
);
};
peerConnection.onsignalingstatechange = () => {
console.log(`Signaling state change: ${peerConnection.signalingState}`);
};
peerConnection.oniceconnectionstatechange = () => {
console.log(
`ICE connection state change: ${peerConnection.iceConnectionState}`,
);
};
// Code for collecting ICE candidates above
peerConnection.onaddstream = (event) => {
setRemoteStream(event.stream);
};
// Code for creating SDP answer below
const offer = roomSnapshot.data().offer;
console.log('Got offer:', offer);
await peerConnection.setRemoteDescription(
new RTCSessionDescription(offer),
);
const answer = await peerConnection.createAnswer();
console.log('Created answer:', answer);
await peerConnection.setLocalDescription(answer);
const roomWithAnswer = {
answer: {
type: answer.type,
sdp: answer.sdp,
},
};
await roomRef.update(roomWithAnswer);
// Code for creating SDP answer above
// Listening for remote ICE candidates below
roomRef.collection('callerCandidates').onSnapshot((snapshot) => {
snapshot.docChanges().forEach(async (change) => {
if (change.type === 'added') {
let data = change.doc.data();
console.log(
`Got new remote ICE candidate: ${JSON.stringify(data)}`,
);
await peerConnection.addIceCandidate(new RTCIceCandidate(data));
}
});
});
// Listening for remote ICE candidates above
}
}
return (
<View style={{flex: 1}}>
<Modal
visible={modalVisible}
transparent={true}
style={{justifyContent: 'center'}}>
<View
style={{
height: 100,
padding: 20,
width: '80%',
alignSelf: 'center',
justifyContent: 'center',
backgroundColor: 'white',
}}>
<TextInput
value={roomName}
onChangeText={(text) => {
setRoomName(text);
}}
placeholder={'Enter text'}
/>
<View
style={{
flexDirection: 'row',
justifyContent: 'space-evenly',
}}>
<ColoredButton
title="close"
onPress={() => {
setModalVisible(!modalVisible);
}}
/>
<ColoredButton
title="create"
onPress={() => {
setModalVisible(!modalVisible);
}}
/>
<ColoredButton
title="join"
onPress={() => {
setModalVisible(!modalVisible);
}}
/>
</View>
</View>
</Modal>
<RTCView
streamURL={localStream ? localStream.toURL() : null}
style={{flex: 1, margin: 20, backgroundColor: 'black'}}
/>
<RTCView
style={{
flex: 1,
marginLeft: 20,
marginRight: 20,
backgroundColor: 'black',
}}
streamURL={remoteStream ? remoteStream.toURL() : null}
/>
<TextInput value={roomName} onChangeText={setRoomName} />
<View
style={{
flexDirection: 'row',
justifyContent: 'space-evenly',
width: '100%',
padding: 20,
}}>
<ColoredButton
title="Create"
onPress={() => {
createRoom().then((r) => {});
}}
/>
<ColoredButton
title="Join"
onPress={() => {
joinRoomById(roomName).then((r) => {});
}}
/>
</View>
</View>
);
};
export default Home;