はじめに
ビデオチャット機能を実装したいとの要望があり、 WebRTC について調べました。
WebRTC はとても面白く、魅力的な技術でしたが、同時にとても手強かったため、忘備録&記憶の整理用この記事を書きました。
同じ頃に Swift製 WebFrameWork である Vapor に興味が沸き、「WebRTC のサーバーサイドを Vapor で書けば一度に両方の技術を取得できるのでは?」と思い、WebRTC 用のシグナリングサーバーを Vapor で作成しました。
作成した Swift 製のシグナリングサーバーはこちらです。
https://simple-video-chat.work/
4文字以上の任意のルーム名を URL の後ろに入力し iOS 以外の異なるブラウザ同士でアクセスするとビデオチャットが始まります。
Ex: https://simple-video-chat.work/xxxx
勉強用に作成したため、いつまで残すかわかりません。ソースコードはこちらです。
O-Junpei/simple-video-chat-server
ハンズオンで WebRTC の概要を理解する
@massie_g さんと @yusuke84 さんのハンズオンが素晴らしいのため、まず最初に取り掛かる事をお勧めします。
ブラウザ間でビデオチャットが表示されると感動します。
WebRTCハンズオン 概要編
https://qiita.com/massie_g/items/916694413353a3293f73
WebRTCハンズオン 準備編
https://qiita.com/massie_g/items/6141a1020dd8bdf6574e
WebRTCハンズオン 本編
https://qiita.com/yusuke84/items/43a20e3b6c78ae9a8f6c
シグナリングサーバーを作成する
(上記ハンズオンに目を通した前提で進めます。)
WebRTCハンズオン本編 STEP3 で作られているようなシグナリングサーバーを Vapor で実装します。
シグナリングサーバーは通信相手の情報を交換する役割、ちょうどブラウザ同士の電話番号(SDPと呼ばれます)を交換するイメージです。
今回は同じルーム(URL)にアクセスしている相手にSDPを転送するシグナリングサーバーを作成しました。
var websocketClients: [String: [WebSocket]] = [:]
// WebSockets
let wss = NIOWebSocketServer.default()
wss.get("socket", String.parameter) { ws, req in
let room = try req.parameters.next(String.self)
// roomがなければ初期化、既にある場合はcloseを削除
if websocketClients[room] == nil {
websocketClients[room] = []
} else {
websocketClients[room] = websocketClients[room]!.filter({
!$0.isClosed
})
}
websocketClients[room]!.append(ws)
ws.onText { ws, text in
for client in websocketClients[room]! {
if client.isClosed {
return
}
if ws === client {
print("slip sender")
} else {
// roomが一緒
client.send(text)
}
}
}
}
services.register(wss, as: WebSocketServer.self)
参考: Vapod Docs Using WebSockets
参考: VaporでWebSocketを使ったチャットアプリを作る
HTML ページの生成
シグナリングサーバーができたので、次はフロント(HTML)をのレンダリング機能を作成します。
routes.swift
は Rails における config/routes.rb
で、ルーティングの設定を行うことができます。
4文字以上のルーム名が入力された場合はビデオチャットページ index.html
へ、それ以外は Vapor のデフォルトページ welcome.heml
をレンダリングしています。
index.html
の中身(JS)はハンズオンからお借りしました。
import Vapor
/// Register your application's routes here.
public func routes(_ router: Router) throws {
// Top
router.get { req in
return try req.view().render("welcome")
}
// room
router.get("/", String.parameter) { req -> Future<View> in
let room = try req.parameters.next(String.self)
if room.count < 4 {
return try req.view().render("welcome")
}
return try req.view().render("index")
}
}
デプロイ
作成した Vapor のアプリケーションを Docker化し、GCPのCompute Engin にデプロイしました。
以下の構成になっています。
以下のコマンドでローカルでも実行できます。
$ docker run -d -p 80:80 kabigon/simple-video-chat:latest
完成
Android のChorme と PC の Chorme で繋げています。
早く知りたかったこと、詰まったこと
シグナリングサーバー(WebSocket)のデバックがしたい
wsta は シンプルな WebSocket クライアントです。
HomeBrew で簡単にインストールすることができ、シグナリングサーバーのデバッグに役立ちます。
$ brew tap esphen/wsta https://github.com/esphen/wsta.git
$ brew install wsta
ターミナルを2つ開き、WebSocket で接続する。
片方に入力した文字が転送される。
$ wsta wss://simple-video-chat.work/socket/xxxx // or
$ wsta ws://localhost:8080/socket/xxxx
WebRTC は localhost 以外はSSL 必須
WebRTC を使うには SSLが必要です。
オンラインで使う場合はlet's encrypt などでSSL化してあげましょう。
WebRTC のデバッグをしたい
chrome://webrtc-internals/
と打ち込むとWebTRTC のデバッグができます。
参考: WebRTCデバッグ入門
NginxによるリバースプロキシとWebSocketを同時に使用する時は設定が必要
WebSocketのハンドシェイク時に使われる Upgrade ヘッダと Connection ヘッダ は Hop-by-Hop ヘッダ である。 通常のEnd-to-End headerと異なり基本的に一度の転送に対して有効なヘッダなため、ReverseProxyを間に挟んでいる場合これらのheaderはバックエンドサーバまで届かない。
Nginxによるリバースプロキシ + WebSocket を使う時は注意が必要です。
nginx.conf ファイルに以下を追記します。
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
参考: Nginxを用いたWebSocketサーバのReverseProxy構成及びSSL/TLS接続
iOS のブラウザ(Safari & Chorme)では WebRTCは使えない場合がある
制限が多く、思ったように動かない場合が多いため注意が必要です。
(追記: Safari 12.1 で映像コーデック VP8 に対応することで、 WebRTC は主要ブラウザで問題なく利用することができるようになる。とのことです。)
参考: Safari と WebRTC について
参考: WebRTC の今
iOS アプリでWebRTC を使ったビデオチャットアプリを作りたい
iOS アプリのブラウザ(WebView)では制限が多いため、WebRTC に対応したブラウザを CocoaPods などでインストールする必要があります。
以下の記事がとても良かったのでおすすめです。
SwiftでWebRTC実装ハンズオン 事前準備編
https://qiita.com/tnoho/items/3b94371e59fe8ad6ce03
SwiftでWebRTC実装ハンズオン 本編
https://qiita.com/tnoho/items/f5afa3ba749eed9b9716
ただ、上記記事のソースコードは Swift3 で書かれており、少し古くなってしまっているので、Swift4.2 で書き直しました。
近いうちにこの記事の続きとして公開します。
やっぱり辛い逃げよう
WebRTCを自前で実装すると大変です。