Видеочат WebRTC с Ajax вместо WebSocket: возможно?

Около шести месяцев назад мне удалось успешно закодировать собственный сервер WebSocket script в PHP. Благодаря этому мне удалось настроить службу видеочата WebRTC на моем локальном хосте. Я был очень доволен, пока не понял, что для его развертывания мне нужен веб-сервер, который дал мне доступ к сокетам.

К сожалению, ни один общий веб-хостинг не позволяет сокеты, а все веб-серверы, предлагающие сокеты, стоят дорого. Хотя это не эффективное решение в широких масштабах, для создания демонстрации для показа людей, я хочу изменить метод сигнализации с WebSocket на Ajax, чтобы я мог показать услугу видеочата WebRTC, которую я сделал.

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

В настоящий момент, когда один клиент подключается к script, я использую Ajax для отправки запроса на PHP скрипт, который проверяет наличие других активных пользователей в БД. Если нет, script затем создает предложение и помещает предложение в БД. После этого клиент проверяет отдельный PHP скрипт каждую секунду, чтобы проверить ответ от другого клиента, подключающегося к script.

Затем я подключаюсь к script другому клиенту, который запрашивает тот же PHP скрипт и DB, который затем понимает, что активный пользователь (первое соединение) уже отправил предложение, которое второе клиент получает и устанавливает для удаленного описания. Затем второй клиент создает ответ, который помещается в БД.

На этом этапе первый клиент (который опрос DB каждый раз) обнаруживает, что присутствует ответ, и устанавливает ответ в качестве удаленного описания для первого клиента. К сожалению, даже после успешного выполнения всего этого, клиентское видео не появляется.

Итак, здесь, когда я смущен и имею три (многочастных) вопроса:

1) Я подумал, что после того, как оба клиента установят свое локальное описание, а затем отправят это локальное описание другому клиенту, а другой клиентский набор, получивший описание в качестве удаленного описания, которое должно было запускать событие onaddstream, что позволяет мне отобразить удаленное видео. Однако этого не происходит. Это работало хорошо, прежде чем я использовал WebSocket, но он не работает вообще с чистым Ajax. Есть ли что-то особенное, чего я не хватает? За последние шесть месяцев изменилась спецификация WebRTC радикально? Я пробовал смотреть спецификации WebRTC, но я не вижу серьезных изменений.

2) После того, как я разочарован тем, что не работал с Ajax, я вернулся к своей версии WebSocket и загрузил его на свой локальный хост. Я вообще не изменил код с тех пор, как использовал его (который отлично работал шесть месяцев назад), но теперь, когда я пытаюсь его использовать, иногда он работает, а иногда и нет. Иногда я получаю ошибки, связанные с невозможностью установки локальных и/или удаленных описаний. Что с этим? Были ли изменения в спецификациях, которые могли бы привести к этому? В связи с этим, несмотря на то, что я не могу заставить удаленные видео всплывать с версией Ajax, я отдал много информации на консоль, и похоже, что и с Ajax-версией, иногда локальные и удаленные описания для обоих клиентов успешно настроены, а иногда возникают ошибки при попытке установить локальные/удаленные описания по любой причине, даже если я выполняю то же самое script каждый раз без каких-либо изменений. Я использую последнюю версию Chrome, и я начинаю задаваться вопросом, есть ли там ошибка или что-то в этом роде.

3) Требуется ли обработчик события onicecandidate для установления соединения? Мое предположение заключалось в том, что сверстники могли установить соединение с просто действительным предложением и ответом и что событие onicecandidate использовалось для предоставления альтернативных маршрутов и т.д., Что могло бы привести к лучшему соединению (но не обязательно). Я ошибаюсь? Если требуется информация onicecandidate, как вы рекомендуете, я обрабатываю это с помощью Ajax в качестве метода сигнализации?

Я знаю, что много информации и много вопросов, но любая информация/проницательность, которую может предложить любой, будет очень оценена. Я на мгновение ударился головой о стол, пытаясь понять это, и ничего не имеет смысла.

Ответ 1

Мой первый совет о вашем приложении. работая/не работая спорадически, это смотреть на текущие онлайн-реализации. На интернет-страницах есть множество демонстраций WebRTC.

AJAX

О AJAX: почему бы не работать? В настоящее время я работаю над тем же, что и вы, и он отлично работает каждый раз (я не могу раскрыть источник на данный момент). Клиенты постоянно посещают сервер через регулярные промежутки времени, и они могут отправлять SDP-описания/ICE-кандидаты на конкретный другой клиент таким образом. Сервер действует как простой мост (это является основой сигнализации).

Будь то WebSocket, AJAX или IPoAC, если вы передаете другому клиенту все, что ему нужно (и в нужный момент, подробнее об этом позже) он должен работать. Я даже сделал демоверсию, в которой вы вручную копируете/вставляете SDP-описание и ICE-кандидатов, используя текстовые области, и нажимаете на кнопки, чтобы двигаться вперед в процессе сигнализации, и это, конечно же, отлично работало.

ICE кандидаты

Теперь: да, вам нужны кандидаты ICE. Посмотрите на образец SDP предложение, который я только что сгенерировал с помощью createOffer на Chromium 27:

v=0
o=- 3866099361 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE audio video
a=msid-semantic: WMS 9kTlKaNe1exIs6JgEFYfXlu6E5f4B5R3I2D8
m=audio 1 RTP/SAVPF 111 103 104 0 8 107 106 105 13 126
c=IN IP4 0.0.0.0
a=rtcp:1 IN IP4 0.0.0.0
a=ice-ufrag:l8Qu31Vu4VG5YApS
a=ice-pwd:TpyQ5iESUH4HvYGE4ay8JUhe
a=ice-options:google-ice
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=sendrecv
a=mid:audio
a=rtcp-mux
a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:bC5YSe2xCmui0wSxUHWKIi9INbZ2y0VrO1swoZbl
a=rtpmap:111 opus/48000/2
a=fmtp:111 minptime=10
a=rtpmap:103 ISAC/16000
a=rtpmap:104 ISAC/32000
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:107 CN/48000
a=rtpmap:106 CN/32000
a=rtpmap:105 CN/16000
a=rtpmap:13 CN/8000
a=rtpmap:126 telephone-event/8000
a=maxptime:60
a=ssrc:1976175890 cname:/+lKYsttecoiyiu5
a=ssrc:1976175890 msid:9kTlKaNe1exIs6JgEFYfXlu6E5f4B5R3I2D8 9kTlKaNe1exIs6JgEFYfXlu6E5f4B5R3I2D8a0
a=ssrc:1976175890 mslabel:9kTlKaNe1exIs6JgEFYfXlu6E5f4B5R3I2D8
a=ssrc:1976175890 label:9kTlKaNe1exIs6JgEFYfXlu6E5f4B5R3I2D8a0
m=video 1 RTP/SAVPF 100 116 117
c=IN IP4 0.0.0.0
a=rtcp:1 IN IP4 0.0.0.0
a=ice-ufrag:l8Qu31Vu4VG5YApS
a=ice-pwd:TpyQ5iESUH4HvYGE4ay8JUhe
a=ice-options:google-ice
a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
a=sendrecv
a=mid:video
a=rtcp-mux
a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:bC5YSe2xCmui0wSxUHWKIi9INbZ2y0VrO1swoZbl
a=rtpmap:100 VP8/90000
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack 
a=rtpmap:116 red/90000
a=rtpmap:117 ulpfec/90000
a=ssrc:3452335690 cname:/+lKYsttecoiyiu5
a=ssrc:3452335690 msid:9kTlKaNe1exIs6JgEFYfXlu6E5f4B5R3I2D8 9kTlKaNe1exIs6JgEFYfXlu6E5f4B5R3I2D8v0
a=ssrc:3452335690 mslabel:9kTlKaNe1exIs6JgEFYfXlu6E5f4B5R3I2D8
a=ssrc:3452335690 label:9kTlKaNe1exIs6JgEFYfXlu6E5f4B5R3I2D8v0

Вы видите что-нибудь, что может помочь другому клиенту подключиться к моей машине? Я так не думаю. Цель всего этого механизма ICE состоит в том, чтобы собрать кандидатов на соединение (локальные, такие как 192.168.1.15, "общедоступные" (публичный IP, назначенный вашим интернет-провайдером), используя STUN, если вы находитесь за любым NAT, который не симметричен, или TURN для симметричные NAT).

После получения этих кандидатов ICE другой партнер закажет их с использованием определенных предопределенных показателей для определения приоритетов, а затем выдаст тесты подключения, чтобы найти хорошего кандидата. Поэтому, пожалуйста, поделитесь ими: другой партнер должен их (и вам тоже нужно).

Итак, вот некоторые из моих кандидатов в ICE:

a=candidate:303249700 1 udp 2113937151 192.168.50.238 43806 typ host generation 0
a=candidate:303249700 2 udp 2113937151 192.168.50.238 43806 typ host generation 0
a=candidate:1552991700 1 tcp 1509957375 192.168.50.238 35630 typ host generation 0

Теперь они являются конкретными (хотя и локальными, потому что я не настроил одноранговое соединение RTC с любыми URL-адресами STUN) для подключения другого однорангового узла к моей машине.

Советы по сигнализации WebRTC

В нижней части этой страницы есть несколько интересных советов. Я, честно говоря, не мог сказать вам прямо сейчас, почему вы должны следовать этим или почему они существуют в первую очередь, но я следовал им и не имел проблем с сигнализацией. Вот они:

  • Для ответчика: НИКОГДА не добавляйте кандидатов ICE, пока этот одноранговый узел не создает/не создает ответ SDP
  • Остановить добавление кандидатов ICE, когда пуск потока начнет течь
  • Не создавайте одноранговое соединение для ответчика, пока не получите предложение SDP

Вы можете управлять всем этим на стороне клиента, имея некоторый конечный компьютер WebRTC. Пожалуйста, см. Ссылку на страницу, чтобы понять, что он подразумевает при запуске удаленного потока.

Попробуйте поделиться кандидатами ICE и добавить их с противоположной стороны и, по крайней мере, следовать советам № 1 и № 3, и ваше приложение должно работать снова.

Сигнальная связь

Вы спросили, как передать кандидата ICE от партнера к другому в случае, если кандидаты ICE важны для обмена (какие они есть). Чтобы делиться вещами с помощью AJAX, независимо от того, что вы делаете, вы можете использовать почтовые ящики. Я считаю, что вы уже делаете это, разместив то, что требуется клиенту в базе данных.

Всякий раз, когда одноранговый узел должен отправить что-то другому, отправьте его как можно скорее (используя AJAX). На стороне сервера поместите эту "почту" в почтовый ящик целевого клиента. Когда сверстник (периодически) обследует сервер для новых писем, дайте ему все свои новые письма.

Когда создается предложение SDP, кандидаты ICE быстро генерируются. Все эти кандидаты ICE и описание SDP, вероятно, попадут в почтовый ящик назначения в течение нескольких миллисекунд. Есть хорошие шансы, что партнер-собеседник опросит все необходимое сразу. Даже если кандидат ICE прибывает поздно, его следующий опрос получит его.

Ответ 2

Это не отвечает на ваши вопросы, но для сервера сигнализации вы можете взглянуть на Socket.io (в Node). Я написал codelab, объяснив, как это установить: bitbucket.org/webrtc/codelab. Это очень просто - полный пример здесь: код сервера сигнализации составляет около 50 строк.

SimpleWebRTC запускает сервер Signalmaster, который использует Socket.io.

(Роберт Ниман написал хорошее сообщение , объясняющее это.)

Другой вариант - использовать XHR с API-интерфейсом Google, согласно apprtc.appspot.com пример: code здесь.

Ответ 3

Любой способ отправки сообщений должен работать одинаково. Это может помочь иметь в виду, что это всего лишь 4 основных сообщения, которые вы хотите обменять:

  • уведомление о присоединении партнера (или слева)
  • сообщение с предложением == > SetRemoteDescription с ним, затем выполните ответ и отправьте его
  • ответное сообщение === > SetRemoteDescription с ним
  • ледяной кандидат, отправленный из другого партнера == > вызов addIceCandidate с ним

Ледяной кандидат - это странная часть. Кроме того, объект-кандидат содержит забавные символы, поэтому, когда вы его отправляете, URI кодирует его. В Coffeescript моя выглядит примерно так:

peer_connection.onicecandidate = (e) ->
   send { 
          line_index: e.candidate.sdpMLineIndex
          candidate: encodeURIComponent(e.candidate.candidate) }

Ответ eepp - это хорошо, но он содержит некоторые советы, которые я считаю неправильными. В частности, эти 3 подсказки, я считаю, неверны:

  • Для ответчика: НИКОГДА не добавляйте кандидатов ICE до тех пор, пока этот партнер генерирует/создает ответ SDP
  • Прекратите добавлять кандидатов ICE, когда они удалены поток начинает течь
  • Не создавайте одноранговое соединение для ответчика до тех пор, пока вы получаете предложение SDP

Вот последовательность событий, которые я сегодня работаю (февраль 2014) в Chrome. Это для упрощенного случая, когда одноранговый узел будет передавать видео в одноранговое соединение.

  • Определите способ обмена сообщениями между сверстниками. (Различия в том, как люди это делают, - это то, что делает разные образцы кода WebRTC столь несоизмеримыми, к сожалению. Но мысленно и в вашей организации кода попытайтесь отделить эту логику от остальных.)
  • С каждой стороны настройте обработчики сообщений для важных сигнальных сообщений. Вы можете настроить их и оставить их. Существует 4 основных сообщения для обработки и отправки:
    • другой партнер присоединился
    • ледяной кандидат, отправленный с другой стороны == > вызов addIceCandidate с ним
    • сообщение с предложением == > SetRemoteDescription с ним, затем выполните ответ и отправьте его
    • ответное сообщение === > SetRemoteDescription с ним
  • С каждой стороны создайте новый объект peerconnection и присоедините к нему обработчики событий для важных событий: onicecandidate, onremovestream, onaddstream и т.д.
    • кандидат на льду === > отправьте его на другую сторону. Добавленный поток
    • === > присоединяет его к элементу видео, чтобы вы могли его видеть.
  • Когда присутствуют оба одноранговых узла и все обработчики находятся на своем месте, одноранговый узел получает какое-то триггерное сообщение для запуска видеозахвата (с помощью вызова getUserMedia)
  • Как только getUserMedia удастся, у нас есть поток. Вызовите addStream на одноранговый объект однорангового соединения.
  • Затем - и только тогда - одноранговое предложение 1 делает предложение
  • Из-за обработчиков, которые мы установили на шаге 2, peer 2 получает это и отправляет ответ
  • Одновременно с этим (и несколько неясно), объект подключения сверстников начинает создавать кандидатов на лед. Они отправляются туда и обратно между двумя сверстниками и обрабатываются (шаги 2 и 3 выше).
  • Потоковая передача начинается сама по себе, непрозрачно, в результате 2-х условий:
    • предложение/ответный обмен
    • Получены, обменены и добавлены ледяные кандидаты.

Я не нашел способ добавить видео после шага 9. Когда я хочу что-то изменить, я возвращаюсь к шагу 3.