所以我一直在尝试使用 WebRTC 在 Vue 中创建一个简单的实时流应用程序,它将视频帧从一侧流式传输到另一侧(而不是相反),这是我到目前为止一直在尝试的 example .在此示例中,我使用 firestore 作为信令服务器,用于在两个对等方之间交换 SDP 和 ICE 候选者。但简而言之,将有两种视图,一种称为“CompOffer”,代表想要初始化报价并从其网络摄像头流式传输到另一个的流媒体,另一种称为“CompAnswer”,代表想要回答该报价并接收的一方框架。
CompOffer :
<template>
<div>
<button @click="offer">Offer</button>
<button @click="connect">Connect</button>
</div>
</template>
<script>
// eslint-disable-next-line
import firestore from "@/firestore";
// eslint-disable-next-line
import { updateDoc, getDoc, doc, setDoc, arrayUnion } from "firebase/firestore";
export default {
data() {
return {
pc: null
};
},
methods: {
async offer() {
// Create a new peer connection
const turn_config = {
iceServers: [
{
urls: "stun:relay.metered.ca:80"
},
{
urls: "turn:relay.metered.ca:80",
username: "7c6e2dfc7ba5dd33578fc9e1",
credential: "18GkZDVEKpCweYAf"
},
{
urls: "turn:relay.metered.ca:443",
username: "7c6e2dfc7ba5dd33578fc9e1",
credential: "18GkZDVEKpCweYAf"
},
{
urls: "turn:relay.metered.ca:443?transport=tcp",
username: "7c6e2dfc7ba5dd33578fc9e1",
credential: "18GkZDVEKpCweYAf"
}
]
};
this.pc = new RTCPeerConnection(turn_config);
this.pc.oniceconnectionstatechange = () => {
console.log(`ICE connection state changed to ${this.pc.iceConnectionState}`);
};
// Getting the user media ( Webcam in this case )
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
stream.getTracks().forEach((track) => {
this.pc.addTrack(track, stream);
// Adding the tracks to the peer to stream to the other side
});
// Setting the ICE candidates to firestore
const icesref = doc(firestore, "offer", "ices");
await setDoc(icesref, {
ices: []
}); // CLearing out the prevbiously ICE candidates list
this.pc.onicecandidate = (e) => {
if (e.candidate) {
updateDoc(icesref, {
ices: arrayUnion(e.candidate.toJSON())
}).then(() => {
console.log("Successfully set ICE candidate");
});
}
};
// Create an offer and set it in firestore
let offer = await this.pc.createOffer();
await setDoc(doc(firestore, "offer", "main"), offer.toJSON());
// Setting the offer to firestore
console.log("Successfully set offer");
await this.pc.setLocalDescription(offer);
},
async connect() {
// Extracting the answer and set to local description
// Extract he ICE candidates also
// Getting Answerer's SDP here
const answer = await getDoc(doc(firestore, "answer", "main")); // Getting the answer
const ices = await getDoc(doc(firestore, "answer", "ices")); // Get answerer ICE candidates
let answersdp, iceslistreal;
if (answer.exists()) answersdp = answer.data();
if (ices.exists()) iceslistreal = ices.data();
// Setting remote Description here
await this.pc.setRemoteDescription(new RTCSessionDescription(answersdp));
iceslistreal["ices"].forEach((e) => {
this.pc.addIceCandidate(new RTCIceCandidate(e));
});
console.log("Successfully set remote description");
}
}
};
</script>
CompAnswer :
<template>
<div>
<button @click="answer">Answer</button>
<video id="vid" playsinline=""></video>
</div>
</template>
<script>
import firestore from "@/firestore";
import { setDoc, doc, arrayUnion, updateDoc, getDoc } from "@firebase/firestore";
export default {
data() {
return { pc: null, stream: new MediaStream() };
},
methods: {
async answer() {
// TURN servers
const turn_config = {
iceServers: [
{
urls: "stun:relay.metered.ca:80"
},
{
urls: "turn:relay.metered.ca:80",
username: "7c6e2dfc7ba5dd33578fc9e1",
credential: "18GkZDVEKpCweYAf"
},
{
urls: "turn:relay.metered.ca:443",
username: "7c6e2dfc7ba5dd33578fc9e1",
credential: "18GkZDVEKpCweYAf"
},
{
urls: "turn:relay.metered.ca:443?transport=tcp",
username: "7c6e2dfc7ba5dd33578fc9e1",
credential: "18GkZDVEKpCweYAf"
}
]
};
this.pc = new RTCPeerConnection(turn_config);
this.pc.oniceconnectionstatechange = () => {
console.log(`ICE connection state changed to ${this.pc.iceConnectionState}`);
};
// Sending the answerer's ICE candidates to the offerer
const icesref = doc(firestore, "answer", "ices");
await setDoc(icesref, { ices: [] });
this.pc.onicecandidate = (e) => {
if (e.candidate) {
updateDoc(icesref, {
ices: arrayUnion(e.candidate.toJSON())
}).then(() => {
console.log("Successfully set ICE candidate");
});
}
};
// Receiving remote tracks
this.pc.ontrack = (event) => {
console.log("New track recieved !!!");
event.streams[0].getTracks().forEach((track) => {
this.stream.addTrack(track);
});
};
// Set the track into the DOM
const vid = document.getElementById("vid");
vid.srcObject = this.stream;
vid.play(); // Make sure to play the video, otherwise there will be no video
// ----Extracting the offer from firestore
const offer = await getDoc(doc(firestore, "offer", "main")); // Get the main SDP
const iceslist = await getDoc(doc(firestore, "offer", "ices")); // Get offerer ICE candidates
let offersdp, iceslistreal;
if (offer.exists()) offersdp = offer.data();
if (iceslist.exists()) iceslistreal = iceslist.data();
// Setting remote description here
await this.pc.setRemoteDescription(new RTCSessionDescription(offersdp));
// Generating answer and setting the answer
let answer = await this.pc.createAnswer();
await setDoc(doc(firestore, "answer", "main"), answer.toJSON());
console.log("Successfully set the answer");
// Setting local description and offerer's ICE candidates here
await this.pc.setLocalDescription(new RTCSessionDescription(answer));
iceslistreal["ices"].forEach((e) => {
this.pc.addIceCandidate(new RTCIceCandidate(e));
});
}
}
};
</script>
初始化连接的步骤如下:
我的示例在本地网络中运行良好,但是当我将它发布到云实例并在两个不同网络上的两台不同机器上尝试这两个视图时,它似乎不再起作用了。
所以我怀疑需要一个 TURN 服务器,因为这两个设备位于对称 NAT 之后,所以我尝试注册一个 OpenRelay 项目 的帐户以获取一些免费的 TURN 服务器用于测试目的,并且我为我的两个设备重组了对等配置观点如下:
代替:
this.pc = new RTCPeerConnection();
我试过了:
const turn_config = {
iceServers: [
{
urls: "stun:relay.metered.ca:80"
},
{
urls: "turn:relay.metered.ca:80",
username: "7c6e2dfc7ba5dd33578fc9e1",
credential: "18GkZDVEKpCweYAf"
},
{
urls: "turn:relay.metered.ca:443",
username: "7c6e2dfc7ba5dd33578fc9e1",
credential: "18GkZDVEKpCweYAf"
},
{
urls: "turn:relay.metered.ca:443?transport=tcp",
username: "7c6e2dfc7ba5dd33578fc9e1",
credential: "18GkZDVEKpCweYAf"
}
]
};
this.pc = new RTCPeerConnection(turn_config);
在两个视图上,但是当我在两个不同的网络中重新测试它时,它仍然无法正常工作,查看器端没有接收到帧,等待片刻后,双方都导致
ICE connection state changed to disconnected
.
在那之后,我也确实尝试在 turn_config 中设置
iceTransportPolicy : "relay"
,因为我猜它不是从 TURN 服务器请求候选人,但是当我这样做时,它根本没有请求任何 ICE 候选人(onicecandidate 不是甚至打过一次)
这里是的控制台日志,这里是的控制台日志。我很确定我粘贴了 https://dashboard.metered.ca/ 给我的正确配置,但它仍然无法正常工作。所以请帮忙,我真的很感激,我使用的 TURN 服务器中的凭据仅用于测试目的,请随意调整或尝试任何必要的诊断方法。
几天前我遇到了类似的问题(老实说free turn服务器是骗局)然后一天后就开始工作了。但现在又宕机了。根据我的经验,他们经常停电并且非常不可靠。 试试这个网站来测试转服务器:https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/
如果仅用于测试目的,则注册免费层:https://www.twilio.com/docs/stun-turn?code-sample=code-generate-nts-token&code-language=Node.js&code-sdk-版本=4.x
抱歉我不能添加评论所以我添加到这篇文章 - 分享我也面临部署在 streamlit 上的 WebRTC 组件的类似问题,https://webrtc.streamlit.app/ 这是一个演示应用程序在 @whitphx 完成的 streamlit 中展示 webrtc 组件。到目前为止,还没有解决方案,令人沮丧。
最初谷歌 STUN 服务器在我将它部署到云端后似乎可以工作,但几天后它就失败了。在我从 OpenRelay 添加 TURN 服务器后,它可以工作,但 2 天后它再次失败..
当我在 TrickleICE 上测试 STUN/TURN 服务器时,它可以工作。 我注意到最近 2 周内发生了这种情况,在此之前该应用程序运行良好。
这是我的问题的更多细节的链接——它与你的问题非常相似。 https://discuss.streamlit.io/t/inconsistent-issue-with-streamlit-webrtc-and-streamlit/39447