Эхо и завершение звонка в webrtc-php аудио звонке
Код практически работает но слышно эхо на каждой стороне, эхо собственного голоса. Так же нужна помощь в завершении звонка. Я полностью не понимаю технологию, и не могу найти ответ в гугле или документации.
var answer = 0;
var pc = null
var localStream = null;
var ws = null;
// Не нужно с вебсокетами, но т.к используется хак, то нужно для различий между звонками
// Уникальный идентификатор пользователя чата, используется в php файлах для отсылки и
// приема сообщений
var unique = Math.floor(100000 + Math.random() * 900000);
// Для передачи аудио потоков, видео тоже возможно но есть сложности
// в запуске где один клиент не имеет камеры
var localVideo = document.getElementById('localVideo');
var remoteVideo = document.getElementById('remoteVideo');
// Что такое ICE сервер - https://voxlink.ru/kb/asterisk-configuration/obzor-protokolov-stun-turn-i-ice-ih-principy-raboty-s-nat-i-ispolzovanie-dlja-voip/
// Необходим для соединения клиентов за роутерами без белого ип
// Ниже бесплатные сервера в свободном доступе, хотя гугловский вроде перестал работать
var configuration = {
'iceServers': [{
'urls': 'stun:stun.stunprotocol.org:3478'
},
{
'urls': 'stun:stun.l.google.com:19302'
}
]
};
// Функция старта звонка, как запустится(по нажатию кнопки) на обоих клиентах
// произойдет соединение
function StartCall() {
// Интерфейс MediaDevices предоставляет доступ к подключенным медиа-устройствам
// ввода, таким как камера, микрофон
// Метод MediaDevices.getUserMedia(), при выполнении, вызывает всплывающий
// диалог, запрашивающий разрешение пользователя на использование медиа устройства
// (камера, микрофон). Результат возвращает промис, содержащий поток
// !!! Без https не работает !!!
navigator.mediaDevices.getUserMedia({
audio: true
}).then(function(stream) { // Объект Promise (промис) используется для
// отложенных и асинхронных вычислений.
// Не будет вызван если пользователь не разрешил микрофон
// Параметром получает аудио поток
localVideo.srcObject = stream;
localStream = stream;
try {
// Замена вебсокету которого нет на хостинге
// Интерфейс EventSource используется для получения серверных событий
ws = new EventSource('serverGet.php?unique=' + unique);
} catch (e) {
console.error("Could not create eventSource ", e);
}
// Хак веб сокета, т.к EventSource не отсылает данные то используется АЯКС
// Теперь EventSource(ws) больше похож на вебсокет
ws.send = function send(message) {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState != 4) {
return;
}
if (this.status != 200) {
console.log("Error sending to server with message: " + message);
}
};
xhttp.open('POST', 'serverPost.php?unique=' + unique, true);
xhttp.setRequestHeader("Content-Type", "Application/X-Www-Form-Urlencoded");
xhttp.send(message);
}
// onmessage сделано так чтобы одновременно получать несколько эвентов
// для ускорения работы
ws.onmessage = function(e) {
if (e.data.includes("_MULTIPLEVENTS_")) {
multiple = e.data.split("_MULTIPLEVENTS_");
for (x = 0; x < multiple.length; x++) {
onsinglemessage(multiple[x]);
}
} else {
onsinglemessage(e.data);
}
}
localVideo.addEventListener('loadedmetadata',
function() {
publish('client-call', null)
}
);
}).catch(function(e) {
alert("Проблема получения доступа к микрофону, возможно не защищенное соединение");
console.log("Problem while getting audio stuff ", e);
});
}
// Привязка кнопки к началу звонка
var btnStart = document.getElementById('btnStart');
btnStart.onclick = function() {
StartCall();
};
// вызывается из EventSource при получении сообщения
function onsinglemessage(data) {
var package = JSON.parse(data);
var data = package.data;
console.log("received single message: " + package.event);
switch (package.event) {
case 'client-call':
icecandidate(localStream);
pc.createOffer({
offerToReceiveAudio: 1,
offerToReceiveVideo: 1
}).then(function(desc) {
pc.setLocalDescription(desc).then(
function() {
publish('client-offer', pc.localDescription);
}
).catch(function(e) {
console.log("Problem with publishing client offer" + e);
});
}).catch(function(e) {
console.log("Problem while doing client-call: " + e);
});
break;
case 'client-answer':
if (pc == null) {
console.error('Before processing the client-answer, I need a client-offer');
break;
}
pc.setRemoteDescription(new RTCSessionDescription(data), function() {},
function(e) {
console.log("Problem while doing client-answer: ", e);
});
break;
case 'client-offer':
// Подключение аудио потока через сервер - пир-ту-пир
icecandidate(localStream);
// pc = RTCPeerConnection класс WebRTC
pc.setRemoteDescription(new RTCSessionDescription(data), function() {
if (!answer) {
pc.createAnswer(function(desc) {
pc.setLocalDescription(desc, function() {
publish('client-answer', pc.localDescription);
}, function(e) {
console.log("Problem getting client answer: ", e);
});
}, function(e) {
console.log("Problem while doing client-offer: ", e);
});
answer = 1;
}
}, function(e) {
console.log("Problem while doing client-offer2: ", e);
});
break;
case 'client-candidate':
if (pc == null) {
console.error('Before processing the client-answer, I need a client-offer');
break;
}
pc.addIceCandidate(new RTCIceCandidate(data), function() {},
function(e) {
console.log("Problem adding ice candidate: " + e);
});
break;
}
};
function icecandidate(localStream) {
// Интерфейс RTCPeerConnection представляет соединение WebRTC между локальным
// пиром (участником соединения) на локальном компьютере и удаленным пиром на
// удаленном компьютере. Он предоставляет методы для соединения с удаленным
// участником соединения, обслуживания, мониторинга и закрытия соединения.
pc = new RTCPeerConnection(configuration);
pc.onicecandidate = function(event) {
if (event.candidate) {
publish('client-candidate', event.candidate);
}
};
try {
// Добавление аудио потока в соединение, происходит в начале аудио звонка
// на обоих сторонах
pc.addStream(localStream);
} catch (e) {
// Если бы у потока было несколько треков(аудио/видео)
var tracks = localStream.getTracks();
for (var i = 0; i < tracks.length; i++) {
pc.addTrack(tracks[i], localStream);
}
}
pc.ontrack = function(e) {
// Подключение удаленного аудио потока
remoteVideo.srcObject = e.streams[0];
};
}
// Отослать эвент другой стороне
function publish(event, data) {
console.log("sending ws.send: " + event);
ws.send(JSON.stringify({
event: event,
data: data
}));
}