Проблема с заиканием воспроизведения звука в веб-приложении

Я разрабатываю веб-приложение, которое передает аудио от администратора нескольким пользователям в режиме реального времени с использованием WebRTC и Socket.IO. Однако я столкнулся с проблемой, при которой воспроизведение звука на стороне пользователя прерывается или периодически останавливается. Вот краткий обзор установки и проблемы:

Настраивать: Администратор записывает звук с микрофона с помощью getUserMedia и MediaRecorder. Аудио кодируется из WAV в MP3 с помощью lamejs и передается пользователям через Socket.IO. Пользователи получают аудиофрагменты и пытаются воспроизвести их с помощью элемента.

Проблема:

Хотя воспроизведение звука начинается успешно, оно часто прерывается или останавливается через короткое время.

Код со стороны администратора:

<!DOCTYPE html>
<html lang = "en">
<head>
    <meta charset = "UTF-8">
    <meta name = "viewport" content = "width=device-width, initial-scale=1.0">
    <title>Admin Broadcasting</title>
    <script src = "https://cdnjs.cloudflare.com/ajax/libs/lamejs/1.2.0/lame.min.js"></script>
</head>
<body>
    <h1>Admin Broadcasting</h1>
    <button id = "startBroadcastBtn">Start Broadcasting</button>
    
    <script src = "/socket.io/socket.io.js"></script>
    <script>
        const startBroadcastBtn = document.getElementById('startBroadcastBtn');
        const socket = io('ws://localhost:9000');
        let mediaRecorder;

        startBroadcastBtn.addEventListener('click', async () => {
            try {
                const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
                if (!stream) {
                    console.error('getUserMedia failed to return a valid stream');
                    return;
                }

                // Initialize MediaRecorder with WAV format
                mediaRecorder = new MediaRecorder(stream);

                // Create WAV to MP3 converter using lamejs
                const mp3Encoder = new lamejs.Mp3Encoder(1, 44100, 128);

                // Add event listener for data availability
                mediaRecorder.ondataavailable = async (event) => {
                    try {
                        if (event.data.size > 0) {
                            console.info('Audio chunk captured:', event.data);

                            // Convert WAV audio data to MP3
                            const reader = new FileReader();
                            reader.onload = async () => {
                                const arrayBuffer = reader.result;
                                const buffer = new Int8Array(arrayBuffer);
                                const length = buffer.length;
                                const pcm = new Int16Array(length / 2);
                                
                                // Convert Int8Array to Int16Array (PCM data)
                                for (let i = 0; i < length; i += 2) {
                                    pcm[i / 2] = buffer[i] | (buffer[i + 1] << 8);
                                }

                                const mp3Data = mp3Encoder.encodeBuffer(pcm);
                                const mp3Blob = new Blob([mp3Data], { type: 'audio/mp3' });
                                console.info('Created MP3 blob:', mp3Blob);
                                socket.emit('admin-audio-chunk', mp3Blob);
                            };
                            reader.readAsArrayBuffer(event.data);
                        }
                    } catch (error) {
                        console.error('Error processing audio data:', error);
                    }
                };

                // Start recording
                console.info('Starting recording...');
                mediaRecorder.start(1000);
                console.info('Recording started.');
            } catch (error) {
                console.error('Error accessing media devices:', error);
            }
        });
    </script>
</body>
</html>

код на стороне пользователя:

<!DOCTYPE html>
<html lang = "en">
<head>
    <meta charset = "UTF-8">
    <meta name = "viewport" content = "width=device-width, initial-scale=1.0">
    <title>User Listening</title>
</head>
<body>
    <h1>User Listening</h1>
    <audio id = "audioPlayer" controls preload = "auto"></audio>

    <script src = "/socket.io/socket.io.js"></script>
    <script>
        const audioPlayer = document.getElementById('audioPlayer');
        const socket = io('ws://localhost:9000');
        let audioChunks = [];

        socket.on('user-audio-chunk', (data) => {
            console.info('Received audio chunk from admin:', data);
            audioChunks.push(data);
            playAudioChunks();
        });

        function playAudioChunks() {
            if (audioChunks.length > 0 && audioPlayer.paused) {
                const audioBlob = new Blob(audioChunks, { type: 'audio/mp3' });
                audioPlayer.src = URL.createObjectURL(audioBlob);
                audioPlayer.play().catch((error) => {
                    console.error('Error playing audio:', error);
                });
                audioChunks = [];
            }
        }
    </script>
</body>
</html>

Как плавно воспроизвести звук и решить проблему?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
62
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Настройка: администратор записывает звук с микрофона с помощью getUserMedia и MediaRecorder. Аудио кодируется из WAV в MP3 с помощью lamejs и передается пользователям через Socket.IO. Пользователи получают аудиофрагменты и пытаются воспроизвести их с помощью элемента.

Вы кодируете фрагменты и воспроизводите фрагменты... вот почему у вас прерывистое звучание звука. Если вам нужен поток аудио, вам нужно относиться к нему как к потоку.

Не создавайте экземпляр нового кодека каждый раз. Я не знаю о вашей сборке LAME, но обычный LAME будет использовать битовый резервуар, который будет переносить данные из одного кадра в другой. Итак, вам нужно настроить один экземпляр и продолжать передавать на него данные PCM.

Теперь, что касается воспроизведения, вообще не используйте веб-сокеты и все такое. Это проще. Используйте простой элемент <audio> и передавайте данные через http:

<audio src = "https://stream.example.com/something"></audio>

Ваш сервер должен брать полученные фрагменты и немедленно записывать их клиенту. Если вместо этого вы попытаетесь декодировать каждый сегмент отдельно, вы просто получите декодированные фрагменты, и они не будут волшебным образом просто воспроизводиться во времени и выстраиваться в линию.

Другие вопросы по теме