- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我正在尝试在同一连接中与两个视频轨道建立 WebRTC 连接,就在收到报价后。
主叫方在接听电话时收不到被叫方添加的所有视频轨道。但是,调用者可以启动提供两个或更多视频轨道的连接。
这就是调用者(发送者)正在做的事情:
const senderStreams = [localStream1];
senderStreams.forEach((stream) => {
stream.getVideoTracks().forEach((track) => sender.addTrack(track, stream));
});
这就是被调用者(接收者)正在做的事情:
const receiverStreams = [localStream2, localStream3]
receiver.onsignalingstatechange = async () => {
if (receiver.signalingState === "have-remote-offer") {
receiverStreams.forEach((stream) => {
stream
.getVideoTracks()
.forEach((track) => receiver.addTrack(track, stream));
});
const answer = await receiver.createAnswer();
await receiver.setLocalDescription(answer);
await sender.setRemoteDescription(answer);
}
};
调用者(发送者)应该收到两个跟踪事件:
sender.ontrack = (e) => {
console.log(`Sender received track:`, e.track.id);
// ...
};
这是完整的 POC 实现:
"use strict";
let localStream1, localStream2, localStream3;
let sender, receiver;
main();
function main() {
const btnOffer1 = document.getElementById("btnOffer1");
const btnOffer2 = document.getElementById("btnOffer2");
const buttons = document.querySelector(".buttons");
btnOffer1.addEventListener("click", () => {
startCall(1);
buttons.remove();
});
btnOffer2.addEventListener("click", () => {
startCall(2);
buttons.remove();
});
}
function startCall(offerOptionNum) {
localStream1 = createCanvasStream();
localStream2 = createCanvasStream();
localStream3 = createCanvasStream();
const senderStreams =
offerOptionNum === 1 ? [localStream1] : [localStream1, localStream2];
const receiverStreams =
offerOptionNum === 1 ? [localStream2, localStream3] : [localStream3];
document.getElementById("senderTotalLocalTracks").innerText =
senderStreams.length;
document.getElementById("receiverTotalLocalTracks").innerText =
receiverStreams.length;
sender = new RTCPeerConnection();
sender.onicecandidate = (e) => onIceCandidate(sender, e);
receiver = new RTCPeerConnection();
receiver.onicecandidate = (e) => onIceCandidate(receiver, e);
sender.onconnectionstatechange = () => onConnectionStateChange(sender);
receiver.onconnectionstatechange = () => onConnectionStateChange(receiver);
sender.onsignalingstatechange = async() => {
console.log(`${getName(sender)} Signaling state: ${sender.signalingState}`);
if (sender.signalingState === "have-local-offer") {
await receiver.setRemoteDescription(sender.localDescription);
}
};
sender.onnegotiationneeded = async() => {
await sender.setLocalDescription(await sender.createOffer());
};
receiver.onsignalingstatechange = async() => {
console.log(
`${getName(receiver)} Signaling state: ${receiver.signalingState}`
);
if (receiver.signalingState === "have-remote-offer") {
receiverStreams.forEach((stream) => {
stream
.getVideoTracks()
.forEach((track) => receiver.addTrack(track, stream));
});
const answer = await receiver.createAnswer();
await receiver.setLocalDescription(answer);
await sender.setRemoteDescription(answer);
}
};
sender.ontrack = (e) => {
console.log(`${getName(sender)} received track:`, e.track.id);
const el = document.getElementById("senderTotalRemoteTracks");
el.innerText = Number(el.innerText) + 1;
};
receiver.ontrack = (e) => {
console.log(`${getName(receiver)} received track:`, e.track.id);
const el = document.getElementById("receiverTotalRemoteTracks");
el.innerText = Number(el.innerText) + 1;
};
senderStreams.forEach((stream) => {
stream.getVideoTracks().forEach((track) => sender.addTrack(track, stream));
});
}
function createCanvasStream() {
const canvas = Object.assign(
document.createElement("canvas", {
width: 640,
height: 480,
})
);
const ctx = canvas.getContext("2d");
const stream = canvas.captureStream(1);
const drawInCanvas = () => ctx.fillRect(0, 0, canvas.width, canvas.height);
drawInCanvas();
setInterval(() => {
drawInCanvas();
}, 1000);
return stream;
}
async function onIceCandidate(pc, event) {
if (event.candidate) {
try {
await getOtherPc(pc).addIceCandidate(event.candidate);
} catch (error) {
console.error(error, event.candidate);
}
}
}
function onConnectionStateChange(pc) {
if (pc) {
console.log(`${getName(pc)} Connection state: ${pc.connectionState}`);
}
}
function getName(pc) {
return pc === sender ? "Sender" : "Receiver";
}
function getOtherPc(pc) {
return pc === sender ? receiver : sender;
}
html,
body {
margin: 0;
font-family: system-ui, sans-serif;
color: #222;
background: #f8f8f8;
}
input,
textarea {
font-size: 1em;
box-sizing: border-box;
padding: 6px 8px;
}
button,
code,
kbd,
pre {
font-size: 1em;
}
code,
kbd,
pre {
font-family: "Menlo", "Monaco", monospace;
border-radius: 3px;
box-sizing: border-box;
padding: 2px 4px 1px 4px;
background: rgba(0, 0, 0, 0.1);
}
pre {
padding: 8px 12px;
}
p {
line-height: 1.5em;
}
a {
color: #222;
}
a:hover {
color: #666;
}
.cards {
display: flex;
}
.buttons {
display: flex;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="https://unpkg.com/blocks.css/dist/blocks.min.css" />
</head>
<body>
<div class="cards">
<div class="card fixed block sender">
<h2>sender</h2>
<p>Remote tracks: <span id="senderTotalRemoteTracks">0</span></p>
<p>Local tracks: <span id="senderTotalLocalTracks">0</span></p>
</div>
<div class="card fixed block receiver">
<h2>receiver</h2>
<p>Remote tracks: <span id="receiverTotalRemoteTracks">0</span></p>
<p>Local tracks: <span id="receiverTotalLocalTracks">0</span></p>
</div>
</div>
<div class="buttons">
<button class="block accent" id="btnOffer1">Offer 1 track / Receive 2 tracks</button>
<button class="block" id="btnOffer2">Offer 2 tracks / Receive 1 track</button>
</div>
</body>
</html>
这可能吗?我做错了什么?
最佳答案
显然,这是 WebRTC 重新协商的情况。
再向接收方添加一条轨道将触发其 negotiationneeded
事件。
根据docs ,
This occurs both during the initial setup of the connection as well asany time a change to the communication environment requiresreconfiguring the connection.
所以我修改了POC来支持双向协商过程,如下:
async function onNegotiationNeeded(pc) {
console.log(`${getName(pc)} negotiationneeded event`);
await pc.setLocalDescription(await pc.createOffer());
}
async function onSignalingStateChange(pc) {
console.log(`${getName(pc)} Signaling state: ${pc.signalingState}`);
const otherPc = getOtherPc(pc);
if (pc.signalingState === "have-local-offer") {
await otherPc.setRemoteDescription(pc.localDescription);
} else if (pc.signalingState === "have-remote-offer") {
if (pc === receiver) {
receiverStreams.forEach((stream) => {
stream.getVideoTracks().forEach((track) => pc.addTrack(track, stream));
});
}
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
await otherPc.setRemoteDescription(answer);
}
}
sender.onnegotiationneeded = () => onNegotiationNeeded(sender);
receiver.onnegotiationneeded = () => onNegotiationNeeded(receiver);
sender.onsignalingstatechange = () => onSignalingStateChange(sender);
receiver.onsignalingstatechange = () => onSignalingStateChange(receiver);
这里是完整的实现:
"use strict";
let localStream1, localStream2, localStream3;
let sender, receiver;
let senderStreams = [],
receiverStreams = [];
main();
function main() {
const btnOffer1 = document.getElementById("btnOffer1");
const btnOffer2 = document.getElementById("btnOffer2");
const buttons = document.querySelector(".buttons");
const btnTryAgain = document.getElementById("btnTryAgain");
btnOffer1.addEventListener("click", () => {
startCall(1);
buttons.remove();
});
btnOffer2.addEventListener("click", () => {
startCall(2);
buttons.remove();
});
btnTryAgain.addEventListener("click", () => {
window.location.reload();
});
}
function startCall(offerOptionNum) {
localStream1 = createCanvasStream();
localStream2 = createCanvasStream();
localStream3 = createCanvasStream();
senderStreams =
offerOptionNum === 1 ? [localStream1] : [localStream1, localStream2];
receiverStreams =
offerOptionNum === 1 ? [localStream2, localStream3] : [localStream3];
document.getElementById("senderTotalLocalTracks").innerText =
senderStreams.length;
document.getElementById("receiverTotalLocalTracks").innerText =
receiverStreams.length;
sender = new RTCPeerConnection();
sender.onicecandidate = (e) => onIceCandidate(sender, e);
receiver = new RTCPeerConnection();
receiver.onicecandidate = (e) => onIceCandidate(receiver, e);
sender.onconnectionstatechange = () => onConnectionStateChange(sender);
receiver.onconnectionstatechange = () => onConnectionStateChange(receiver);
sender.onnegotiationneeded = () => onNegotiationNeeded(sender);
receiver.onnegotiationneeded = () => onNegotiationNeeded(receiver);
sender.onsignalingstatechange = () => onSignalingStateChange(sender);
receiver.onsignalingstatechange = () => onSignalingStateChange(receiver);
sender.ontrack = (e) => {
console.log(`${getName(sender)} received track id:`, e.track.id);
const el = document.getElementById("senderTotalRemoteTracks");
el.innerText = Number(el.innerText) + 1;
};
receiver.ontrack = (e) => {
console.log(`${getName(receiver)} received track id:`, e.track.id);
const el = document.getElementById("receiverTotalRemoteTracks");
el.innerText = Number(el.innerText) + 1;
};
senderStreams.forEach((stream) => {
stream.getVideoTracks().forEach((track) => sender.addTrack(track, stream));
});
}
function createCanvasStream() {
const canvas = Object.assign(
document.createElement("canvas", {
width: 640,
height: 480,
})
);
const ctx = canvas.getContext("2d");
const stream = canvas.captureStream(1);
const drawInCanvas = () => ctx.fillRect(0, 0, canvas.width, canvas.height);
drawInCanvas();
setInterval(() => {
drawInCanvas();
}, 1000);
return stream;
}
async function onIceCandidate(pc, event) {
if (event.candidate) {
try {
await getOtherPc(pc).addIceCandidate(event.candidate);
} catch (error) {
console.error(error, event.candidate);
}
}
}
function onConnectionStateChange(pc) {
if (pc) {
console.log(`${getName(pc)} Connection state: ${pc.connectionState}`);
}
}
async function onNegotiationNeeded(pc) {
console.log(`${getName(pc)} negotiationneeded event`);
await pc.setLocalDescription(await pc.createOffer());
}
async function onSignalingStateChange(pc) {
console.log(`${getName(pc)} Signaling state: ${pc.signalingState}`);
const otherPc = getOtherPc(pc);
if (pc.signalingState === "have-local-offer") {
await otherPc.setRemoteDescription(pc.localDescription);
} else if (pc.signalingState === "have-remote-offer") {
if (pc === receiver) {
receiverStreams.forEach((stream) => {
stream.getVideoTracks().forEach((track) => pc.addTrack(track, stream));
});
}
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
await otherPc.setRemoteDescription(answer);
}
}
function getName(pc) {
return pc === sender ? "Sender" : "Receiver";
}
function getOtherPc(pc) {
return pc === sender ? receiver : sender;
}
html,
body {
margin: 0;
font-family: system-ui, sans-serif;
color: #222;
background: #f8f8f8;
}
input,
textarea {
font-size: 1em;
box-sizing: border-box;
padding: 6px 8px;
}
button,
code,
kbd,
pre {
font-size: 1em;
}
code,
kbd,
pre {
font-family: "Menlo", "Monaco", monospace;
border-radius: 3px;
box-sizing: border-box;
padding: 2px 4px 1px 4px;
background: rgba(0, 0, 0, 0.1);
}
pre {
padding: 8px 12px;
}
p {
line-height: 1.5em;
}
a {
color: #222;
}
a:hover {
color: #666;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="reset.css" />
<link rel="stylesheet" href="https://unpkg.com/blocks.css/dist/blocks.min.css" />
<style>
.cards {
display: flex;
}
.buttons {
display: flex;
}
</style>
</head>
<body>
<div class="cards">
<div class="card fixed block sender">
<h2>sender</h2>
<p>Remote tracks: <span id="senderTotalRemoteTracks">0</span></p>
<p>Local tracks: <span id="senderTotalLocalTracks">0</span></p>
</div>
<div class="card fixed block receiver">
<h2>receiver</h2>
<p>Remote tracks: <span id="receiverTotalRemoteTracks">0</span></p>
<p>Local tracks: <span id="receiverTotalLocalTracks">0</span></p>
</div>
</div>
<div class="buttons">
<button class="block accent" id="btnOffer1">Offer 1 track / Receive 2 tracks</button>
<button class="block" id="btnOffer2">Offer 2 tracks / Receive 1 track</button>
</div>
<button class="block" id="btnTryAgain">Try again</button>
<script src="main.js" async></script>
</body>
</html>
关于javascript - WebRTC 无法在同一连接中回答具有多个轨道的报价,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66249659/
F#中的“引号”是什么,它们的用途是什么? 最佳答案 In short, a quotation is metadata that represents the code of a particula
有人可以解释一下,为什么我可以使用 $1两次得到不同的结果? perl -wle '"ok" =~ /(.*)/; sub { "huh?" =~ /(.*)/; print for @_ }->(
我正在尝试向迷你购物车 block 添加一个 block (按钮):要么到 name="cart_sidebar"或者最好是 name="topCart.extra_actions"因为它自动呈现它的
我尝试做一些事情并尝试弄清楚。我只想向单击的 div 添加一个类。如果单击 div 号 1,然后单击 div 号 2,则该类将从 div1 中删除并添加到 div2 中。希望您能理解。 这是一个cod
我正在创建一个执行以下操作的 magento 支付扩展程序: 当用户在商家网站上点击结账时,他会被重定向到一个网站(如 paypal),并在该网站上输入他的支付数据。如果付款方式失败,用户将被重定向到
我正在开发一个自定义忠诚度积分模块。在结账期间,客户可以选择兑换积分。 在模块设置中,我创建了一个redeem_points eav_attribute(它存在于eav_attribute 表中),并
我目前正在做一个小项目,我需要对以下场景进行建模: 场景 客户打来电话,他想要一辆新车的报价。 销售代表。注册客户信息。 销售代表。在系统中创建报价,并将项目添加到报价(汽车)。 销售代表。通过电子邮
在 Elixir 中,什么时候应该使用 Macro.escape/1 而不是 quote/1?我看过beginner's guide但这没有帮助。 最佳答案 quote/2返回 abstract sy
我不明白什么是报价单。有人可以详细回答什么是 magento 报价、它们存储的数据、它们的生命周期和其他相关信息吗? 编辑:请注意区别:Magento vs Magneto。还有“magento”和“
我有一个 Terraform 脚本,它基于 Azure 市场中的此镜像在 Azure 上创建虚拟机: https://azuremarketplace.microsoft.com/en-us/mark
我正在尝试使用 JavaScript 重新创建(或“伪造”)Skype 报价。因此,我需要将 XML 字符串推送到剪贴板,格式为“SkypeMessageFragment”。警报显示效果很好,但使用
我的数据框 (ds) 中的每日数据看起来像这样跨越了几年: 对于每一天,我都需要将所有报价标准化为该特定日期的特定时间。例如,在 6 月 1 日,我需要将所有报价标准化为 6 月 1 日下午 3 点的
有人知道如何在 PL/SQL 存储过程中生成 .Net DateTime.Ticks,而无需在 Oracle 数据库中使用 .Net 程序集吗? 我在 .Net 中有一项服务,将 DateTime.T
很难说出这里要问什么。这个问题模棱两可、含糊不清、不完整、过于宽泛或夸夸其谈,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开,visit the help center . 关闭 1
我在 Steam API 中搜索发送 Steam 报价的方法,但找不到。可以使用 API 或者您知道其他方法吗?我想在 PHP/Symfony 中使用它。 最佳答案 您目前无法通过 API 创建交易报
我是一名初学者 Scheme 程序员,我想丰富我在函数式编程方面的知识。我在 DrRacket IDE 中编程。最近我发现了一些有趣的代码: (car ''(a b)) 输出: 'quote
按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visit the
我有以下结构化数据: { "@context": "https://schema.org/", "@type": "Offer", "priceCurrency": "EUR", "p
我是一名优秀的程序员,十分优秀!