Chrome doesnt output Audio

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);

}