Pull to refresh

Видео-чат через браузер. WebRTC — это просто, если есть библиотека

Reading time8 min
Views14K

Я работаю с MFF и GH. Дружат ли другие браузеры с WebRTC, можно узнать, зайдя на sipjs.com — там без регистрации можно полюбоваться на себя в двух экземплярах (если есть веб-камера), послать себе сообщение или файл. И все это на одной странице. Неинтересно. Интересно, когда я на одной странице, а мой визави на другой. Демо-пример нужно чуть-чуть подправить...



Как это все работает (в моем понимании):


WebRTC — это соединение двух UserAgent (т.е. двух браузеров, далее UA) точка-точка, при котором сигнал с веб-камеры, захваченной одним браузером, стремительным потоком передается другому браузеру.


Чтобы установить соединение и прорваться через частокол NAT'ов нужен SIP и STUN сервер. Оба UA должны быть зарегистрированы на SIP сервере. Что-то типа "1234@myfreefreefreeswitch.ru".


UA с именем "1234@..." и паролем "111" авторизуется на SIP-сервере "myfreefreefreeswitch.ru" и говорит: хочу связаться с UA "5678@..." для передачи голоса. Если оба в сети, сервер их соединяет.


Браузер спрашивает пользователя: "Микрофон просят. Дадим?". Дадим, и получим voice-ip.


Проверить видео-чат с двух разных компьютеров можно на основе демо-примера с sipjs.com, используя в качестве SIP/STUN-сервера sipjs.onsip.com.


Sipjs.onsip.com не требует предварительной регистрации. Он обслуживает пары UA с именами
"alice.случайная строка@sipjs.onsip.com" и
"bob.та же самая случайная строка@sipjs.onsip.com".


На самом деле "bob." или "alice." не обязательны. Можно подключиться с любым уникальным именем. Но это неприлично.


Случайные строки в их демо-примере генеряться JS при открытии страницы и зачем-то передаются на сервер в document.cookie. К SIP-протоколу куки отношения не имеют, видимо, нужны для чего-то другого (например, чтобы зарегистрировать нового любопытного пользователя).


Для проверки видео-чата с 2-х компьютеров http-сервер не обязателен, достаточно 2-х html страниц (на одной: "я Боб, хочу связаться с Алисой", на другой: "я Алиса, хочу связаться с Бобом") и одного скрипта.


Страницы почти одинаковые (сделаны по одному шаблону).!!! Прежде, чем их открыть надо в обеих страницах одинаково исправить 1 строку !!!


ЭТО ВАЖНО!!! var token = '42c3';
42c3 надо исправить на любую длинную строку (англ.буквы и цифры), иначе ваши Боб и Алиса войдут в конфликт с теми, кто не исправил.


Кроме того, возможно, что ваша строка в какой-то момент устареет и все перестанет работать. Замените ее в обеих страницах на новую одинаковую строку.


Помните: это демо-пример от sipjs.com и onsip.com используется не совсем так, как они планировали. Надеюсь, они не обидятся — мы же их популяризируем.


Alice-tv.html
<!DOCTYPE html>    
<html lang="ru">
  <head>
    <meta charset="utf-8">
    <title>Alice-tv</title>
    <script>
        var domain = 'sipjs.onsip.com';

        var token = '42c3';
        var d123 = new Date();
        d123.setTime(d123.getTime() + 1000*60*60); // expires in 1 hour
        document.cookie = ('onsipToken=' + token + ';' + 'expires=' + d123.toUTCString() + ';');

        var fromName = 'Alice';
        var toName   = 'Bob';

        var fromURI  = fromName.toLowerCase() + '.' + token + '@' + domain;
        var toURI    = toName.toLowerCase()   + '.' + token + '@' + domain;
    </script>
    <script src="https://rawgit.com/onsip/SIP.js/0.7.5/dist/sip-0.7.5.js"></script>
    <script src="demo.js"></script>
  </head>

  <body>    
    <div class="content">
        <div class="demo-window">
            <div class="left">
              <h4>Я Алиса</h4>
              <h5>В окне доктор Боб</h5>
            </div>
            <div class="demo-view">
              <video id="video" muted="muted"></video>
            </div>

            <button id="video-button" class="right" type="button">video</button>
            <div class="clearfix"></div>

            <div id="content-message">

                <div id="message-display">
                    <p class="message"><span class="message-from"></span> <span class="message-body placeholder">No messages yet</span></p>
                </div>
                <textarea id="message-input" class="message-input" placeholder="Enter your message here!"></textarea>
                <br>
                <button id="message-button" class="right" type="button">send message</button>
            </div>
        </div>
    </div>
</body>
</html>

Bob-tv.html
<!DOCTYPE html>    
<html lang="ru">
  <head>
    <meta charset="utf-8">
    <title>Bob-tv</title>
    <script>
        var domain = 'sipjs.onsip.com';

        var token = '42c3';
        var d123 = new Date();
        d123.setTime(d123.getTime() + 1000*60*60); // expires in 1 hour
        document.cookie = ('onsipToken=' + token + ';' + 'expires=' + d123.toUTCString() + ';');

        var fromName = 'Bob';
        var toName   = 'Alice';

        var fromURI  = fromName.toLowerCase() + '.' + token + '@' + domain;
        var toURI    = toName.toLowerCase()   + '.' + token + '@' + domain;
    </script>
    <script src="https://rawgit.com/onsip/SIP.js/0.7.5/dist/sip-0.7.5.js"></script>
    <script src="demo.js"></script>
  </head>

  <body>    
    <div class="content">
        <div class="demo-window">
            <div class="left">
              <h4>Я доктор Боб</h4>
              <h5>В окне вижу Алису</h5>
            </div>
            <div class="demo-view">
              <video id="video" muted="muted"></video>
            </div>

            <button id="video-button" class="right" type="button">video</button>
            <div class="clearfix"></div>

            <div id="content-message">

                <div id="message-display">
                    <p class="message"><span class="message-from"></span> <span class="message-body placeholder">No messages yet</span></p>
                </div>
                <textarea id="message-input" class="message-input" placeholder="Enter your message here!"></textarea>
                <br>
                <button id="message-button" class="right" type="button">send message</button>
            </div>
        </div>
    </div>
</body>
</html>

Скрипт не передает звук (компьютер не начнет орать, как телевизор). Чтобы разрешить audio, надо исправить false на true в 2-х местах:


строка 96: var options = mediaOptions(false, true, remoteRender, null);
строка 116-117: session = makeCall(userAgent, target,
false, true,


demo.js
function createUA(callerURI, displayName) {

    var configuration = {
        traceSip: true,
        uri: callerURI,
        displayName: displayName
    };
    var userAgent = new SIP.UA(configuration);
    return userAgent;
}    

function setUpMessageInterface(userAgent, target, messageRenderId, messageInputId, buttonId) {
    var messageRender = document.getElementById(messageRenderId);
    var messageInput = document.getElementById(messageInputId);
    var button = document.getElementById(buttonId);

    function sendMessage() {
        var msg = messageInput.value;
        if (msg !== '') {
            messageInput.value = '';
            userAgent.message(target, msg);
        }
    }

    var noMessages = true;

    userAgent.on('message', function (msg) {
        if (noMessages) {
            noMessages = false;
            if (messageRender.childElementCount > 0)
                messageRender.removeChild(messageRender.children[0]);
        }
        var msgTag = createMsgTag(msg.remoteIdentity.displayName, msg.body);
        messageRender.appendChild(msgTag);
    });

    button.addEventListener('click', function () {
        sendMessage();
    });
    messageInput.onkeydown = (function(e) {
        if(e.keyCode == 13 && !e.shiftKey) {
            e.preventDefault();
            sendMessage();
        }
    });
}

function createMsgTag(from, msgBody) {
    var msgTag = document.createElement('p');
    msgTag.className = 'message';

    var fromTag = document.createElement('span');
    fromTag.appendChild(document.createTextNode(from + ':'));

    var msgBodyTag = document.createElement('span');
    msgBodyTag.appendChild(document.createTextNode(' ' + msgBody));
    msgTag.appendChild(fromTag);
    msgTag.appendChild(msgBodyTag);
    return msgTag;
}

function mediaOptions(audio, video, remoteRender, localRender) {
    return {
        media: {
            constraints: {
                audio: audio,
                video: video
            },
            render: {
                remote: remoteRender,
                local: localRender
            }
        }
    };
}

function makeCall(userAgent, target, audio, video, remoteRender, localRender) {
    var options = mediaOptions(audio, video, remoteRender, localRender);
    var session = userAgent.invite('sip:' + target, options);
    return session;
}

function setUpVideoInterface(userAgent, target, remoteRenderId, buttonId) {
    var onCall = false;
    var session;
    var remoteRender = document.getElementById(remoteRenderId);
    var button = document.getElementById(buttonId);

    userAgent.on('invite', function (incomingSession) {
        onCall = true;
        session = incomingSession;
        var options = mediaOptions(false, true, remoteRender, null);
        button.firstChild.nodeValue = 'hang up';
        session.accept(options);
        session.on('bye', function () {
            onCall = false;
            button.firstChild.nodeValue = 'video';
            session = null;
        });
    });

    button.addEventListener('click', function () {
        if (onCall) {
            onCall = false;
            button.firstChild.nodeValue = 'video';
            session.bye();
            session = null;
        }
        else {
            onCall = true;
            button.firstChild.nodeValue = 'hang up';
            session = makeCall(userAgent, target,
                               false, true,
                               remoteRender, null);
            session.on('bye', function () {
                onCall = false;
                button.firstChild.nodeValue = 'video';
                session = null;
            });
        }
    });
}

//****************************

(function () {
if (SIP.WebRTC.isSupported()) {
    window.fromUA = createUA(fromURI, fromName);

    var registrationFailed = false;
    var failRegistration = function () {
        registrationFailed = true;
        failInterfaceSetup();
    };

    fromUA.on('registered', setupInterfaces);
    fromUA.on('registrationFailed', failRegistration);
    window.onunload = function () {
        fromUA.stop();
    };

    function setupInterfaces() {
        setUpVideoInterface(fromUA, toURI, 'video', 'video-button');
        setUpMessageInterface(fromUA, toURI, 'message-display', 'message-input', 'message-button');
    }
    function failInterfaceSetup() {
        alert('Max registration limit hit. The app is disabled.');
    }
}
})();

Для тех, кто будет устанавливать это на сервер и знаком с jinja2


Шаблон
<!DOCTYPE html>    
<html lang="ru">
  <head>
    <meta charset="utf-8">
    <title>{{fromName}}-tv</title>
    <script>
        var domain = 'sipjs.onsip.com';

        var token = '{{token}}';
        var d123 = new Date();
        d123.setTime(d123.getTime() + 1000*60*60); // expires in 1 hour
        document.cookie = ('onsipToken=' + token + ';' + 'expires=' + d123.toUTCString() + ';');

        var fromName = '{{fromName}}';
        var toName   = '{{toName}}';

        var fromURI  = fromName.toLowerCase() +  '.' + token + '@' + domain;
        var toURI    = toName.toLowerCase()   +  '.' + token + '@' + domain;
    </script>
    <script src="https://rawgit.com/onsip/SIP.js/0.7.5/dist/sip-0.7.5.js"></script>
    <script src="{{request.static_url('mydoctor:static/demo.js')}}"></script>
  </head>

  <body>    
    <div class="content">
        <div class="demo-window">
            <div class="left">
              <h4>{{title}}</h4>
              <h5>{{comment}}</h5>
            </div>
            <div class="demo-view">
              <video id="video" muted="muted"></video>
            </div>

            <button id="video-button" class="right" type="button">video</button>
            <div class="clearfix"></div>

            <div id="content-message">

                <div id="message-display">
                    <p class="message"><span class="message-from"></span> <span class="message-body placeholder">No messages yet</span></p>
                </div>
                <textarea id="message-input" class="message-input" placeholder="Enter your message here!"></textarea>
                <br>
                <button id="message-button" class="right" type="button">send message</button>
            </div>
        </div>
    </div>
</body>
</html>

и 2 питоновских словаря
_token = uuid.uuid4().hex
...
    {
        'title': 'Я Алиса',
        'comment': 'В окне доктор Боб',
        'fromName':'Alice',
        'toName':'Bob',
        'token': _token
    }
...
    {
        'title': 'Я доктор Боб',
        'comment': 'В окне вижу Алису',
        'fromName':'Bob',
        'toName':'Alice',
        'token': _token
    }

Уникальные расширения имен для Алисы и Боба сделает сервер при загрузке.


Я попытался установить свой SIP-сервер. Выбрал FreeSWITCH, день разбирался с настройками, но так и не смог настроить: голос поступал с задержкой в 5 (пять) секунд, видео зависало со странной записью в логе FS о том, что видео-формат не поддерживается.


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


Надеюсь, что какой-нибудь спец по SIP/STUN улыбнется, вколотит мне минус за неумейство и объяснит, какой SIP/STUN выбрать и как его настроить.

Tags:
Hubs:
+3
Comments10

Articles