ここで作っていたOSSでWebRTC-HTTP Ingestion Protocol(WHIP)のサポートを開始した.こちらは0.7.0から入っている.
WHIPというのはこういうやつ
WebRTC SFUだと,配信を開始するまでにシグナリングが必要で,SDPのやりとりを複数回双方向にやらなきゃいけなくて大変だよね,というのがある.しかも双方向なのでたいていWebSocketとか使わないといけないので,それもまた面倒なことではある.ので,HTTPだけでさくっと映像を送りつけるためにWHIPというのは存在するのだと概ね理解している.
で,プロトコルが決まっているのでだいたいそのとおりに実装するだけ……なのだがちょいちょい裏話を書いておく.
やることはWHIP用のエンドポイントを生やすだけ
WHIPが要求しているのはWebサーバでPOST/PATCH/DELETEのエンドポイントを生やすだけなのだが.その前に一つ.RheomeshはWebRTC SFU用のライブラリであり,これ自体がWebサーバを内包するようなライブラリではない.ExampleではもちろんWebサーバを使ったサンプルを乗せているが,あくまでもそれはユーザが好きなWebサーバを使ってシグナリングを実装する中で,SFUに関わるところだけRheomeshを呼ぶという前提にしてある(これはmediasoupなんかでも同じ).
なので,WHIPをやるにあたり,勝手にWebサーバを立てるという案は採用できない.あくまでエンドポイントを生やすためのメソッドを用意するだけで,Webサーバ自体はやっぱりユーザが建ててね,という建付けは変更していない.なので今回の方針としては,actixでWebサーバを立てるときに突っ込める設定を提供するところまでとしている.
追加されるエンドポイントは,
- POST /whip/{session_id} - メディアのSDPを受け取りanswerのSDPを返す
- PATCH /whip/{session_id} - Trickle ICEのSDPを受け取る
- DELETE /whip/{session_id} - WHIPのセッションを終わりにする
の3つだけ.session_idは,文字列であればなんでも良い.ここでいうセッションは,接続を管理するidでしかないので,同じユーザの同じ接続がちゃんと同じsession_idになっていれば問題ない. 例えば,一人のユーザは絶対に一つのミーティングルームにしか入らない(一人で複数のミーティングに同時接続できない)という制約があるのであれば,別にuser_idでも構わない.
基本的にはセッションごとにTransportを作って管理するので,セッションとTransportの対応付けをやってもらう必要がある.これは,WHIPであってもTransportの作成タイミングはPOSTよりも前になるために,ユーザ側で管理してもらう必要がある.
struct SessionStore { sessions: Arc<Mutex<HashMap<String, Session>>> } struct Session { session_id: String, // Or user_id. publish_transport: Arc<PublishTransport> } #[async_trait] impl PublishTransportProvider for SessionStore { async fn get_publish_transport( &self, session_id: &str, ) -> Result<Arc<PublishTransport>, actix_web::Error> { let sessions = self.sessions.lock().await; if let Some(session) = sessions.get(session_id) { let p = session.publish_transport.clone(); Ok(p) } else { Err(actix_web::error::ErrorNotFound("Session not found")) } } }
こんな感じで,PublishTransportProviderを実装してもらう必要がある.
使い方
actix-webを使っている場合は,endpointを突っ込むだけだ.
#[actix_web::main] async fn main() -> std::io::Result<()> { let store = SessionStore { sessions: Arc::new(Mutex::new(HashMap::new())), }; let store_data = Data::new(store.clone()); let endpoint = WhipEndpoint::new(store); HttpServer::new(move || { App::new() .app_data(store_data.clone()) .configure(|cfg| { endpoint.clone().configure(cfg); }) }) .bind("0.0.0.0:4000")? .run() .await }
これで前述の3つのエンドポイントが自動的に生える.
WHIPはサーバ側のシグナリングのプロトコルの話だけであり,クライアントがどのように動くべきかを特に規定していないのだが,一応動作確認のためにもサンプルを作ってある.
https://github.com/h3poteto/rheomesh/blob/master/client/example/whip/src/pages/room.tsx
こんな感じでSDPを作って送りつければ良いと思う.
SDPはRheomeshのクライアントでも,RTCPeerConnectionからでも簡単に手に入るとは思うが,TrickleICE用の,candidateだけを含んだSDPを生成するのがちょっと面倒だった.
actix-web以外のサーバは?
これは将来的にサポートしようとは思っている.ただ,どうしてもエンドポイントの設定が入る都合上,あんまり汎用的にはできないと思っていて,一つずつかなぁ.今流行りなのはaxumとかかね.どれからいけるかわからんけど,エンドポイントの設定を流し込めるようなインターフェースがあれば,サポートしていけるとは思う.
WHEPは?
今回,WHIPはクライアントからサーバにメディアを送りつけるだけのシグナリングだった.SFUサーバである以上受信側のことも考えてほしくて,それはWebRTC -HTTP Egress Protocol(WHEP)というのがだ,こちらはまだサポートしていない.そのうちやるつもりではあるけど,今回はWHIPだけ先に実装したというだけ.