我正在尝试在同一连接中与两个视频轨道建立 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 as any time a change to the communication environment requires reconfiguring 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/