Ко всем статьям

Потоковое соединение с помощью WebRTC и simple-peer

Реализация потокового соединения на JavaScript с использованием технологии WebRTC и библиотеки simple-peer

WebRTC и ICE

WebRTC – это браузерная технология, которая позволяет передавать потоковые данные между клиентами. Ее преимущество заключается в возможности устанавливать соединения между браузерами без необходимости прописывать серверную логику приложения. С помощью WebRTC мы можем передавать не только аудио- или видео-данные, а также файлы и текстовые сообщения.

Установка соединения между браузерами предполагает обмен данными и наличие двух сторон: инициирующей и принимающей. Чтобы установить соединение, пользователи должны найти друг друга в сети и сообщить о типе своих носителей. Но если у устройств нет публичного адреса, соединение напрямую невозможно. Для этого мы и используем ICE – Interactive Connectivity Establishment. ICE представляет собой метод, позволяющий двум компьютерам находить способ соединения друг с другом настолько напрямую, насколько это возможно. Он состоит из 3 процессов: Nat, Stun, Turn, которые в совокупности позволяют сгруппировать несколько устройств под одним IP-адресом, получить IP-адрес и порт, гарантировать прямое соединение.

Чтобы не настраивать все этапы вручную, мы используем библиотеку simple-peer.

Реализация на simple-peer

Итак, давайте напишем простое приложение для трансляции видео-потока.

Верстка выглядит так:

<!DOCTYPE html>

<html lang="en">

<head>

    <meta charset="UTF-8">

    <meta http-equiv="X-UA-Compatible" content="IE=edge">

    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>webRTC</title>

</head>

<body>

    <p>Офер</p>

    <input class="input__offer" type="text">

    <button class="button_offer">Отправить</button>

    <br>

    <p>Ответ</p>

    <input class="input__answer" type="text">

    <button class="button_answer">Отправить</button> <br>     <video muted></video>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/simple-peer/9.11.1/simplepeer.min.js"></script>

    <script src="./app.js"></script>

</body>

</html>

Как вы могли заметить, у нас два поля для ввода и две кнопки. Это вызвано особенностями работы библиотеки – для подключения необходимо обменяться не только данными, но и ответами. То есть после получения данных нашего собеседника на его основе создается ответ, который нужно отправить.

Механизм обмена ответами будет указан ниже. А пока приступим к написанию кода.

1. Создадим основной объект peer, и сформируем наши данные:

navigator.mediaDevices.getUserMedia({video: true, audio: true}).then(stream => {

    const peerInitiator = new SimplePeer({initiator: true, trickle: false, stream})

    peerInitiator.on('signal', data => {

        console.log(JSON.stringify(data))

    })

})

Функция navigator.mediaDevices.getUserMedia — запрашивает доступ к камере и микрофону, в зависимости от значений параметров video и audio.

Далее мы создаем пир, в который передаем параметры. Initiator:true говорит что данный пир - инициатор, и будет формировать наши данные. В дальнейшем, нам понадобится второй пир, для формирования ответа.

Если оставить параметр trickle:false и запустить сайт, в консоли появится следующее:

В этом большом объеме строк содержится информация о нас.

Если убрать параметр trickle:false, сообщений станет больше. И как раз для того, чтобы объединить данные, мы и будем использовать trickle:false.

Последний параметр – stream – хранит наши видео/аудио данные и при включенном параметре данные будут формироваться с учетом того, что мы хотим передавать аудио/видео данные.

2. Создадим обработчик события клика по кнопке, при котором мы будем получать значение input и на его основе формировать наш ответ. Заметьте, что для ответа мы создали еще один peer

navigator.mediaDevices.getUserMedia({video: true, audio: true}).then(stream => {

    const peerInitiator = new SimplePeer({initiator: true, trickle: false, stream})

    const peerAnswer = new SimplePeer({trickle: false})

    const inputOffer = document.querySelector('.input__offer')

    const buttonOffer = document.querySelector('.button_offer')

    peerInitiator.on('signal', data => {

        console.log(JSON.stringify(data))

    })

    buttonOffer.addEventListener('click', () => {

        const valueOffer = inputOffer.value

        peerAnswer.signal(valueOffer)

        peerAnswer.on('signal', data => {

            console.log(JSON.stringify(data))

        })

    })

})

3. Теперь мы должны получить наш ответ из поля ввода

navigator.mediaDevices.getUserMedia({video: true, audio: true}).then(stream => {

    const peerInitiator = new SimplePeer({initiator: true, trickle: false, stream})

    const peerAnswer = new SimplePeer({trickle: false})

    const inputOffer = document.querySelector('.input__offer')

    const buttonOffer = document.querySelector('.button_offer')

    const inputAnswer = document.querySelector('.input__answer')

    const buttonAnswer = document.querySelector('.button_answer')

    const video = document.querySelector('video')

    peerInitiator.on('signal', data => {

        console.log(JSON.stringify(data))

    })

    buttonOffer.addEventListener('click', () => {

        const valueOffer = inputOffer.value

        peerAnswer.signal(valueOffer)

        peerAnswer.on('signal', data => {

            console.log(JSON.stringify(data))

        })

    })

    buttonAnswer.addEventListener('click', () => {

        const valueAnswer = inputAnswer.value

        peerInitiator.signal(valueAnswer)

    })

})

Поздравляю! Мы сделали peer соединения, теперь нам осталось получить видео поток и добавить его в тег <video>

4. Получение и добавление видео потока

navigator.mediaDevices.getUserMedia({video: true, audio: true}).then(stream => {

    const peerInitiator = new SimplePeer({initiator: true, trickle: false, stream})

    const peerAnswer = new SimplePeer({trickle: false})

    const inputOffer = document.querySelector('.input__offer')

    const buttonOffer = document.querySelector('.button_offer')

    const inputAnswer = document.querySelector('.input__answer')

    const buttonAnswer = document.querySelector('.button_answer')

    const video = document.querySelector('video')

    peerInitiator.on('signal', data => {

        console.log(JSON.stringify(data))

    })

    buttonOffer.addEventListener('click', () => {

        const valueOffer = inputOffer.value

        peerAnswer.signal(valueOffer)

        peerAnswer.on('signal', data => {

            console.log(JSON.stringify(data))

        })

    })

    peerAnswer.on('stream', (stream) => {

        video.srcObject = stream

        video.play()

    })

    buttonAnswer.addEventListener('click', () => {

        const valueAnswer = inputAnswer.value

        peerInitiator.signal(valueAnswer)

    })

})

Теперь вернемся к сайту. У нас есть два пользователя, при загрузке страницы в консоли формируются наши данные. Мы обмениваемся данными (копируем их) друг другу в поле ввода, нажимаем на кнопку, после чего формируется ответ.

Таким же образом обмениваемся и вставляем в поле ввода:


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