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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  1. уведомление о том, что одноранговый узел присоединился (или оставил)
  2. сообщение о предложении ==> SetRemoteDescription с ним, затем выполните ответ и отправьте его
  3. SetRemoteDescription сообщение ===> SetRemoteDescription с ним
  4. ледяной кандидат, отправленный из другого партнера ==> вызов 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. Это для упрощенного случая, когда одноранговый узел будет передавать видео в одноранговое соединение 2.

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

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