新SkyWayでPublish / Subscribeをどう設計するか?実装でハマるポイントまとめ

新SkyWay(@skyway-sdk/room)は、WebRTCの通信を
Publish / Subscribeモデルで扱う設計になっています。

一見シンプルですが、この部分を正しく理解していないと、

  • 映像が一部のユーザーだけ見えない
  • 後から参加した人に映像が届かない
  • 再接続後に通信が壊れる

といった問題が起きやすくなります。

この記事では、Publish / Subscribeの基本と、実装でハマりやすいポイントを整理します。


Publish / Subscribeの基本

新SkyWayでは、通信は次の流れで行われます。

Stream
  ↓ publish
Publication
  ↓ subscribe
Subscription
  ↓
RemoteStream

重要なのは、

Streamを公開する側(Publish)と受信する側(Subscribe)が分かれている

という点です。


よくある最初の実装

最初に書きがちなコードはこんな感じです。

room.onStreamPublished.add(async (e) => {
  await member.subscribe(e.publication.id);
});

新しいStreamが来たらsubscribeする、というシンプルな実装です。

ただ、このままだと不具合が発生します。


この実装の問題点

問題は、

既に存在しているPublicationを拾えない

ことです。

onStreamPublished は、

  • 新しく公開されたStream

しか通知しません。

そのため、

  • 自分が後からRoomに参加した場合
  • 既に配信されている映像

これらは取得できません。

結果として、

  • 相手は配信しているのに見えない
  • 人によって見える・見えないが発生する

という状態になります。


正しいSubscribeの実装パターン

新SkyWayでは、Subscribeは必ず2段構えにします。

① 既存のPublicationをsubscribe

room.publications.forEach(pub => {
  if (pub.publisher.id !== member.id) {
    member.subscribe(pub.id);
  }
});

② 新規のPublicationをsubscribe

room.onStreamPublished.add(async (e) => {
  if (e.publication.publisher.id !== member.id) {
    await member.subscribe(e.publication.id);
  }
});

この2つをセットで実装するのが基本です。


なぜこの設計が必要なのか

新SkyWayでは、

  • PublicationはRoomに存在する
  • SubscriptionはMemberごとに作られる
  • 自動購読は行われない

という仕様になっています。

つまり、

「何を受信するか」はクライアント側が明示的に決める必要がある

という設計です。


自分のStreamをsubscribeしない

Subscribe処理では、必ずこの条件を入れます。

if (pub.publisher.id !== member.id)

これを入れないと、

  • 自分の映像を自分で再生する
  • 音声がループする

といった問題が発生します。


UIとSubscriptionはセットで管理する

もう一つ重要なのが、

UIの状態とSubscriptionを一致させる

ことです。

例えば、

  • 映像を非表示にした
  • 通話UIからユーザーを消した

といった場合、DOMを消すだけではなく、

subscription.unsubscribe();

まで行う必要があります。

これをやらないと、

  • 裏で受信し続ける
  • 帯域を無駄に使う
  • CPU負荷が上がる

といった問題につながります。


再接続時の挙動に注意する

新SkyWayでは、

  • Subscriptionは自動復元されない
  • Publicationも再利用されない

という前提があります。

そのため、

再接続後は必ずsubscribeをやり直す必要があります

この前提で設計しておくと、後から安定します。


実務で使える実装パターン

実際のプロジェクトでは、次のようにまとめておくと扱いやすいです。

function setupSubscriptions(room, member) {
  // 既存のPublication
  room.publications.forEach(pub => {
    if (pub.publisher.id !== member.id) {
      member.subscribe(pub.id);
    }
  });

  // 新規のPublication
  room.onStreamPublished.add(e => {
    if (e.publication.publisher.id !== member.id) {
      member.subscribe(e.publication.id);
    }
  });
}

これを

  • Room参加時
  • 再接続時

の両方で呼びます。


よくあるミス

実際にハマりやすいポイントをまとめると以下です。

  • onStreamPublishedだけで実装している
  • 既存Publicationをsubscribeしていない
  • unsubscribeしていない
  • 自分のStreamをsubscribeしている
  • 再接続時の処理を考慮していない

まとめ

新SkyWayでは、通信は

  • Publish(公開)
  • Subscribe(購読)

によって構成されます。

そのため、

受信処理は自動ではなく、自分で設計する必要がある

という前提になります。

特に重要なのはこの2つです。

  • subscribeは必ず2段構えにする
  • UIとSubscriptionを同期する

この2点を押さえておくと、
安定したリアルタイム通信が実装しやすくなります。