https://habr.com/ru/post/502726/Дорогой читатель, перед тем как мы начнем писать код, давайте разберем саму концепцию видеозвонков.
Представим ситуацию: у нас есть чат-платформа и нам необходимо прикрутить к ней видеозвонки, то есть в онлайне сидит некий Вася и он хочет позвонить Пете, для реализации такой фичи нам понадобится технология
WebSocket.
Что ж, давайте поднимем наш WebSocket сервер, нам в этом поможет node.js;
Создадим файл
sockets.js и запишем туда код сокет сервера:
const WebSocketServer = require('websocket').server;
const http = require('http');
const server = http.createServer(function(request, response) {
//здесь мы ничего не пишем,потому что мы используем сокеты,а не http
});
server.listen(1337, function() {});
// создаем вебсокет сервер
const wsServer = new WebSocketServer({
httpServer: server
});
wsServer.on('request', function(request) {
let connection = request.accept(null, request.origin);
//принимаем подключение к сокету
})
Создадим файл
index.html и запишем туда код для открытия сокет-соединения:
<video autoplay muted height='300' width='300' style="position:fixed;bottom:0;left:0;z-index: 9999;" src="" id='my'>
</video> <!-- наше видео -->
<video autoplay height='300' width='300' style="position:fixed;bottom:0;left:300px;z-index: 999999;" src="" id='not_my'>
</video> <!--видео нашего собеседника-->
Теперь создадим и подключим файл
script.js к нашему html файлу:
let connection = new WebSocket('ws://127.0.0.1:1337');//подключаемся к нашему сокет-серверу
connection.onopen = function(){
//если вам необходимо,можете отправлять какие-либо данные на сокет при его открытии
}
connection.onmessage = function(message){
//функция которая будет выполняться при приходе сообщения от сокета
}
connection.onerror = function (error) {
console.error(error)
//функция которая выполнится,если будет ошибка соединения
};
Итак, вернемся к нашему Васе и Пете
Это начальный этап на котором Вася с Петей просто обмениваются JSON, о том будем ли принимать звонок или нет.То есть при заходе на нашу страницу мы обязательно должны открывать WebSocket соединение для связи с нашим сокет сервером.
- Мы шлем сначала на сокет сервер JSON с тем, что мы хотим позвонить Пете с аккаунта Васи
connection.send(JSON.stringify({
//данные для инициализации
}))
- На сокет сервере мы должны этот JSON принять, после того как мы получили request, мы на наш сервер навешиваем событие message:
connection.on('message',function(message){
//можем работать с message и отсылать кому угодно,но прежде переведем все из JSON в JS
let self = JSON.parse(message.utf8Data);
})
Пообщавшись и решив, что оба пользователя готовы к разговору, мы должны разобраться как работает видеосвязь в браузере, в js встроен модуль для этого — RTCPeerConnection
Нам необходимо открыть RTCPeerConnection, с помощью которого мы сможем сгененрировать offer и отправить его нужному нам пользователю опять же через наш сокет-сервер, который получив его сгенерирует нам answer и отправит обратно, после чего мы начинаем обмениваться ice пакетами, в которых содержится информация об окружении данного компьютера, которая необходима для успешного установления видеосвязи.
Генерируем и отправляем offer
var pc = new RTCPeerConnection();
var peerConnectionConfig = {
iceServers: [
{
urls: 'stun:stun.l.google.com:19302'
}
]
}
pc.onicecandidate = function (event) {
console.log('new ice candidate', event.candidate);
if (event.candidate !== null) {
connection.send(JSON.stringify({
//отправляем json и ice пакеты
}))
}
};
navigator.getUserMedia = navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia;
navigator.getUserMedia({video: true,audio:true}, function(stream) {
// Добавление локального потока не вызовет onaddstream обратного вызова,
// так называют его вручную.
var my_video = document.getElementById('my')
my_video.srcObject = stream
pc.onaddstream = e => {
document.getElementById('not_my').srcObject = e.stream;
console.log('not stream is added')
}
pc.addStream(stream);
pc.createOffer(function(offer) {
pc.setLocalDescription(offer, function() {
//отправляем наш offer на сокет
}))
}, e=> console.log(e));
}, e=> console.log(e));
},function (){console.warn("Error getting audio stream from getUserMedia")});
// функция помощник
function endCall() {
var videos = document.getElementsByTagName("video");
for (var i = 0; i < videos.length; i++) {
videos[i].pause();
}
pc.close();
}
function error(err) {
endCall();
}
Обрабатываем offer и генерируем answer
var pc = new RTCPeerConnection();//создаем connection
var peerConnectionConfig = { //конфигурация ice server
iceServers: [
{
urls: 'stun:stun.l.google.com:19302'
}
]
}
pc.onicecandidate = function (event) {
console.log('new ice candidate', event.candidate);
if (event.candidate !== null) {
//отправляем ice пакеты
}
};
navigator.getUserMedia = navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia;
navigator.getUserMedia({video: true,audio:true}, function(stream) {//подключаем у пользователя видеосвязь и аудиосвязь и втыкаем ее на страницу
var my_video = document.getElementById('my')
my_video.srcObject = stream
console.log('stream is added while offering')
pc.onaddstream = e => {
console.log('not my stream is added while offering')
document.getElementById('not_my').srcObject = e.stream;
}
pc.addStream(stream);
pc.setRemoteDescription(new RTCSessionDescription(data.offer), function() {
pc.createAnswer(function(answer) {
pc.setLocalDescription(answer, function() {
//отправляем ответ
}, e => console.log(e));
}, e => console.log(e));
}, e => console.log(e));
},function (){console.warn("Error getting audio stream from getUserMedia")});
}
}
Принимаем answer и создаем видеопоток
pc.setRemoteDescription(new RTCSessionDescription(data.answer), function() { }, error);
И последнее что нам нужно сделать — обработать ice пакеты
pc.addIceCandidate(new RTCIceCandidate(ice))//тут ice - тот,который пришел нам от пользователя