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

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

vue + skywayでミュート・カメラオフ機能を実装する

2020/07/06の日報

Hanasot開発の日報、第10日目です。
やったことは

  • vue + skywayでミュート・カメラオフ機能を実装する

です。
時間は、

  • Hanasot開発 1h26min
  • 日報ブログ 1h

です。
運動は

  • レバー・プッシュアップ 左右17repsずつ→左右12repsずつ→左右13repsずつ

です。

vue + skywayでミュート・カメラオフ機能を実装する

ちょっと時間かかりましたが割とサクッとできました。読み込み時は下の画像のように

  • カメラオン
  • マイクオフ

の状態です。

f:id:chusotsuengineer:20200706220753p:plain
カメラオン・マイクオフ(ちなみにトトロの方はiPadです)

このマイクボタンとカメラボタンをクリックすることによってオンオフが切り替わります。

f:id:chusotsuengineer:20200706220852p:plain
マイクオン・カメラオフ(相手の方でも黒い画面になっています)
f:id:chusotsuengineer:20200706220943p:plain
マイクオン・カメラオン

で、今回追加したソースコードはこちらです。

<template>
  <div>
    <div class="c-video">
      <video id="my-video" width="400px" autoplay muted playsinline></video>
      <div class="c-video__mic" @click="isMuted = !isMuted">
        <img :src="require(isMuted ? '../assets/mic_off.svg' : '../assets/mic.svg')">
      </div>
      <div
        class="c-video__camera"
        @click="isCameraOff = !isCameraOff">
        <img :src="require(isCameraOff ? '../assets/camera_off.svg' : '../assets/camera.svg')">
      </div>
    </div>
~~~~中略~~~~
    data() {
      return {
        localStream: null,
        roomID: '',
        peer: {},
        participants: [],
        isMuted: true,
        isCameraOff: false,
      }
    },
    watch: {
      isCameraOff(newValue) {
        const stream = this.localStream
        stream.getVideoTracks()[0].enabled = !newValue;
        this.localStream = stream;
      },
      isMuted(newValue) {
        const stream = this.localStream
        stream.getAudioTracks()[0].enabled = !newValue;
        this.localStream = stream;
      },
    },
    mounted() {
      // カメラ映像取得
      navigator.mediaDevices.getUserMedia({video: true, audio: true})
        .then( stream => {
          stream.getAudioTracks()[0].enabled = !this.isMuted;
          stream.getVideoTracks()[0].enabled = !this.isCameraOff;
          // 成功時にvideo要素にカメラ映像をセットし、再生
          const videoElm = document.getElementById('my-video')
          videoElm.srcObject = stream;
          videoElm.play();
          // 着信時に相手にカメラ映像を返せるように、グローバル変数に保存しておく
          this.localStream = stream;
~~~~中略~~~~

順番に解説していきます。

マイク・カメラボタンがクリックされたら画像を切り替える

この部分ですね。

<div class="c-video__mic" @click="isMuted = !isMuted">
  <img :src="require(isMuted ? '../assets/mic_off.svg' : '../assets/mic.svg')">
</div>

これはマイクですが、カメラもほぼ一緒です。 重要なのは@click="isMuted = !isMuted":src="require(isMuted ? '../assets/mic_off.svg' : '../assets/mic.svg')"になります。前者がv-on、後者はv-bindと呼ばれる機能です。

クリックなどのイベントを検知するv-on

v-on:click=""を省略して書くと@click=""となります。コード量も少なくわかりやすいので基本的にこちらを使うことがほとんどです。今回は@click="isMuted = !isMuted"と記述しています。これは「ボタンがクリックされたときに、dataプロパティで定義しているisMuted = true```を trueならfalse に、 falseならtrue に反転させてください」という意味です。

htmlの属性にjsの処理を書けるv-bind

そしてその下のimgタグのsrc属性には:src="require(isMuted ? '../assets/mic_off.svg' : '../assets/mic.svg')"とあります。これは「isMutedがtrueならマイクの画像を、falseならマイクに斜線の入った画像を表示してください」という意味ですね。v-bindの時はレンダリングの関係?でrequireでパスを指定しないと適切なパスが出力されないので注意。あ、v-bind:src=""の省略は:src=""で、src以外にも:class=""とかもできます。属性ならなんでもOKです。

これらを使って、ボタンの表示を切り替えてます。ここまではUIの処理です。

isMutedとisCameraOffを監視して、カメラとマイクの使用許可を切り替える

次に本題のボタンの真偽値を監視して、カメラとマイクのオンオフ切り替えを行う処理ですね。watchプロパティを使います。

    watch: {
      isCameraOff(newValue) {
        const stream = this.localStream
        stream.getVideoTracks()[0].enabled = !newValue;
        this.localStream = stream;
      },
      isMuted(newValue) {
        const stream = this.localStream
        stream.getAudioTracks()[0].enabled = !newValue;
        this.localStream = stream;
      },
    },

これなにをしているかというと、「関数名と同名のdataプロパティを監視して、値が切り替わったら関数を実行する」というものです。今回は「isMutedを監視して、真偽値が切り替わったら自分の映像を格納しているlocalStreamを取ってきて、マイクの許可をオンオフする」という処理をしてます。カメラも同じですね。

getAudioTracks()[0].enabledという部分はデバイス使用の許可を表します。trueならマイクの使用可能、falseならマイクの使用不可です。これはskywayの機能ではなく、ブラウザのデフォルトの機能になります。映像や音声の送信を止めたりするというよりは、カメラやマイクが使えなくなって結果的にミュートになったりカメラオフになったりするというわけですね。

ちなみにmountedで

          stream.getAudioTracks()[0].enabled = !this.isMuted;
          stream.getVideoTracks()[0].enabled = !this.isCameraOff;

このようにenabledを定義しています。これをしなくてもオンオフ切り替えはできるような気がするんですが、初期値が不許可の場合に限り上手く動きませんでした。ですので、dataプロパティの値を画面読み込み時に一応定義しておくのが無難かと思います。

所感

結構進みましたねー。いよいよそれらしくなってきた感じがします!まだまだ先は長いですが、「お題を出す機能」と「発言者を一人に限定する機能」さえ作ればアイスブレイクくらいはすぐ実装できそうです。がんばります!