gpt4 book ai didi

javascript - WebRTC 无法在同一连接中回答具有多个轨道的报价

转载 作者:行者123 更新时间:2023-12-05 06:51:08 27 4
gpt4 key购买 nike

我正在尝试在同一连接中与两个视频轨道建立 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/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com