Фронтенд розробка JavaScript. / Передача відео аудіо потоку з Openvidu. / Передача відео аудіо потоку з Openvidu.
Передача відео аудіо потоку з Openvidu.
Бібліотека Openvidu надає набір інструментів для забезпечення передачі відео та аудіопотоку між браузерами, шарингу робочого столу, їх запису на стороні сервера, а також обслуговування IP камер.
Процес побудований на технології WebRTC.
Ця технологія досить складна і включає передачу інформації про мережеві та відео налаштування між двома точками по веб-сокетах.
Ця інформація передається у вигляді SDP (опис кодеків, частоти кадрів та ін.) та ICE кандидатів (мережева конфігурація).
Openvidu нам дозволяє не вникати в тонкощі цієї взаємодії яких досить багато, т.к. у різних мережах з’єднати два браузери буває ой як не просто.
Сама бібліотека включає кілька процесів-серверів.
KMS - безкоштовний Karentoo медіа сервер, який обробляє відео та пропускає його через себе кодуючи та записуючи (опціонально), потім він може його передати та браузер буде думати що він працює з іншим браузером але насправді буде віддавати та приймати потік з медіа-сервера. Портти 40000 - 57000 TCP+UDP
Coturn - TURN сервер для визначення IP адрес клієнтів та знаходження дірок мережі через які проводити відео. 3478 порт для визначення IP та 57001 - 65535 TCP+UDP для побудови моста з KMS.
Openvidu - сокет сервер c REST API для обміну повідомленнями між браузером та KMS. Порт 4443.
Redis - база даних
Схема взаємодії.
Маємо 3-х гравців.
openvidu-browser JavaScript бібліотека для встановлення з’єднання.
openvidu-server - Java програма, яка керує KMS медіа сервером.
Kurento Media Server - низькорівневий сервер для операцій з медіа-потоком та його передачею.
Запуск та встановлення включає наступні кроки.
Установка docker та docker-compose.
apt install docker-ce docker-compose
Установка openvidu
cd /opt
curl https://s3-eu-west-1.amazonaws.com/aws.openvidu.io/install_openvidu_latest.sh | bash
Прописуємо налаштування DOMAIN_OR_PUBLIC_IP та OPENVIDU_SECRET в .env.
DOMAIN_OR_PUBLIC_IP=localhost
Стартуємо сервер.
./openvidu start
Старт сервера вручну
docker run -p 4443:4443 --rm -e OPENVIDU_SECRET=MY_SECRET openvidu/openvidu-server-kms:2.18.0
При цьому ми відкриваємо 4443 порт прокидаючи його з контейнера назовні.
Встановлюємо приклад програми
git clone https://github.com/OpenVidu/openvidu-tutorials.git -b v2.18.0
Заходимо у будь-який приклад та запускаємо там статичний вебсервер. Наприклад.
python3 -m http.server 8080
Розробка фронтенд програми.
Приєднуємо клієнта.
<script src="openvidu-browser-2.18.0.js"></script>
Стартуємо сесію.
OV = New OpenVidu();
OV – об’єкт-точка входу до бібліотеки.
Через неї створюємо решту об’єктів.
Сесія
session = OV.initSession();
Є кімнатою користувача, до якої можна додавати з’єднання.
Сесія має низку подій.
streamCreated - спрацьовує коли до сесії додається новий потік. У цій події ми підписуємося на потік і потім ми можемо додавати відеотег довільний HTML елемент за ID.
session.on('streamCreated', event => {
var subscriber = session.subscribe(event.stream, 'video-container');
})
videoElementCreated - це подія передплатника в якому ми можемо щось зробити після того, як додано відео тег і пішло відео.
streamDestroyed - подія сесії, що спрацьовує при залишанні кімнати та видаленні кожного відеопотоку.
exception - подія сесії при помилках виконання асинхронних операцій.
З’єднання
Представляє конкретний відеопотік користувача, який може бути доданий у сесію.
Сесія має унікальний ідентифікатор, як і з’єднання має унікальний токен.
Створення сесії на сервері.
Для того, щоб на сервері створити сесію, необхідно надіслати POST запит на адресу /openvidu/api/sessions та передати вигадане ім’я сесії.
function createSession(sessionId) {
// See https://docs.openvidu.io/en/stable/reference-docs/REST-API/#post-openviduapisessions
return new Promise((resolve, reject) => {
$.ajax({
type: "POST",
url: this.OPENVIDU_SERVER_URL + "/openvidu/api/sessions",
data: JSON.stringify({ customSessionId: sessionId }),
headers: {
"Authorization": "Basic " + btoa("OPENVIDUAPP:" + this.OPENVIDU_SERVER_SECRET),
"Content-Type": "application/json"
},
success: (response) => {
return resolve(response.id)
},
error: (error) => {
if (error.status === 409) {
resolve(sessionId);
} else {
console.warn('Error');
}
}
}
});
});
},
При цьому запит може повернути статус 409 у разі сесії з таким ім’ям. При цьому нічого страшного не відбувається, а результат ігнорується і передається далі ланцюжком асинхронних промісів.
Створення з’єднання.
POST запит на /openvidu/api/sessions/’ + sessionId + ‘/connection’
Поверне токен з’єднання.
function createToken(sessionId) {
// See https://docs.openvidu.io/en/stable/reference-docs/REST-API/#post-openviduapisessionsltsession_idgtconnection
return new Promise((resolve, reject) => {
$.ajax({
type: 'POST',
url: this.OPENVIDU_SERVER_URL + '/openvidu/api/sessions/' + sessionId + '/connection',
data: JSON.stringify({}),
headers: {
'Authorization': 'Basic ' + btoa('OPENVIDUAPP:' + this.OPENVIDU_SERVER_SECRET),
'Content-Type': 'application/json',
},
success: (response) => {
return resolve(response.token)
},
error: (error) => reject(error)
});
});
},
Функція, що поєднує 2 запити в ланцюжок.
function getToken(mySessionId) {
return createSession(mySessionId).then(sessionId => createToken(sessionId));
}
Т.к. ці запити мають секрети, їх слід виконувати на стороні сервера і не світити в js.
Розмітка сторінки.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Openvidu sender</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="/static/openvidu.js"></script>
</head>
<body>
<h1>Openvidu Sender page</h1>
<div id="senderCam"></div>
<a id="start">Start video translation</a>
<script src="/static/ov/index.js"></script>
<script>
$('#start').on('click', () => {
window.myapp.initappSender('woman');
})
</script>
</body>
</html>
openvidu.js - браузерна бібліотека, можна скопіювати з будь-якого прикладу.
Створюємо об’єкт програми та функцію ініціалізації для транслюючого відео.
var ovapp = {
OPENVIDU_SERVER_URL: 'https//localhost:4443',
OPENVIDU_SERVER_SECRET: 'MY_SECRET',
initappSender: function(uname) {
OV = new OpenVidu();
session = OV.initSession();
}
}
Додамо функції створення сесії та з’єднання.
var ovapp = {
OPENVIDU_SERVER_URL: 'https://localhost:4443',
OPENVIDU_SERVER_SECRET: 'MY_SECRET',
initappSender: function(uname) {
OV = new OpenVidu();
session = OV.initSession();
this.getToken('woman').then(
(token) => {
console.log(`we got token ${token}!!!`)
}
);
},
createSession: function (sessionId) {
return new Promise((resolve, reject) => {
$.ajax({
type: "POST",
url: this.OPENVIDU_SERVER_URL + "/openvidu/api/sessions",
data: JSON.stringify({ customSessionId: sessionId }),
headers: {
"Authorization": "Basic " + btoa("OPENVIDUAPP:" + this.OPENVIDU_SERVER_SECRET),
"Content-Type": "application/json"
},
success: response => resolve(response.id),
error: (error) => {
if (error.status === 409) {
resolve(sessionId);
} else {
console.warn('No connection to OpenVidu Server. This may be a certificate error at ' + this.OPENVIDU_SERVER_URL);
if (window.confirm('No connection to OpenVidu Server. This may be a certificate error at \"' + this.OPENVIDU_SERVER_URL + '\"\n\nClick OK to navigate and accept it. ' +
'If no certificate warning is shown, then check that your OpenVidu Server is up and running at "' + this.OPENVIDU_SERVER_URL + '"')) {
location.assign(this.OPENVIDU_SERVER_URL + '/accept-certificate');
}
}
}
});
});
},
createToken: function (sessionId) {
return new Promise((resolve, reject) => {
$.ajax({
type: 'POST',
url: this.OPENVIDU_SERVER_URL + '/openvidu/api/sessions/' + sessionId + '/connection',
data: JSON.stringify({}),
headers: {
'Authorization': 'Basic ' + btoa('OPENVIDUAPP:' + this.OPENVIDU_SERVER_SECRET),
'Content-Type': 'application/json',
},
success: (response) => resolve(response.token),
error: (error) => reject(error)
});
});
},
getToken: function(mySessionId) {
return this.createSession(mySessionId).then(sessionId => this.createToken(sessionId));
}
}
Результат.
Створимо сокет-з’єднання для отримання повідомлень про запит на передачу відео.
Встановимо клієнта.
npm install socket.io-client --save
Скопіюємо з папки dist та підключимо бібліотеку на сторінці.
<script src="/static/socket.io.min.js"></script>
Створимо з’єднання із сервером по сокетах.
var ovapp = {
...
SOCKET_URL: 'http://localhost:5001',
socketConnect: function(login) {
socket = io(`${this.SOCKET_URL}`, {transports:['websocket']});
socket.on('connect', () => {
console.log('Connection was established');
window.sessionStorage.setItem('sid',socket.id);
socket.emit('login',{login});
})
},
При цьому ми за подією з’єднання додатково повідомляємо сервер про логіну людини, яка приєдналася, щоб сервер це зафіксував у БД і знав хто в онлайні
initappSender: function(username) {
...
this.socketConnect(username);
...
Відреагуємо на повідомлення на прийом відео від сторони, що приймає.
Відобразимо діалогове вікно з кнопками згоди або відмови, а також з відео тегом.
socket.on('calling', (msg) => {
const tpl = `<div id="responseBox">
<h1 id="callerName">${msg.login} is calling you!<h1>
<input type="text" id="recieverLogin" value="${msg.login}">
<video autoplay="true" width="200" id="myVideo"></video>
<div style="text-align: center">
<a class="btn" id="acceptOffer">Accept</a>
<a class="btn" id="declineOffer">Decline</a>
<a class="btn" style="display:none" id="stopVideo">Stop video</a>
</div>
</div>`;
$('#senderCam').html(tpl);
$('#senderCam').show();
});
Оформимо сторінку на стороні, що приймає.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Openvidu reciever</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="/static/openvidu.js"></script>
<script src="/static/socket.io.min.js"></script>
<link rel="stylesheet" href="/static/video.css">
</head>
<body>
<h1>Openvidu Reciever page</h1>
<div id="recieverCam"></div>
<button id="VideoCall" data-username="woman">Video call</button>
<script src="/static/ov/index.js"></script>
<script>
window.ovapp.initappReciever('man');
</script>
</body>
</html>
window.myapp.initappReciever(‘man’) - тут ми ініціалізуємо додаток для приймаючого та передаємо його логін, для подальшої передачі його на бік передавального, щоб вивести його на діалоговому вікні виклику.
У кнопку виклику закладаємо інформацію про логіну викликаного.
Оформимо функцію initappReciever.
var ovapp = {
...
initappReciever: function(username){
console.log('Init sender app');
socket = io(`${this.SOCKET_URL}`, {transports:['websocket']});
$('#VideoCall').on('click', (e) => {
this.callUser();
})
},
...
Тепер у функції callUser ми повинні надіслати повідомлення до браузера передавального.
callUser: function() {
const url = `${this.SERVER_URL}/call`;
const username = $('#VideoCall').attr( "data-username" );
$.ajax({
url: url,
type: "POST",
data: JSON.stringify({
login: username,
sid: window.sessionStorage.getItem('sid')
}),
contentType: "application/json",
success: (response) => {
if(response.status === 0) {
$('#VideoCall').html(response.message);
} else {
alert(response.message);
}
}
});
},
Для цього пошлемо HTTP запит на сервер і він передасть його через сокет на бік передавального.
Результат.
Відпрацюємо відмову, надіславши на сервер повідомлення по ресту.
socket.on('calling', (msg) => {
...
$('#senderCam').show();
$('#declineOffer').on('click', async (e) => {
const url = `${this.SERVER_URL}/decline`;
$.ajax({
type: "POST",
url: url,
contentType: "application/json",
data: JSON.stringify({
'sid': window.sessionStorage.getItem('sid'),
'reciever_login': $('#recieverLogin').val()
}),
success: (data) => {
console.log(data);
$('#senderCam').html('');
},
});
})
});
Тепер ухвалення запрошення.
$('#acceptOffer').on('click', async (e) => {
$('#acceptOffer').hide();
$('#declineOffer').hide();
$('#stopVideo').show();
$('#callerName').html('Click here to move');
const url = `${this.SERVER_URL}/status`;
$.ajax({
type: "POST",
url: url,
contentType: "application/json",
data: JSON.stringify({
'sid': window.sessionStorage.getItem('sid'),
'status':'beasy'
}),
success: (data) => {
},
});
});
Де ми приховуємо та відображаємо відповідні кнопки та надсилаємо повідомлення на сервер про те, що користувач зайнятий.
Далі нам необхідно відобразити камеру на передавальній строні.
$('#acceptOffer').on('click', async (e) => {
$('#acceptOffer').hide();
$('#declineOffer').hide();
$('#stopVideo').show();
$('#callerName').html('Click here to move');
OV = new OpenVidu();
session = OV.initSession();
this.getToken(this.USERNAME).then(function(token) {
session.connect(token, { })
.then(() => {
var publisher = OV.initPublisher('video-container', {
audioSource: undefined,
videoSource: undefined,
publishAudio: true,
publishVideo: true,
resolution: '640x480',
frameRate: 30,
insertMode: 'APPEND',
mirror: false
});
session.publish(publisher);
})
});
...
Відобразимо її і на приймаючій.