So Iam setting up a conferencing Platform with mediasoup. This is my Client Side. Everything is working fine with Firefox but when i Client joins which uses Chrome, i can only See Video but i dont hear any audio.
What i did:
- checked for audiocontext to be running
I dont know what the Problem is, maybe the Codecs or something.
My Codecs defined in my Mediaserver:
router = await worker.createRouter({
mediaCodecs: [
{
kind: ‘audio’,
mimeType: ‘audio/opus’,
clockRate: 48000,
channels: 2
},
{
kind: ‘video’,
mimeType: ‘video/VP8’,
clockRate: 90000,
parameters: {
‘x-google-start-bitrate’: 1000
}
}
]
});
My Clientside Code:
const mediasoupClient = require(‘mediasoup-client’);
const io = require(‘socket.io-client’);
const SIGNALING_SERVER_IP = ‘MY_IP’;
const socket = io(https://${SIGNALING_SERVER_IP}:3000
);
let localStream = null;
let roomUUID;
let device;
let producerTransport;
let videoProducer = null;
let audioProducer = null;
let consumerTransport = null;
let consumers = ;
let isProducing = false; // To prevent multiple calls to startProducing
let label2name = new Map();
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
const audioDestination = audioContext.createMediaStreamDestination();
let expectedAudioStreams = 0;
let receivedAudioStreams = 0;
let audioStreams = ;
// Create a single audio element to play the mixed audio
const audioElement = new Audio();
audioElement.srcObject = audioDestination.stream;
audioElement.autoplay = true;
document.body.appendChild(audioElement);
async function getLocalStream() {
try {
localStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
console.log(‘Local stream obtained:’, localStream);
addVideoStream(localStream, true, ‘local’);
await createDevice();
await createProducerTransport();
} catch (error) {
console.error(‘Error accessing media devices.’, error);
}
}
function addVideoStream(stream, muted, label) {
console.log(Adding video stream (${label}):
, stream);
// Check if the stream is already added
const existingVideo = document.querySelector(`video[data-label="${label}"]`);
if (existingVideo) {
console.warn(`Video stream (${label}) already added, skipping.`);
return;
}
const videoElement = document.createElement('video');
videoElement.srcObject = stream;
videoElement.muted = muted;
videoElement.autoplay = true;
videoElement.controls = true;
videoElement.setAttribute('data-label', label); // Set a label to identify the video stream
videoElement.style.objectFit = 'contain'; // Ensure video fits within the container
videoElement.style.objectPosition = 'center'; // Center the video within the container
document.getElementById('videoContainer').appendChild(videoElement);
document.getElementById('joinForm').style.display = 'none';
document.getElementById('videoContainer').style.display = 'flex';
videoElement.volume = document.getElementById('videoContainer').volume || 1.0;
adjustVolume();
}
function addAudioStream(stream, label) {
console.log(Adding audio stream (${label}):
, stream);
const audioSource = audioContext.createMediaStreamSource(stream);
audioSource.connect(audioDestination);
audioStreams.push(audioSource);
// Increment the count of received audio streams
receivedAudioStreams++;
console.log(`Received ${receivedAudioStreams}/${expectedAudioStreams} audio streams`);
// Only play the mixed audio once all audio streams are received
if (receivedAudioStreams === expectedAudioStreams) {
audioElement.play();
}
}
function adjustVolume() {
const volume = document.getElementById(‘videoContainer’).volume || 1.0;
document.querySelectorAll(‘video’).forEach(video => {
video.volume = volume;
});
}
async function createDevice() {
device = new mediasoupClient.Device();
const routerRtpCapabilities = await new Promise((resolve) => {
socket.emit(‘getRouterRtpCapabilities’, resolve);
});
await device.load({ routerRtpCapabilities });
console.log(‘Device created and loaded’);
}
async function createProducerTransport() {
console.log(“Creating Producer Transport”);
socket.emit(‘createProducerTransport’, { roomUUID }, async (transportOptions) => {
if (transportOptions.error) {
console.error(‘Error from server creating producer transport:’, transportOptions.error);
return;
}
const { id, iceParameters, iceCandidates, dtlsParameters } = transportOptions;
console.log('Received transport options:', transportOptions);
producerTransport = device.createSendTransport({
id,
iceParameters,
iceCandidates,
dtlsParameters
});
console.log('Producer transport created:', producerTransport);
producerTransport.on('connect', ({ dtlsParameters }, callback, errback) => {
console.log('Producer Transport Connect event triggered with dtlsParameters:', dtlsParameters);
socket.emit('connectProducerTransport', { dtlsParameters }, (response) => {
if (response.error) {
console.error('connectProducerTransport response error:', response.error);
errback(response.error);
} else {
console.log('connectProducerTransport response success');
callback();
}
});
});
producerTransport.on('produce', async (parameters, callback, errback) => {
console.log('Start Producing with parameters:', parameters);
socket.emit('produce', parameters, ({ id }) => {
if (id) {
console.log('Produced track with id:', id);
callback({ id });
} else {
errback('Produce failed');
}
});
});
producerTransport.on('connectionstatechange', (state) => {
console.log('Producer transport connection state changed:', state);
console.log(producerTransport);
if (state === 'failed') {
console.error('Producer transport entered failed state');
producerTransport.close();
}
});
if (!isProducing) {
await startProducing();
isProducing = true;
}
});
}
async function startProducing() {
try {
if (localStream.getVideoTracks().length > 0) {
const videoTrack = localStream.getVideoTracks()[0];
console.log(‘Producing video track:’, videoTrack);
videoProducer = await producerTransport.produce({ track: videoTrack });
console.log(‘Video producer created:’, videoProducer.id);
} else {
console.warn(‘No video track available’);
}
if (localStream.getAudioTracks().length > 0) {
const audioTrack = localStream.getAudioTracks()[0];
console.log('Producing audio track:', audioTrack);
audioProducer = await producerTransport.produce({ track: audioTrack });
console.log('Audio producer created:', audioProducer.id);
} else {
console.warn('No audio track available');
}
if (videoProducer) {
videoProducer.on('transportclose', () => {
console.log('Video transport closed');
});
videoProducer.on('trackended', () => {
console.log('Video track ended');
});
}
if (audioProducer) {
audioProducer.on('transportclose', () => {
console.log('Audio transport closed');
});
audioProducer.on('trackended', () => {
console.log('Audio track ended');
});
}
} catch (error) {
console.error('Error producing tracks:', error);
}
}
async function createConsumerTransport() {
console.log(Creating Consumer Transport
);
return new Promise((resolve, reject) => {
socket.emit(‘createConsumerTransport’, { roomUUID }, async (transportOptions) => {
if (transportOptions.error) {
console.error(‘Error from server creating consumer transport:’, transportOptions.error);
reject(transportOptions.error);
return;
}
const { id, iceParameters, iceCandidates, dtlsParameters } = transportOptions;
consumerTransport = device.createRecvTransport({
id,
iceParameters,
iceCandidates,
dtlsParameters
});
console.log('Consumer transport created:', consumerTransport);
let isConnected = false;
consumerTransport.on('connect', ({ dtlsParameters }, callback, errback) => {
if (isConnected) {
console.warn('Consumer transport already connected');
return;
}
console.log('Consumer Transport Connect event triggered with dtlsParameters:', dtlsParameters);
socket.emit('connectConsumerTransport', { dtlsParameters }, (response) => {
if (response.error) {
errback(response.error);
} else {
console.log('connectConsumerTransport response success');
isConnected = true;
callback();
}
});
});
consumerTransport.on('connectionstatechange', (state) => {
console.log('Consumer transport state:', state);
if (state === 'failed') {
consumerTransport.close();
}
});
resolve(consumerTransport);
});
});
}
async function consume(producerId) {
socket.emit('consume', { producerId, rtpCapabilities: device.rtpCapabilities }, async (response) => {
const { id, kind, rtpParameters } = response;
const consumer = await consumerTransport.consume({ id, producerId, kind, rtpParameters });
console.log('Consumed track:', consumer.track);
consumers.push(consumer);
const stream = new MediaStream();
stream.addTrack(consumer.track);
if (kind === 'video') {
addVideoStream(stream, false, `producer-${producerId}`);
} else if (kind === 'audio') {
addAudioStream(stream, `producer-${producerId}`);
}
});
}
socket.on(‘newProducer’, async ({ producerId, kind, name}) => {
console.log(“New producer detected:”, producerId, kind);
if (kind == “audio”) expectedAudioStreams++
if (kind == “video”) label2name.set(name, producerId);
await consume(producerId);
});
socket.on(‘joined-room’, async ({ roomUUID, name, totalClients, existingProducers, participants }) => {
console.log(User ${name} joined the room: ${roomUUID} with Clients: ${totalClients}
);
expectedAudioStreams = totalClients - 1;
console.log(Expecting ${expectedAudioStreams} audio streams
);
// Display the participants list
updateParticipantsList(participants);
// Start Producing
await getLocalStream();
// Start Consuming
consumerTransport = await createConsumerTransport();
// Consume existing producers if available
for (const producerId of existingProducers) {
await consume(producerId, 'video'); // Assuming you know the kind, adjust as necessary
await consume(producerId, 'audio'); // Assuming you know the kind, adjust as necessary
}
});
socket.on(‘new-participant’, ({ name }) => {
addParticipant(name);
});
socket.on(‘user-left’, ({ name }) => {
removeParticipant(name);
removeUserVideo(name);
});
document.addEventListener(‘DOMContentLoaded’, function() {
console.log(‘DOM fully loaded and parsed’);
const urlParams = new URLSearchParams(window.location.search);
roomUUID = urlParams.get(‘room’);
console.log(‘Room UUID:’, roomUUID);
const joinButton = document.getElementById('joinButton');
if (joinButton) {
joinButton.addEventListener('click', joinMeeting);
console.log('Join button found and event listener added');
} else {
console.error('Join button not found');
}
const leaveButtonContainer = document.getElementById('leaveButtonContainer');
if (leaveButtonContainer) {
leaveButtonContainer.addEventListener('click', leaveMeeting);
console.log('Leave button container found and event listener added');
} else {
console.error('Leave button container not found');
}
// Set an initial volume and add an event listener to adjust volume
const videoContainer = document.getElementById('videoContainer');
videoContainer.volume = 1.0;
videoContainer.addEventListener('volumechange', adjustVolume);
// Ask the server if the room still exists
socket.emit('ask-for-room', roomUUID, (response) => {
console.log(response)
if (response.exists) {
// Check if there is a saved session in localStorage
const savedRoomUUID = localStorage.getItem('roomUUID');
const savedName = localStorage.getItem('name');
const savedMeetingCode = localStorage.getItem('meetingCode');
if (savedRoomUUID && savedName && savedMeetingCode) {
roomUUID = savedRoomUUID;
document.getElementById('nameInput').value = savedName;
document.getElementById('meetingCodeInput').value = savedMeetingCode;
joinMeeting();
}
} else {
console.error('Room does not exist');
// Optionally, clear local storage if the room no longer exists
localStorage.removeItem('roomUUID');
localStorage.removeItem('name');
localStorage.removeItem('meetingCode');
alert(`Room doesnt exist anymore, you got redirected to the Homepage'}`);
window.location.href = '/';
}
});
});
async function joinMeeting() {
console.log(‘Join button clicked’);
const name = document.getElementById(‘nameInput’).value.trim();
const meetingCode = document.getElementById(‘meetingCodeInput’).value.trim();
if (!name || !meetingCode || name.trim() === ‘’) {
alert(‘Name and meeting code are required.’);
return;
}
console.log(‘Name:’, name, ‘Meeting Code:’, meetingCode);
// Save session details to localStorage
localStorage.setItem('roomUUID', roomUUID);
localStorage.setItem('name', name);
localStorage.setItem('meetingCode', meetingCode);
socket.emit('join', { roomUUID, name, meetingCode }, (response) => {
console.log(response)
if (response.success) {
console.log('Successfully joined the meeting:', response);
const joinForm = document.getElementById('joinForm');
const videoContainer = document.getElementById('videoContainer');
const leaveButtonContainer = document.getElementById('leaveButtonContainer');
// Hide join form smoothly
joinForm.style.opacity = 0;
joinForm.style.transform = 'scale(0.9)';
setTimeout(() => {
joinForm.style.display = 'none';
// Show video container and leave button smoothly
meetingContainer.style.display = 'flex';
leaveButtonContainer.style.display = 'block';
setTimeout(() => {
videoContainer.style.opacity = 1;
videoContainer.style.transform = 'scale(1)';
leaveButtonContainer.style.opacity = 1;
leaveButtonContainer.style.transform = 'scale(1)';
}, 50);
}, 500);
} else {
alert(`Failed to join the meeting: ${response ? response.message : 'Unknown error'}`);
}
});
}
async function leaveMeeting() {
console.log(‘Leave button clicked’);
socket.emit(‘leave’, { roomUUID }, (response) => {
if (response.success) {
console.log(‘Successfully left the meeting:’, response);
const videoContainer = document.getElementById(‘videoContainer’);
const leaveButtonContainer = document.getElementById(‘leaveButtonContainer’);
// Hide video container and leave button smoothly
videoContainer.style.opacity = 0;
videoContainer.style.transform = 'scale(0.9)';
leaveButtonContainer.style.opacity = 0;
leaveButtonContainer.style.transform = 'scale(0.9)';
setTimeout(() => {
videoContainer.style.display = 'none';
leaveButtonContainer.style.display = 'none';
// Clear session details from localStorage
document.getElementById('joinForm').style.display = 'block';
document.getElementById('videoContainer').style.display = 'none';
document.getElementById('leaveButtonContainer').style.display = 'none';
clearLocalStorage();
stopLocalStream();
stopAllTransportsAndProducers();
// Redirect to homepage
window.location.href = '/';
}, 500);
} else {
alert(`Failed to leave the meeting: ${response ? response.message : 'Unknown error'}`);
window.location.href = '/';
}
});
}
function clearLocalStorage() {
localStorage.removeItem(‘roomUUID’);
localStorage.removeItem(‘name’);
localStorage.removeItem(‘meetingCode’);
}
function stopLocalStream() {
if (localStream) {
localStream.getTracks().forEach(track => track.stop());
localStream = null;
}
}
function stopAllTransportsAndProducers() {
if (videoProducer) {
videoProducer.close();
videoProducer = null;
}
if (audioProducer) {
audioProducer.close();
audioProducer = null;
}
if (producerTransport) {
producerTransport.close();
producerTransport = null;
}
if (consumerTransport) {
consumerTransport.close();
consumerTransport = null;
}
consumers.forEach(consumer => {
consumer.close();
});
consumers = ;
}
function updateParticipantsList(participants) {
const participantsList = document.getElementById(‘participantsList’);
participantsList.innerHTML = ‘’; // Clear the list
participants.forEach(name => {
const listItem = document.createElement(‘li’);
listItem.textContent = name;
participantsList.appendChild(listItem);
});
}
function addParticipant(name) {
const participantsList = document.getElementById(‘participantsList’);
const listItem = document.createElement(‘li’);
listItem.textContent = name;
participantsList.appendChild(listItem);
}
function removeParticipant(name) {
console.log(name)
const participantsList = document.getElementById(‘participantsList’);
const listItems = participantsList.getElementsByTagName(‘li’);
console.log(listItems)
for (let i = 0; i < listItems.length; i++) {
if (listItems[i].textContent.trim() === name.trim()) {
participantsList.removeChild(listItems[i]);
break;
}
}
}
function removeUserVideo(name) {
const producerId = label2name.get(name);
console.log(“ProducerID”,producerId)
if (!producerId) {
console.warn(No producer ID found for participant (${name}).
);
return;
}
const videoElement = document.querySelector(video[data-label="producer-${producerId}"]
);
if (videoElement) {
videoElement.parentNode.removeChild(videoElement);
console.log(`Removed video stream for ${name} (${producerId}).`);
} else {
console.warn(`Video stream for ${name} (${producerId}) not found.`);
}
// Remove the mapping
label2name.delete(producerId);
}