Вопрос или проблема
Ниже приведен мой код 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
в реальном времени.