webrtc完美谈判vue 2
我正在实施
经过大量的记录和调试后,我解决了我遇到的一些问题。我在iCeservers
配置中删除了转向服务器,这使候选候选者聚集完成。以前它被困在“聚会”上。现在,这两个对等式已经交换了本地/远程描述并添加了候选ICE,但是ConnectionState
>仍然没有更改。
这是我的rtcpeerConnection
对象:
RTCPeerConnection
canTrickleIceCandidates: true
connectionState: "new"
currentLocalDescription: RTCSessionDescription {type: 'offer', sdp: 'v=0\r\no=- 4764627134364341061 2 IN IP4 127.0.0.1\r\ns…4754 label:f12fee59-268c-4bc3-88c1-8ac27aec8a9c\r\n'}
currentRemoteDescription: RTCSessionDescription {type: 'answer', sdp: 'v=0\r\no=- 3069477756847576830 2 IN IP4 127.0.0.1\r\ns…Nd1pO\r\na=ssrc:1149065622 cname:VquHLgyd/d3Nd1pO\r\n'}
iceConnectionState: "new"
iceGatheringState: "complete"
localDescription: RTCSessionDescription {type: 'offer', sdp: 'v=0\r\no=- 4764627134364341061 2 IN IP4 127.0.0.1\r\ns…4754 label:f12fee59-268c-4bc3-88c1-8ac27aec8a9c\r\n'}
onaddstream: null
onconnectionstatechange: ƒ (e)
ondatachannel: null
onicecandidate: ƒ (_ref)
onicecandidateerror: ƒ (e)
oniceconnectionstatechange: ƒ ()
onicegatheringstatechange: ƒ (e)
onnegotiationneeded: ƒ ()
onremovestream: null
onsignalingstatechange: null
ontrack: ƒ (_ref3)
pendingLocalDescription: null
pendingRemoteDescription: null
remoteDescription: RTCSessionDescription {type: 'answer', sdp: 'v=0\r\no=- 3069477756847576830 2 IN IP4 127.0.0.1\r\ns…Nd1pO\r\na=ssrc:1149065622 cname:VquHLgyd/d3Nd1pO\r\n'}
sctp: null
signalingState: "stable"
[[Prototype]]: RTCPeerConnection
这是liveStream.vue
:
<template>
<div>
<main>
<div>
<div id="video-container">
<h2>LiveStream</h2>
<video id="local-video" ref="localVideo" autoplay="true"></video>
</div>
</div>
</main>
<aside>
<div>
<div>
<p>ViewStream</p>
<div v-for="(item, key) in participants" :key="key">
<Video :videoId="key" :videoStream="participants[key].peerStream" />
</div>
<div></div>
</div>
</div>
</aside>
</div>
</template>
<script>
import { videoConfiguration } from "../mixins/WebRTC";
import Video from "../components/Video.vue";
export default {
name: "LiveStream",
components: {
Video,
},
data() {
return {
participants: {},
localStream: null,
pc: null,
roomInfo: {
room: undefined,
username: "testUser",
},
constraints: {
video: {
width: 450,
height: 348,
},
},
};
},
mixins: [videoConfiguration],
methods: {
async initializeWebRTC(user, desc) {
console.log("initializeWebRTC called", { user, desc });
this.participants[user] = {
...this.participants[user],
pc: this.setupRTCPeerConnection(
new RTCPeerConnection(this.configuration),
user,
this.roomInfo.username,
this.roomInfo.room
),
peerStream: null,
peerVideo: null,
};
for (const track of this.localStream.getTracks()) {
this.participants[user].pc.addTrack(track, this.localStream);
console.log("local track added", track);
}
this.createOffer(
this.participants[user].pc,
user,
this.roomInfo.room,
true
);
this.onIceCandidates(
this.participants[user].pc,
user,
this.roomInfo.room,
true
);
},
createPeerConnection() {
this.pc = new RTCPeerConnection(this.configuration);
},
},
created() {
this.roomInfo.room = this.getRoomName();
},
async mounted() {
this.myVideo = document.getElementById("local-video");
await this.getUserMedia();
await this.getAudioVideo();
this.$socket.client.emit("joinRoom", {
...this.roomInfo,
creator: true,
});
},
beforeDestroy() {
this.pc.close();
this.pc = null;
this.$socket.$unsubscribe("newParticipant");
this.$socket.$unsubscribe("onMessage");
this.$socket.client.emit("leaveRoom", {
to: this.to,
from: this.username,
room: this.roomInfo.room,
});
},
sockets: {
connect() {
console.log("connected socket");
},
newParticipant(userObject) {
if (userObject.username === this.roomInfo.username) return;
this.$set(this.participants, userObject.username, {
user: userObject.username,
});
this.initializeWebRTC(userObject.username);
},
async onMessage({ desc, from, room, candidate }) {
if (from === this.username) return;
try {
if (desc) {
const offerCollision =
desc.type === "offer" &&
(this.makingOffer ||
this.participants[from].pc.signalingState !== "stable");
this.ignoreOffer = !this.isPolitePeer && offerCollision;
if (this.ignoreOffer) {
return;
}
if (desc.type === "offer") {
this.handleAnswer(desc, this.participants[from].pc, from, room);
} else {
this.addRemoteTrack(this.participants[from], from);
await this.setRemoteDescription(desc, this.participants[from].pc);
}
} else if (candidate) {
try {
await this.addCandidate(
this.participants[from].pc,
candidate.candidate
);
} catch (err) {
if (!this.ignoreOffer) {
throw err;
}
}
}
} catch (err) {
console.error(err);
}
},
},
};
</script>
这是我创建的杂物来处理很多连接功能:
export const videoConfiguration = {
data() {
return {
// Media config
constraints: {
audio: {
echoCancellation: true,
noiseSuppression: true,
autoGainControl: false
},
video: {
width: 400,
height: 250
}
},
configuration: {
iceServers: [
{
urls: [
"stun:stun1.l.google.com:19302",
"stun:stun2.l.google.com:19302"
]
}
]
},
offerOptions: {
offerToReceiveAudio: 1,
offerToReceiveVideo: 1
},
myVideo: null,
localStream: null,
username: null,
isPolitePeer: false,
makingOffer: false,
ignoreOffer: false
};
},
async created() {
this.username = await this.getUsername();
},
beforeDestroy() {
this.localStream.getTracks().forEach((track) => track.stop());
},
methods: {
/**
* Get permission to read from user's microphone and camera.
* Returns audio and video streams to be added to video element
*/
async getUserMedia() {
if ("mediaDevices" in navigator) {
try {
const stream = await navigator.mediaDevices.getUserMedia(
this.constraints
);
if ("srcObject" in this.myVideo) {
this.myVideo.srcObject = stream;
this.myVideo.volume = 0;
} else {
this.myVideo.src = stream;
}
this.localStream = stream;
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
}
}
},
getAudioVideo() {
const video = this.localStream.getVideoTracks();
// eslint-disable-next-line no-console
console.log(video);
const audio = this.localStream.getAudioTracks();
// eslint-disable-next-line no-console
console.log(audio);
},
async setRemoteDescription(remoteDesc, pc) {
try {
await pc.setRemoteDescription(remoteDesc);
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
}
},
addCandidate(pc, candidate) {
try {
const rtcIceCandidate = new RTCIceCandidate(candidate);
pc.addIceCandidate(rtcIceCandidate);
console.log(`${this.username} added a candidate`);
} catch (error) {
console.error(
`Error adding a candidate in ${this.username}. Error: ${error}`
);
}
},
onIceCandidates(pc, to, room) {
pc.onicecandidate = ({ candidate }) => {
if (!candidate) return;
this.$socket.client.emit("new-ice-candidate", {
candidate,
to: to,
from: this.username,
room: room
});
};
},
async createOffer(pc, to, room) {
console.log(`${this.roomInfo.username} wants to start a call with ${to}`);
pc.onnegotiationneeded = async () => {
try {
this.makingOffer = true;
await pc.setLocalDescription();
this.sendSignalingMessage(pc.localDescription, true, to, room);
} catch (err) {
console.error(err);
} finally {
this.makingOffer = false;
}
};
},
async createAnswer(pc, to, room) {
try {
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
this.sendSignalingMessage(pc.localDescription, false, to, room);
} catch (error) {
console.error(error);
}
},
async handleAnswer(desc, pc, from, room) {
await this.setRemoteDescription(desc, pc);
this.createAnswer(pc, from, room);
},
sendSignalingMessage(desc, offer, to, room) {
const isOffer = offer ? "offer" : "answer";
// Send the offer to the other peer
if (isOffer === "offer") {
this.$socket.client.emit("offer", {
desc: desc,
to: to,
from: this.username,
room: room,
offer: isOffer
});
} else {
this.$socket.client.emit("answer", {
desc: desc,
to: to,
from: this.username,
room: room,
offer: isOffer
});
}
},
addRemoteTrack(user, video) {
user.peerVideo = user.peerVideo || document.getElementById(video);
user.pc.ontrack = ({ track, streams }) => {
user.peerStream = streams[0];
track.onunmute = () => {
if (user.peerVideo.srcObject) {
return;
}
user.peerVideo.srcObject = streams[0];
};
};
},
/**
* Using handleRemoteTrack temporarily to add the tracks to the RTCPeerConnection
* for ViewStream since the location of pc is different.
* @param {*} user
*/
handleRemoteTrack(pc, user) {
this.peerVideo = document.getElementById(user);
pc.ontrack = ({ track, streams }) => {
this.peerStream = streams[0];
track.onunmute = () => {
if (this.peerVideo.srcObject) {
return;
}
this.peerVideo.srcObject = streams[0];
};
};
},
setupRTCPeerConnection(pc) {
pc.onconnectionstatechange = (e) => {
console.log(
"WebRTC: Signaling State Updated: ",
e.target.signalingState
);
};
pc.oniceconnectionstatechange = () => {
console.log("WebRTC: ICE Connection State Updated");
};
pc.onicegatheringstatechange = (e) => {
console.log(
"WebRTC: ICE Gathering State Updated: ",
e.target.iceGatheringState
);
};
pc.onicecandidateerror = (e) => {
if (e.errorCode === 701) {
console.log("ICE Candidate Error: ", e);
}
};
return pc;
}
}
};
我创建了一个具有viewStream.Vue 文件和我尝试设置的目录结构。 (在此处发布的代码太多了。)
彩带,我可以看到他们交换了报价/答案和候选冰。但是,我仍然看不到
ConnectionState
或iceconnectionState
的任何更改。我没有做一块吗?我在记录数据并挖掘Chrome时注意到的一件事是:// Webrtc-internals/是
。MediaStream
ID不匹配。 我在调用getUsermedia()
的呼叫后记录了轨道,并记下轨道ID的
此图显示了呼叫者(顶部)和callee(底部)
我在将本地曲目添加到rtcpeerconnection
和它们与两个同龄人生成的东西匹配。
在这里,流媒体的曲目添加到rtcpeerconnection
中。 IDS从上方匹配。
,我也为每个同行登录,这是ID不匹配的时候。
我不知道这张图片中正在生成ID是什么。在第一张图片中,它与Callee的ID不同。
那是正常行为吗? ID不匹配的事实是否是流的原因,这两端都不始于两端?我不知道是什么原因引起的。当呼叫的任一端添加到rtcpeerconnection
时,ID是相同的。
编辑5/1:我从配置中删除了转向服务器,以及连接过程的固定部分。仍然有问题,使媒体在同行之间流动。但是我可以看到,我已经在连接的每一侧捕获了MediaStream
。
I'm implementing WebRTC Perfect Negotiation in my Vue 2 application. The app will have multiple viewers and a single streamer.
After a lot of logging and debugging, I've resolved some of the problems that I was having. I removed the TURN server in the iceServers
configuration, and that allowed the ICE Candidate gathering to finish. Previously it was stuck at "gathering". Now, the two peers have exchanged local/remote descriptions and added ICE candidates, but there still is not a change in the connectionState
.
Here is my RTCPeerConnection
object:
RTCPeerConnection
canTrickleIceCandidates: true
connectionState: "new"
currentLocalDescription: RTCSessionDescription {type: 'offer', sdp: 'v=0\r\no=- 4764627134364341061 2 IN IP4 127.0.0.1\r\ns…4754 label:f12fee59-268c-4bc3-88c1-8ac27aec8a9c\r\n'}
currentRemoteDescription: RTCSessionDescription {type: 'answer', sdp: 'v=0\r\no=- 3069477756847576830 2 IN IP4 127.0.0.1\r\ns…Nd1pO\r\na=ssrc:1149065622 cname:VquHLgyd/d3Nd1pO\r\n'}
iceConnectionState: "new"
iceGatheringState: "complete"
localDescription: RTCSessionDescription {type: 'offer', sdp: 'v=0\r\no=- 4764627134364341061 2 IN IP4 127.0.0.1\r\ns…4754 label:f12fee59-268c-4bc3-88c1-8ac27aec8a9c\r\n'}
onaddstream: null
onconnectionstatechange: ƒ (e)
ondatachannel: null
onicecandidate: ƒ (_ref)
onicecandidateerror: ƒ (e)
oniceconnectionstatechange: ƒ ()
onicegatheringstatechange: ƒ (e)
onnegotiationneeded: ƒ ()
onremovestream: null
onsignalingstatechange: null
ontrack: ƒ (_ref3)
pendingLocalDescription: null
pendingRemoteDescription: null
remoteDescription: RTCSessionDescription {type: 'answer', sdp: 'v=0\r\no=- 3069477756847576830 2 IN IP4 127.0.0.1\r\ns…Nd1pO\r\na=ssrc:1149065622 cname:VquHLgyd/d3Nd1pO\r\n'}
sctp: null
signalingState: "stable"
[[Prototype]]: RTCPeerConnection
Here is LiveStream.vue
:
<template>
<div>
<main>
<div>
<div id="video-container">
<h2>LiveStream</h2>
<video id="local-video" ref="localVideo" autoplay="true"></video>
</div>
</div>
</main>
<aside>
<div>
<div>
<p>ViewStream</p>
<div v-for="(item, key) in participants" :key="key">
<Video :videoId="key" :videoStream="participants[key].peerStream" />
</div>
<div></div>
</div>
</div>
</aside>
</div>
</template>
<script>
import { videoConfiguration } from "../mixins/WebRTC";
import Video from "../components/Video.vue";
export default {
name: "LiveStream",
components: {
Video,
},
data() {
return {
participants: {},
localStream: null,
pc: null,
roomInfo: {
room: undefined,
username: "testUser",
},
constraints: {
video: {
width: 450,
height: 348,
},
},
};
},
mixins: [videoConfiguration],
methods: {
async initializeWebRTC(user, desc) {
console.log("initializeWebRTC called", { user, desc });
this.participants[user] = {
...this.participants[user],
pc: this.setupRTCPeerConnection(
new RTCPeerConnection(this.configuration),
user,
this.roomInfo.username,
this.roomInfo.room
),
peerStream: null,
peerVideo: null,
};
for (const track of this.localStream.getTracks()) {
this.participants[user].pc.addTrack(track, this.localStream);
console.log("local track added", track);
}
this.createOffer(
this.participants[user].pc,
user,
this.roomInfo.room,
true
);
this.onIceCandidates(
this.participants[user].pc,
user,
this.roomInfo.room,
true
);
},
createPeerConnection() {
this.pc = new RTCPeerConnection(this.configuration);
},
},
created() {
this.roomInfo.room = this.getRoomName();
},
async mounted() {
this.myVideo = document.getElementById("local-video");
await this.getUserMedia();
await this.getAudioVideo();
this.$socket.client.emit("joinRoom", {
...this.roomInfo,
creator: true,
});
},
beforeDestroy() {
this.pc.close();
this.pc = null;
this.$socket.$unsubscribe("newParticipant");
this.$socket.$unsubscribe("onMessage");
this.$socket.client.emit("leaveRoom", {
to: this.to,
from: this.username,
room: this.roomInfo.room,
});
},
sockets: {
connect() {
console.log("connected socket");
},
newParticipant(userObject) {
if (userObject.username === this.roomInfo.username) return;
this.$set(this.participants, userObject.username, {
user: userObject.username,
});
this.initializeWebRTC(userObject.username);
},
async onMessage({ desc, from, room, candidate }) {
if (from === this.username) return;
try {
if (desc) {
const offerCollision =
desc.type === "offer" &&
(this.makingOffer ||
this.participants[from].pc.signalingState !== "stable");
this.ignoreOffer = !this.isPolitePeer && offerCollision;
if (this.ignoreOffer) {
return;
}
if (desc.type === "offer") {
this.handleAnswer(desc, this.participants[from].pc, from, room);
} else {
this.addRemoteTrack(this.participants[from], from);
await this.setRemoteDescription(desc, this.participants[from].pc);
}
} else if (candidate) {
try {
await this.addCandidate(
this.participants[from].pc,
candidate.candidate
);
} catch (err) {
if (!this.ignoreOffer) {
throw err;
}
}
}
} catch (err) {
console.error(err);
}
},
},
};
</script>
Here is the mixin I created to handle a lot of the connection functionality:
export const videoConfiguration = {
data() {
return {
// Media config
constraints: {
audio: {
echoCancellation: true,
noiseSuppression: true,
autoGainControl: false
},
video: {
width: 400,
height: 250
}
},
configuration: {
iceServers: [
{
urls: [
"stun:stun1.l.google.com:19302",
"stun:stun2.l.google.com:19302"
]
}
]
},
offerOptions: {
offerToReceiveAudio: 1,
offerToReceiveVideo: 1
},
myVideo: null,
localStream: null,
username: null,
isPolitePeer: false,
makingOffer: false,
ignoreOffer: false
};
},
async created() {
this.username = await this.getUsername();
},
beforeDestroy() {
this.localStream.getTracks().forEach((track) => track.stop());
},
methods: {
/**
* Get permission to read from user's microphone and camera.
* Returns audio and video streams to be added to video element
*/
async getUserMedia() {
if ("mediaDevices" in navigator) {
try {
const stream = await navigator.mediaDevices.getUserMedia(
this.constraints
);
if ("srcObject" in this.myVideo) {
this.myVideo.srcObject = stream;
this.myVideo.volume = 0;
} else {
this.myVideo.src = stream;
}
this.localStream = stream;
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
}
}
},
getAudioVideo() {
const video = this.localStream.getVideoTracks();
// eslint-disable-next-line no-console
console.log(video);
const audio = this.localStream.getAudioTracks();
// eslint-disable-next-line no-console
console.log(audio);
},
async setRemoteDescription(remoteDesc, pc) {
try {
await pc.setRemoteDescription(remoteDesc);
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
}
},
addCandidate(pc, candidate) {
try {
const rtcIceCandidate = new RTCIceCandidate(candidate);
pc.addIceCandidate(rtcIceCandidate);
console.log(`${this.username} added a candidate`);
} catch (error) {
console.error(
`Error adding a candidate in ${this.username}. Error: ${error}`
);
}
},
onIceCandidates(pc, to, room) {
pc.onicecandidate = ({ candidate }) => {
if (!candidate) return;
this.$socket.client.emit("new-ice-candidate", {
candidate,
to: to,
from: this.username,
room: room
});
};
},
async createOffer(pc, to, room) {
console.log(`${this.roomInfo.username} wants to start a call with ${to}`);
pc.onnegotiationneeded = async () => {
try {
this.makingOffer = true;
await pc.setLocalDescription();
this.sendSignalingMessage(pc.localDescription, true, to, room);
} catch (err) {
console.error(err);
} finally {
this.makingOffer = false;
}
};
},
async createAnswer(pc, to, room) {
try {
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
this.sendSignalingMessage(pc.localDescription, false, to, room);
} catch (error) {
console.error(error);
}
},
async handleAnswer(desc, pc, from, room) {
await this.setRemoteDescription(desc, pc);
this.createAnswer(pc, from, room);
},
sendSignalingMessage(desc, offer, to, room) {
const isOffer = offer ? "offer" : "answer";
// Send the offer to the other peer
if (isOffer === "offer") {
this.$socket.client.emit("offer", {
desc: desc,
to: to,
from: this.username,
room: room,
offer: isOffer
});
} else {
this.$socket.client.emit("answer", {
desc: desc,
to: to,
from: this.username,
room: room,
offer: isOffer
});
}
},
addRemoteTrack(user, video) {
user.peerVideo = user.peerVideo || document.getElementById(video);
user.pc.ontrack = ({ track, streams }) => {
user.peerStream = streams[0];
track.onunmute = () => {
if (user.peerVideo.srcObject) {
return;
}
user.peerVideo.srcObject = streams[0];
};
};
},
/**
* Using handleRemoteTrack temporarily to add the tracks to the RTCPeerConnection
* for ViewStream since the location of pc is different.
* @param {*} user
*/
handleRemoteTrack(pc, user) {
this.peerVideo = document.getElementById(user);
pc.ontrack = ({ track, streams }) => {
this.peerStream = streams[0];
track.onunmute = () => {
if (this.peerVideo.srcObject) {
return;
}
this.peerVideo.srcObject = streams[0];
};
};
},
setupRTCPeerConnection(pc) {
pc.onconnectionstatechange = (e) => {
console.log(
"WebRTC: Signaling State Updated: ",
e.target.signalingState
);
};
pc.oniceconnectionstatechange = () => {
console.log("WebRTC: ICE Connection State Updated");
};
pc.onicegatheringstatechange = (e) => {
console.log(
"WebRTC: ICE Gathering State Updated: ",
e.target.iceGatheringState
);
};
pc.onicecandidateerror = (e) => {
if (e.errorCode === 701) {
console.log("ICE Candidate Error: ", e);
}
};
return pc;
}
}
};
I created a CodeSandbox that has the ViewStream.vue
file and the directory structure for how I'm trying to set it up. (It's just too much code to post here.)
When the viewer joins the room created by the streamer, I can see that they exchange offer/answer and ice candidates. However, I still do not see any change in the
connectionState
oriceConnectionState
. Is there a piece that I'm not doing?One thing I noticed when logging data and digging through chrome://webrtc-internals/ is that the
MediaStream
ID's don't match.
I log out the tracks after the call togetUserMedia()
, and note the track ID's.
This image shows the stream IDs for the caller (top) and the callee (bottom)
I then log when I'm adding the local tracks to the RTCPeerConnection
, and they match what was generated for both peers.
Here, the tracks for the streamer are added to the RTCPeerConnection
. The IDs match from above.
However, I'm also logging for each peer when I receive a remote track, and that's when the ID's don't match.
I don't know what is generating the ID in this picture. It's different from the ID of the callee in the first picture.
Is that normal behavior? Would the fact that the IDs don't match be the cause of the streams not starting on either end? I don't know what would cause this. The IDs are the same when added to the RTCPeerConnection
on either end of the call.
Edit 5/1: I removed the TURN server from my config, and that fixed part of the connection process. Still having a problem getting media to flow between peers. But I can see that I've captured a MediaStream
on each side of the connection.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
看起来
ConnectionState
没有更新。在创建要约和答案之间可能存在种族条件,而该要约和答案不允许ConnectionState
更新。您可能希望看到添加有关创建要约和答案的承诺,以及何时完成候选人以应对这种情况。
It looks like the
connectionState
is not updating. There maybe a race condition between creating the offer and answer that is not allowingconnectionState
to update.You may want see look into adding a promise on creating the offer and answer, and on when the ice candidates are completed to handle this race condition.
我可以有服务器端代码吗?这样我就可以重新创建发生了什么事?
编辑:
如果您在第二个问题中谈论这一点。
我想这只是说明了如何通过有礼貌的同伴成为来电者的callee解决礼貌和不礼貌的同伴之间的碰撞。
这仅仅是
polite peer
的定义,我不必担心在这种情况下明确更改角色。基本上是一个有礼貌的同行,是可能会发送要约的,但是如果要约从另一个同伴到达,则响应: 〜
”
Can I have the Server side code? So that I can recreate whats going on?
Edit:
If you are talking about this in your second question.
I guess it just explains how the collision between a polite and impolite peer is resolved by polite peer becoming callee from caller.
And that is just the definition of
Polite Peer
and I thing you don't need to worry much about explicitly changing the roles in this context.A polite peer, essentially, is one which may send out offers, but then responds if an offer arrives from the other peer with "Okay, never mind, drop my offer and I'll consider yours instead.
~ MDN DocsI hope this answers your Second Question
找到解决方案:
我遇到了此错误:
typeError:无法执行“ rtcpeerconnnection”上的“ addiceCandidate”:提供的值不在类型的'rtcicecandidateInit'。
google> Google> Google fine google fine google fine google degroment google degroment goge in to ground goge n ground goge。 “ https://stackoverflow.com/questions/58908081/webrtc-getting-failed-to-to-execute-execute-execute-addicecandidate-on-rtcpeerconnection-error-onor-on"> thiso thisso thisso so strice 这使我相信这是一份我可以安全地忽略的错误。
但是,MDN文档说,如果我缺少对象的必需部分,我会得到一个TypeError,这使我意识到,在通话的两边,我都在破坏
{cantixing}
,传递不完整的对象。因此,将传递给每个政党的候选人并没有真正被添加。一旦我解决了一切工作。Found the solution:
I was getting this error:
TypeError: Failed to execute 'addIceCandidate' on 'RTCPeerConnection': The provided value is not of type 'RTCIceCandidateInit'.
Googling that led me to this SO article which led me to believe that it was an error that I could safely ignore.
However, the MDN docs said that I'd get a TypeError if I was missing a required part of the object, and that led me to realize that on both sides of the call I was destructuring
{candidate}
, passing in an incomplete object. So the candidates that were being passed to each party weren't really being added. Once I fixed that everything worked.