ある中卒がWeb系エンジニアになるまでと、それからのこと

うつ病で高校を中退したり、たこ焼き屋のオヤジにホームページとたこ焼きを作らされたり、そのホームページが訴えられそうになったり、弁護士を目指したりした後にエンジニアになった人が書くブログ

vueプロジェクト内でskywayによるグループ通話を実装する

2020/07/01の日報

Hanasot開発の日報、第5日目です。
今日は

  • vueプロジェクト内でskywayによるグループ通話を実装する

を行いました。
時間としては、

  • Hanasot開発 1h3min
  • 日報ブログ引っ越し準備 1h44min
  • 日報ブログ 1h11min

です。
運動は

*ハーフ・ハンドスタンド・プッシュアップ 20reps → 20reps → 16reps

です。

vueプロジェクト内でskywayによるグループ通話を実装する

実はこれまだ終わっていません。昨日までの日報ではサクサクと進んで進捗らしい進捗を報告できていましたが、流石にそう毎日うまくはいきませんね。
ですので今日は、 「何をしようとしていて」⇒やりたいこと 「何でつまづいているのか」⇒ハマっている箇所 の報告です。

やりたいこと
必要なこと

昨日の時点でskywayを用いてビデオ通話を行うことはできていました。それから発展して今回は複数人によるグループ通話を実装しようと思っています。また、skywayに対してグループ通話を行いたいという旨のリクエストを送信して、グループ通話用のデータを返してもらうことはできていますから、必要なのは参加者分の動画を表示する機能ということになります。
vueはそういう要素を増やしたり消したりといった処理はとても得意なので、簡単にできるはずです。はずなんです。

動作フロー

動作フローはこんな感じです。

  1. 任意のチャットルームのIDを参加者Aが入力してskywayに送信する
    2.参加者BがAの決めたIDを入力してskywayに送信する
    3.IDがマッチするとskywayがオブジェクトを返すので、それを受け取る
    4.オブジェクトには自分以外の参加者のカメラを通した動画が格納されている(リアルタイムで更新される)
    5.その動画をhtmlのvideoタグのsrcに設定する
    6.ビデオ通話できるので楽しい
    7.参加者CがAの決めたIDを入力してskywayに送信する
    8.skywayがCを含めたオブジェクトを返すので、それを受け取る
    9.C用のvideoタグを追加する
    10.3人で通話できて楽しい
ハマっている箇所

動作フローでいうと5です。3人目の追加ができないよーっていうんじゃなくて2人目からハマってます。
もっというと2人だけでいいならできているんです。ただ3人目を見据えて、「参加者人数に合わせてvideoタグを生成し、それぞれにsrcを設定する」実装ができていません。

詳しくご説明していきます。まずはこういうvideoタグを作りました。

<video
      v-for="participant in participants"
      :key="participant.id"
      :id="participant.id"
      width="400px"
      autoplay muted playsinline></video>

v-forとkey属性はvueで使うもので、「participants配列の数だけvideoタグを作ってください。それとparticipantsの中身をparticipantとしてループ処理してください。生成したタグを見分けるためにparticipantのidを振ってください。」という意味になります。
これは多分問題ないです。

次にこんなふうにdataプロパティでparticipants配列を用意しました。

data() {
  return {
~~~~略~~~~
        participants: [],
      }

上で解説したtemplateで使うためですね。この配列に参加者idを詰めていけば、参加者の数だけvideoタグが生成されて、それぞれのvideoタグにidが振られるはずです。これも問題ないでしょう。

問題はこの処理です。

setEventListener: (mediaConnection) => {
  mediaConnection.on('stream', stream => {
   // video要素にカメラ映像をセットして再生
   Object.keys(mediaConnection.remoteStreams).forEach(async key => {
      const remoteStream = mediaConnection.remoteStreams[key];
      const remoteId = remoteStream.peerId;
      await this.participants.push(remoteId);
      const videoElm = document.getElementById(remoteId);
      videoElm.srcObject = remoteStream;
      videoElm.play();
    })
  });
}

ややこしいですが順に解説していきます。
あ、今回は省略していますがこのsetEventListenerという関数は着信を検知して呼び出されています。誰かがルームに入ったぞ、となったら呼び出されるわけですね。
まずは

 mediaConnection.on('stream', stream => {

の部分です。これは参加者の動画を受信した時に発火します。参加者が入室したことによりsetEventListenerが呼ばれて、その後動画もちゃんと送られてきたらこの中の処理に入っていくということです。

次にちょっとややこしい

Object.keys(mediaConnection.remoteStreams).forEach(async key => {

の部分です。
これは参加者情報(remoteStreams)を一人分ずつ処理するためにループで回しています。mapやforEachで書けばスッキリなのですが、skywayから返ってくるデータがオブジェクト型のため「keyを配列の形で取り出して、取り出したkey配列をforEachで回す」という処理を行わなければならないので、こんなややこしくなっちゃってるんですね。

そして

const remoteStream = mediaConnection.remoteStreams[key];
const remoteId = remoteStream.peerId;
await this.participants.push(remoteId);

これですね。上で解説したようにオブジェクトのkey配列をforEachで処理しているので、オブジェクト[key]とすることで目的の「参加者1人分のデータ」を取り出して定数に詰めています。
remoteStream.peerIdと記述することで参加者固有のIDを取り出します。それをvideoタグ生成のためのparticipantsに追加するわけです。したいんです。でもできないんです。ここが問題なので videoElm以下は割愛します。

arrow関数と普通の関数

本当の問題はmediaConnection.on('stream', stream => {にあります。これarrow関数っていうんですけど、ES6から実装された構文です。メリットはfunctionsって書くより短く書けることです。絶対それだけじゃないんですけど僕レベルではそれだけです。ただこれvueで扱うとき問題があって「宣言元のthisを参照する」んですよ。端的に言うとthis.hogehogeとしてもdataプロパティを参照できなくなります。
そう、つまり先程this.participants.pushとしていた箇所で、dataプロパティのparticipantsにアクセスできないんですね。うん。arrow関数やめればいいじゃん。試したっけ。試してないよ多分。

こんなしょーもないことでハマってたのか。明日ちゃんと普通の関数で試してみます。

所感

自分のやってることを整理するの大事

この記事を書く直前までarrow関数を使わねばならないものだと決めつけていました。実際普通にfunctionsと書けばそれで解決する問題なのに。
うーん。仕事でもこういうのやらかしてそうだなぁ。なんか「こうしなきゃダメだ!」って勝手に縛りを作っていて、その縛りを回避しようと無駄なコードを書くことある気がするんですよ。悩んだらちょっと頭を冷やしてやりたいことを整理するのが大事ですね。