Проблема с аудиопотоком в приложении Flask с использованием Socket IO

Вопрос или проблема

Ниже приведен мой код user1_interface.html/user2_interface.html, в котором я могу слышать звук, но проблема в том, что кнопка звука зависит от видео. Если видео включено, то только тогда я могу включить звук.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{{ name | capitalize }}</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.4.1/socket.io.min.js"></script>
    <style>
        .video-button {
            position: absolute;
            top: 20px;
            right: 110px;
            background-color: transparent;
            border: none;
            cursor: pointer;
        }

        .video-button img {
            width: 40px;
            height: 40px;
        }

        .remote-video-style {
            position: fixed;
            bottom: -12px;
            right: 20px;
            width: 180px;
            height: 180px;
            z-index: 1000;
        }

        .mute-button {
            position: absolute;
            top: 20px;
            right: 160px;
            background-color: transparent;
            border: none;
            cursor: pointer;
        }

        .mute-button img {
            width: 35px;
            height: 35px;
        }
    </style>
</head>
<body>
    <h2>{{ name | capitalize }}</h2>

    <video id="remoteVideo" autoplay playsinline></video>
    
    <button id="cameraButton" onclick="toggleCamera()" class="video-button">
        <img id="camera-icon" src="{{ url_for('static', filename="vidoff.png") }}" alt="Camera On"
        data-show="{{ url_for('static', filename="vidon.png") }}"
        data-hide="{{ url_for('static', filename="vidoff.png") }}">
    </button>

    <button id="mute-button" class="mute-button">
        <img id="mute-icon" src="{{ url_for('static', filename="mute.png") }}" alt="Mute" 
        data-show="{{ url_for('static', filename="unmute.png") }}"
        data-hide="{{ url_for('static', filename="mute.png") }}">
    </button>

    <script>
        const socket = io();
        const remoteVideo = document.getElementById("remoteVideo");
        const cameraButton = document.getElementById("cameraButton");
        const cameraIcon = document.getElementById("camera-icon");
        const muteButton = document.getElementById("mute-button");
        const muteIcon = document.getElementById("mute-icon");
        let localStream = null;
        let peerConnection = null;
        let isCameraOn = false;
        let isMuted = true;  // Изначально выключен звук

        async function toggleCamera() {
            if (isCameraOn) {
                stopCamera();
            } else {
                startCamera();
            }
        }

        async function startCamera() {
            try {
                // Доступ к видео и аудио
                localStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
                cameraIcon.src = cameraIcon.getAttribute('data-show');
                isCameraOn = true;

                createPeerConnection();
                localStream.getTracks().forEach(track => peerConnection.addTrack(track, localStream));

                // Изначально выключить звук
                if (localStream.getAudioTracks().length > 0) {
                    localStream.getAudioTracks()[0].enabled = !isMuted;
                }

                // Создать предложение и отправить его другому пользователю
                const offer = await peerConnection.createOffer();
                await peerConnection.setLocalDescription(offer);
                socket.emit('offer', { type: 'offer', sdp: offer.sdp });
            } catch (error) {
                console.error("Ошибка доступа к камере и микрофону:", error);
            }
        }

        function stopCamera() {
            if (localStream) {
                localStream.getTracks().forEach(track => track.stop());
                localStream = null;
            }
            if (peerConnection) {
                peerConnection.close();
                peerConnection = null;
            }

            cameraIcon.src = cameraIcon.getAttribute('data-hide');
            isCameraOn = false;
            remoteVideo.srcObject = null;
            remoteVideo.classList.remove("remote-video-style");
            socket.emit('offer', { type: 'offer', sdp: null });
        }

        function createPeerConnection() {
            peerConnection = new RTCPeerConnection();

            // Обработка входящего удаленного трека
            peerConnection.ontrack = (event) => {
                if (event.streams && event.streams[0]) {
                    remoteVideo.srcObject = event.streams[0];
                    console.log("Получен удаленный поток:", event.streams[0]);
                } else {
                    console.warn("Нет потоков в событии ontrack.");
                }
                remoteVideo.classList.add("remote-video-style");
            };

            // Обработка кандидатов ICE
            peerConnection.onicecandidate = (event) => {
                if (event.candidate) {
                    socket.emit('candidate', { candidate: event.candidate });
                }
            };
        }

        // Функция переключения звука
        muteButton.addEventListener("click", () => {
            if (localStream && localStream.getAudioTracks().length > 0) {
                isMuted = !isMuted;
                muteIcon.src = isMuted ? muteIcon.getAttribute('data-hide') : muteIcon.getAttribute('data-show');
                localStream.getAudioTracks()[0].enabled = !isMuted;
                
                console.log("Звук выключен:", isMuted);
                
                // Уведомить другого пользователя о статусе звука
                socket.emit('audio-mute', { isMuted });
            }
        });

        // Обработчики событий сокетов для сигнализации
        socket.on("offer", async (data) => {
            if (data.sdp) {
                if (!peerConnection) createPeerConnection();
                await peerConnection.setRemoteDescription(new RTCSessionDescription({ type: "offer", sdp: data.sdp }));
                const answer = await peerConnection.createAnswer();
                await peerConnection.setLocalDescription(answer);
                socket.emit("answer", { type: "answer", sdp: answer.sdp });
            } else {
                if (peerConnection) {
                    peerConnection.close();
                    peerConnection = null;
                }
                remoteVideo.srcObject = null;
                remoteVideo.classList.remove("remote-video-style");
            }
        });

        socket.on("answer", async (data) => {
            if (peerConnection) {
                await peerConnection.setRemoteDescription(new RTCSessionDescription({ type: "answer", sdp: data.sdp }));
            }
        });

        socket.on("candidate", async (data) => {
            if (peerConnection && data.candidate) {
                await peerConnection.addIceCandidate(new RTCIceCandidate(data.candidate));
            }
        });

        // Обработка отключения/включения звука для удаленного аудио
        socket.on("audio-mute", (data) => {
            if (remoteVideo.srcObject && remoteVideo.srcObject.getAudioTracks().length > 0) {
                remoteVideo.srcObject.getAudioTracks()[0].enabled = !data.isMuted;
                console.log("Удаленный звук выключен:", data.isMuted);
            }
        });
    </script>
</body>
</html>

Теперь я модифицировал код user1_interface.html/user2_interface.html и сделал звук независимым от видео, но теперь я не могу услышать звук. Ниже приведен фрагмент кода с независимой функцией звука.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{{ name | capitalize }}</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.4.1/socket.io.min.js"></script>
    <style>
        .video-button {
            position: absolute;
            top: 20px;
            right: 110px;
            background-color: transparent;
            border: none;
            cursor: pointer;
        }

        .video-button img {
            width: 40px;
            height: 40px;
        }

        .remote-video-style {
            position: fixed;
            bottom: -12px;
            right: 20px;
            width: 180px;
            height: 180px;
            z-index: 1000;
        }

        .mute-button {
            position: absolute;
            top: 20px;
            right: 160px;
            background-color: transparent;
            border: none;
            cursor: pointer;
        }

        .mute-button img {
            width: 35px;
            height: 35px;
        }
    </style>
</head>
<body>
    <h2>{{ name | capitalize }}</h2>

    <video id="remoteVideo" autoplay playsinline></video>
    
    <button id="cameraButton" onclick="toggleCamera()" class="video-button">
        <img id="camera-icon" src="{{ url_for('static', filename="vidoff.png") }}" alt="Camera On"
        data-show="{{ url_for('static', filename="vidon.png") }}"
        data-hide="{{ url_for('static', filename="vidoff.png") }}">
    </button>

    <button id="mute-button" class="mute-button">
        <img id="mute-icon" src="{{ url_for('static', filename="mute.png") }}" alt="Mute" 
        data-show="{{ url_for('static', filename="unmute.png") }}"
        data-hide="{{ url_for('static', filename="mute.png") }}">
    </button>

    <script>
        const socket = io();
        const remoteVideo = document.getElementById("remoteVideo");
        const cameraButton = document.getElementById("cameraButton");
        const cameraIcon = document.getElementById("camera-icon");
        const muteButton = document.getElementById("mute-button");
        const muteIcon = document.getElementById("mute-icon");
        
        let localStream = null;
        let audioStream = null;  // Отдельный аудиопоток
        let peerConnection = null;
        let isCameraOn = false;
        let isMuted = true;  // Изначально выключен звук

        // Функция для инициализации аудиопотока (только микрофон)
        async function initAudioStream() {
            try {
                audioStream = await navigator.mediaDevices.getUserMedia({ audio: true });
                audioStream.getAudioTracks()[0].enabled = !isMuted;  // Установить начальное состояние звука
                console.log("Аудиопоток инициализирован:", audioStream);
                // Добавить аудиотрек в соединение, если доступен
                if (peerConnection && audioStream) {
                    audioStream.getTracks().forEach(track => peerConnection.addTrack(track, audioStream));
                }
            } catch (error) {
                console.error("Ошибка доступа к микрофону:", error);
            }
        }

        // Функция для переключения звука
        muteButton.addEventListener("click", () => {
            if (!audioStream) {
                // Инициализировать аудиопоток, если еще не сделано
                initAudioStream().then(() => {
                    toggleAudio();
                });
            } else {
                toggleAudio();
            }
        });

        function toggleAudio() {
            isMuted = !isMuted;
            muteIcon.src = isMuted ? muteIcon.getAttribute('data-hide') : muteIcon.getAttribute('data-show');
            if (audioStream && audioStream.getAudioTracks().length > 0) {
                audioStream.getAudioTracks()[0].enabled = !isMuted;
                console.log("Звук выключен:", isMuted);
                socket.emit('audio-mute', { isMuted });
            }
        }

        // Функция для полного отключения аудиопотока
        function stopAudioStream() {
            if (audioStream) {
                audioStream.getTracks().forEach(track => track.stop());
                audioStream = null;
            }
        }

        // Функция для переключения камеры вкл/выкл
        async function toggleCamera() {
            if (isCameraOn) {
                stopCamera();
            } else {
                startCamera();
            }
        }

        async function startCamera() {
            try {
                // Доступ к видео (аудио уже доступен отдельно в initAudioStream)
                localStream = await navigator.mediaDevices.getUserMedia({ video: true });
                cameraIcon.src = cameraIcon.getAttribute('data-show');
                isCameraOn = true;

                createPeerConnection();

                // Добавить каждый видеотрек в соединение
                localStream.getTracks().forEach(track => peerConnection.addTrack(track, localStream));

                // Отправить предложение другому участнику
                const offer = await peerConnection.createOffer();
                await peerConnection.setLocalDescription(offer);
                socket.emit('offer', { type: 'offer', sdp: offer.sdp });
            } catch (error) {
                console.error("Ошибка доступа к камере:", error);
            }
        }

        function stopCamera() {
            if (localStream) {
                localStream.getTracks().forEach(track => track.stop());
                localStream = null;
            }
            if (peerConnection) {
                peerConnection.close();
                peerConnection = null;
            }

            cameraIcon.src = cameraIcon.getAttribute('data-hide');
            isCameraOn = false;
            remoteVideo.srcObject = null;
            remoteVideo.classList.remove("remote-video-style");
            socket.emit('offer', { type: 'offer', sdp: null });
        }

        function createPeerConnection() {
            peerConnection = new RTCPeerConnection();

            // Обработка входящего удаленного трека
            peerConnection.ontrack = (event) => {
                if (event.streams && event.streams[0]) {
                    remoteVideo.srcObject = event.streams[0];
                    console.log("Получен удаленный поток:", event.streams[0]);
                } else {
                    console.warn("Нет потоков в событии ontrack.");
                }
                remoteVideo.classList.add("remote-video-style");
            };

            // Обработка кандидатов ICE
            peerConnection.onicecandidate = (event) => {
                if (event.candidate) {
                    socket.emit('candidate', { candidate: event.candidate });
                }
            };

            // Добавить аудиопоток независимо от видео
            if (audioStream) {
                audioStream.getTracks().forEach(track => peerConnection.addTrack(track, audioStream));
            }
        }

        // Обработчики событий сокетов для сигнализации
        socket.on("offer", async (data) => {
            if (data.sdp) {
                if (!peerConnection) createPeerConnection();
                await peerConnection.setRemoteDescription(new RTCSessionDescription({ type: "offer", sdp: data.sdp }));
                const answer = await peerConnection.createAnswer();
                await peerConnection.setLocalDescription(answer);
                socket.emit("answer", { type: "answer", sdp: answer.sdp });
            } else {
                if (peerConnection) {
                    peerConnection.close();
                    peerConnection = null;
                }
                remoteVideo.srcObject = null;
                remoteVideo.classList.remove("remote-video-style");
            }
        });

        socket.on("answer", async (data) => {
            if (peerConnection) {
                await peerConnection.setRemoteDescription(new RTCSessionDescription({ type: "answer", sdp: data.sdp }));
            }
        });

        socket.on("candidate", async (data) => {
            if (peerConnection && data.candidate) {
                await peerConnection.addIceCandidate(new RTCIceCandidate(data.candidate));
            }
        });

        // Обработка отключения/включения звука для удаленного аудио
        socket.on("audio-mute", (data) => {
            if (remoteVideo.srcObject && remoteVideo.srcObject.getAudioTracks().length > 0) {
                remoteVideo.srcObject.getAudioTracks()[0].enabled = !data.isMuted;
                console.log("Удаленный звук выключен:", data.isMuted);
            }
        });
    </script>
</body>
</html>

Ниже приведен код app.py, который я использую –

from flask import Flask, render_template, request, redirect, url_for, abort
from flask_socketio import SocketIO, emit

app = Flask(__name__)
socketio = SocketIO(app)

@app.route("https://stackoverflow.com/")
def index():
    return render_template('index.html')

@app.route('/candidate', methods = ['GET'])
def candidateLogin():
    return render_template('user1.html')

@app.route('/interviewer', methods = ['GET'])
def interviewerLogin():
    return render_template('user2.html')

@app.route('/candidate_interface')
def candidateInterface():
    name = request.args.get('name')
    return render_template('user1_interface.html')

@app.route('/interviewer_interface')
def interviewerInterface():
    name = request.args.get('name')
    return render_template('user2_interface.html')

@app.route('/candidate_signin', methods = ['POST'])
def candidateSignin():
    name = request.args.get('name')
    print(name)
    return redirect(url_for('candidateInterface'))

@app.route('/interviewe_signin', methods = ['POST'])
def intervieweSignin():
    name = request.args.get('name')
    print(name)
    return redirect(url_for('interviewerInterface'))

@socketio.on('offer')
def handle_offer(data):
    print("offer: ", data, '\n')
    emit('offer', data, broadcast=True, include_self=False)

@socketio.on('answer')
def handle_answer(data):
    print("answer: ", data, '\n')
    emit('answer', data, broadcast=True, include_self=False)

@socketio.on('candidate')
def handle_candidate(data):
    print("candidate: ", data, '\n')
    emit('candidate', data, broadcast=True, include_self=False)

@socketio.on('audio-mute')
def handle_audio_mute(data):
    print("audio-mute:", data, '\n')
    emit('audio-mute', data, broadcast=True, include_self=False)

if __name__ == '__main__':
    socketio.run(app, debug=True)

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

Я пробовал

Ответ или решение

Проблемы с потоковой передачей аудио в приложении Flask с Socket.IO

Ваша проблема заключается в том, что вы смогли собрать код для передачи аудио независимо от видео в вашем приложении Flask с Socket.IO, однако аудио не воспроизводится. Давайте разберем ваш код и выявим возможные причины возникшей проблемы, предложив решения.

1. Обзор текущего подхода

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

2. Ошибки в инициализации аудиопотока

Ваша логика в функции initAudioStream выглядит корректно, так как вы инициируете только аудиопоток. Но обратите внимание, что вы не добавляете аудиотрек к peerConnection сразу после инициализации. Вы добавляете трек к peerConnection только после нажатия кнопки. Это может привести к ситуации, когда аудиотрек еще не подключен при первом вызове toggleAudio.

Решение:

Подумайте о добавлении аудио трека в createPeerConnection, когда он инициализируется, а не только при нажатии на кнопку.

function createPeerConnection() {
    peerConnection = new RTCPeerConnection();

    // Добавление аудиотрека, если он существует
    if (audioStream) {
        audioStream.getTracks().forEach(track => peerConnection.addTrack(track, audioStream));
    }

    peerConnection.ontrack = (event) => {
        if (event.streams && event.streams[0]) {
            remoteVideo.srcObject = event.streams[0];
            console.log("Received remote stream:", event.streams[0]);
        }
        remoteVideo.classList.add("remote-video-style");
    };

    // ...
}

3. Синхронизация сигналов

Согласно вашему коду, вы используете сокеты для обмена аудиопотоками между пользователями, но у вас отсутствует проверка на доступность аудиотреков в удаленном видео. Вам необходимо убедиться, что вы правильно обрабатываете события, такие как ‘audio-mute’ и ‘offer’, чтобы настраивать аудиопотоки корректно как на стороне клиента, так и на сервере.

Решение:

Проверьте, что аудиотреки добавляются от другого пользователя при получении offer через сокеты.

Измените обработчик socket.on("offer", ...) следующим образом:

socket.on("offer", async (data) => {
    if (data.sdp) {
        if (!peerConnection) createPeerConnection();

        // Добавьте здесь проверку на наличие аудиотреков
        if (audioStream) {
            audioStream.getTracks().forEach(track => peerConnection.addTrack(track, audioStream));
        }

        await peerConnection.setRemoteDescription(new RTCSessionDescription({ type: "offer", sdp: data.sdp }));
        const answer = await peerConnection.createAnswer();
        await peerConnection.setLocalDescription(answer);
        socket.emit("answer", { type: "answer", sdp: answer.sdp });
    } else {
        if (peerConnection) {
            peerConnection.close();
            peerConnection = null;
        }
        remoteVideo.srcObject = null;
        remoteVideo.classList.remove("remote-video-style");
    }
});

4. Проверка мутации звука

Убедитесь, что ваш код, контролирующий состояние мутации (включен/выключен), адекватно отражает текущее состояние. Добавьте логи для контроля состояния ваших треков:

console.log("Audio track enabled state: ", audioStream.getAudioTracks()[0].enabled);

Заключение

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

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

Оцените материал
Добавить комментарий

Капча загружается...