WEBRTC показывает дополнительный поток только с одним контактным соединениемJavascript

Форум по Javascript
Ответить Пред. темаСлед. тема
Anonymous
 WEBRTC показывает дополнительный поток только с одним контактным соединением

Сообщение Anonymous »

Я пытаюсь разрешить моему приложению для встреч с видео -вызовом WEBRTC иметь несколько подключений к одноранговому однорангу и привести к динамически появлению потока в удаленном видео -вызове с использованием JavaScript. У меня есть только две вкладки в двух окнах, открытых в URL -адресах видео -тестирования. Одним из них является инкогнито, а одним из них является обычным браузером Google Chrome, но оба являются браузерами Google Chrome. Они оба вошли в суперпользователи. Проблема в том, что удаленные видео отображаются дважды одного и того же пользователя удаленного пользователя.




button {
border: 0;
background-color: orange;
padding: 10px;
color: white;
border-radius: 7px;
}

video {
border-radius: 15px;
}

.videoContainer {
display: flex;
margin: 20px;
width: 640px;
}

.videoContainer h2 {
color: white;
position: relative;
bottom: -380px;
left: -350px;
width: max-content;
}

#meet {
display: flex;
}

#recordButton.recording {
background-color: red;
}

#downloadButton {
background-color: #4caf50;
}

button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}

A Free Bird Video Call








{{ request.user.full_name }}





Start Call

Start Recording


Download Recording



// Global variables
let peerConnection = null;
let localStream = null;
let isInitiator = false;
let iceCandidatesQueue = [];
let mediaRecorder = null;
let recordedChunks = [];
let isRecording = false;
const roomName = "{{ room_name }}";
const signalingChannel = new WebSocket(
`wss://${window.location.host}/ws/webrtc/${roomName}/`
);

setupMediaStream();

signalingChannel.onopen = async () => {
console.log("WebSocket connected!");
signalingChannel.send(
JSON.stringify({
type: "join",
room: roomName,
username: "{{ request.user.full_name }}",
})
);
};

async function setupMediaStream() {
try {
localStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true,
});
document.getElementById("localVideo").srcObject = localStream;
console.log("Local stream setup successful");
globalThis.localStream = localStream;
} catch (err) {
console.error("Error accessing media devices:", err);
throw err;
}
}

async function initializePeerConnection(localStream) {
const servers = {'iceServers': [{'urls': 'stun:stun.l.google.com:19302'}]}

peerConnection = new RTCPeerConnection(servers);
console.log("Created peer connection");

peerConnection.addStream(globalThis.localStream);
console.log("✅ Track added to peer connection");

peerConnection.ontrack = (event) => {
const userId = "{{ request.user.id }}"
console.log("Received remote track");
const remoteVideo = document.getElementById("remote-videos");
const stream = event.streams[0];
const container = document.createElement("div");
container.classList.add("videoContainer");
container.id = `remote-video-${userId}`;
const videoElement = document.createElement('video');
videoElement.srcObject = stream;
videoElement.play();

remoteVideo.appendChild(container);
container.appendChild(videoElement);
container.appendChild(document.createElement("h2"));
setupRecording();

};

// ICE candidate handling

peerConnection.onsignalingstatechange = (event) => {
console.log("Signaling state change:", peerConnection.signalingState);
};

peerConnection.onconnectionstatechange = (event) => {
console.log("Connection state change:", peerConnection.connectionState);
};

return peerConnection;
}

async function setupAndStart() {
try {
if (!peerConnection) {
await initializePeerConnection();
const remoteStreams = peerConnection.getRemoteStreams();
console.log(remoteStreams);

}
} catch (err) {
console.error("Error in setupAndStart:", err);
}
}

async function handleCandidate(message) {
try {
if (!peerConnection || !peerConnection.remoteDescription) {
console.log("Queueing ICE candidate");
iceCandidatesQueue.push(message.candidate);

}

if (message.candidate) {
await peerConnection.addIceCandidate(
new RTCIceCandidate(message.candidate)
);
console.log("Added ICE candidate");
}
} catch (err) {
console.error("Error adding ICE candidate:", err);
}
}

async function handleOffer(message) {
try {
if (!peerConnection) {
await setupAndStart();
}

await peerConnection.setRemoteDescription(
new RTCSessionDescription({
type: "offer",
sdp: message.sdp,
})
);

peerConnection.onicecandidate = (event) => {
if (event.candidate) {
console.log("Sending ICE candidate");
signalingChannel.send(
JSON.stringify({
type: "ice-candidate",
candidate: event.candidate,
username: "{{ request.user.full_name }}",
})
);
console.log("ICE candidate sent");
}
};

// Process queued candidates
while (iceCandidatesQueue.length > 0) {
const candidate = iceCandidatesQueue.shift();
await peerConnection.addIceCandidate(
new RTCIceCandidate(candidate)
);
console.log("Added queued ICE candidate");
}

const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer);

signalingChannel.send(
JSON.stringify({
type: "answer",
sdp: answer.sdp,
username: "{{ request.user.full_name }}",
})
);
} catch (err) {
console.error("Error handling offer:", err);
}
}

async function handleAnswer(message) {
try {
await peerConnection.setRemoteDescription(
new RTCSessionDescription({
type: "answer",
sdp: message.sdp,
})
);

// Process queued candidates
while (iceCandidatesQueue.length > 0) {
const candidate = iceCandidatesQueue.shift();
await peerConnection.addIceCandidate(
new RTCIceCandidate(candidate)
);
console.log("Added queued ICE candidate");
}
} catch (err) {
console.error("Error handling answer:", err);
}
}

async function startCall() {
try {
iceCandidatesQueue = [];
await setupAndStart();

const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);

signalingChannel.send(
JSON.stringify({
type: "offer",
sdp: offer.sdp,
username: "{{ request.user.full_name }}",
})
);
} catch (err) {
console.error("Error starting call:", err);
}
}

function updateRemoteUsername(message) {
const userId = "{{ request.user.id }}"
const remoteVideoContainer = document.getElementById(`remote-video-${userId}`);
if (remoteVideoContainer) {
const remoteNameElement = remoteVideoContainer.querySelector("h2");
if (remoteNameElement) {
remoteNameElement.textContent = message;
}
}
}

signalingChannel.onmessage = async (event) => {
const message = JSON.parse(event.data);
console.log("Received message:", message);

try {
switch (message.type) {
case "join":
if (!message.self) {
isInitiator = true;
updateRemoteUsername(message.username);
await setupAndStart();
}
break;
case "offer":

updateRemoteUsername(message.username);
await handleOffer(message);

break;
case "answer":
await handleAnswer(message);
break;
case "ice-candidate":
await handleCandidate(message);
break;
case "candidate":
await handleCandidate(message);
break;
default:
console.log("Unknown message type:", message.type);
}
} catch (err) {
console.error("Error handling message:", err);
}
};

async function setupRecording(stream) {
try {
// Create a canvas element to combine the videos
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");

// Get video elements
const localVideo = document.getElementById("localVideo");
const remoteVideo = document.getElementById("remoteVideo");

// Wait for both streams to be available
if (!localVideo.srcObject || !remoteVideo.srcObject) {
console.log(
"Waiting for both streams before setting up recording..."
);
return;
}

// Set canvas size to match the video container width and height
const videoContainer = document.querySelector(".videoContainer");
const containerStyle = window.getComputedStyle(videoContainer);
canvas.width = videoContainer.offsetWidth * 2; // Width of two videos
canvas.height = videoContainer.offsetHeight;

// Create a stream from the canvas
const canvasStream = canvas.captureStream(30); // 30 FPS

// Add audio tracks from both streams
const localAudioTrack = localVideo.srcObject.getAudioTracks()[0];
const remoteAudioTrack = remoteVideo.srcObject.getAudioTracks()[0];
if (localAudioTrack) canvasStream.addTrack(localAudioTrack);
if (remoteAudioTrack) canvasStream.addTrack(remoteAudioTrack);

// Create MediaRecorder
mediaRecorder = new MediaRecorder(canvasStream, {
mimeType: "video/webm;codecs=vp8,opus",
});

// Handle data available event
mediaRecorder.ondataavailable = (event) => {
if (event.data && event.data.size > 0) {
recordedChunks.push(event.data);
}
};

// Handle recording stop
mediaRecorder.onstop = () => {
console.log("Recording stopped");
document.getElementById("downloadButton").disabled = false;
};

// Enable record button
document.getElementById("recordButton").disabled = false;

// Draw frames to canvas
function drawFrame() {
// Draw local video
ctx.drawImage(localVideo, 0, 0, canvas.width / 2, canvas.height);
// Draw remote video
ctx.drawImage(
remoteVideo,
canvas.width / 2,
0,
canvas.width / 2,
canvas.height
);

// Add names under videos
ctx.fillStyle = "white";
ctx.font = "20px Arial";
const localName = "{{ request.user.full_name }}";
const remoteName =
remoteVideo.parentElement.querySelector("h2").textContent;

// Add local name
ctx.fillText(localName, 10, canvas.height - 20);
// Add remote name
ctx.fillText(remoteName, canvas.width / 2 + 10, canvas.height - 20);

if (isRecording) {
requestAnimationFrame(drawFrame);
}
}

// Start the recording loop when recording starts
mediaRecorder.onstart = () => {
drawFrame();
};

console.log("Recording setup complete");
} catch (err) {
console.error("Error setting up recording:", err);
}
}

async function toggleRecording() {
const recordButton = document.getElementById('recordButton');

if (!isRecording) {
// Start recording
recordedChunks = [];
try {
if (!mediaRecorder) {
await setupRecording();
}

if (mediaRecorder && mediaRecorder.state === 'inactive') {
mediaRecorder.start(1000); // Record in 1-second chunks
isRecording = true;
recordButton.textContent = 'Stop Recording';
recordButton.classList.add('recording');
console.log("Recording started");
}
} catch (err) {
console.error("Error starting recording:", err);
}
} else {
// Stop recording
if (mediaRecorder && mediaRecorder.state === 'recording') {
mediaRecorder.stop();
isRecording = false;
recordButton.textContent = 'Start Recording';
recordButton.classList.remove('recording');
console.log("Recording stopped");
}
}
}

function downloadRecording() {
try {
if (!recordedChunks.length) {
console.error("No recording data available");
return;
}

const blob = new Blob(recordedChunks, {
type: 'video/webm'
});

const url = URL.createObjectURL(blob);
const a = document.createElement('a');
document.body.appendChild(a);
a.style.display = 'none';
a.href = url;
a.download = `call-recording-${new Date().toISOString()}.webm`;

a.click();

// Cleanup
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
recordedChunks = [];
document.getElementById('downloadButton').disabled = true;
} catch (err) {
console.error("Error downloading recording:", err);
}
}

// Cleanup when the call ends
window.onbeforeunload = () => {
if (localStream) {
localStream.getTracks().forEach((track) => track.stop());
}
if (peerConnection) {
peerConnection.close();
}
if (signalingChannel) {
signalingChannel.close();
}
if (mediaRecorder && mediaRecorder.state === 'recording') {
mediaRecorder.stop();
}
isRecording = false;
};

window.onunload = () => {
if (peerConnection) {
peerConnection.close();
}
};




< /code>
Есть подсказки относительно того, почему есть дополнительное удаленное видео? Кстати, я использую одну и ту же веб -камеру для обоих коллег. Один для местного. 2 для отдаленных сверстников. Должно быть только 2 потока веб -камеры. 1 для местного. 1 для отдаленных сверстников. Это потому, что у меня было только две вкладки, открытые для зала заседания видеозвонок.

Подробнее здесь: https://stackoverflow.com/questions/794 ... connection
Реклама
Ответить Пред. темаСлед. тема

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

  • Похожие темы
    Ответы
    Просмотры
    Последнее сообщение

Вернуться в «Javascript»