AWS MediaServices配信映像をA-Frame上に再生してみた

デジタルオフィスというお題で、それぞれのリモートワークを仮想的なオフィス空間に表示することに取り組んでいます。

ざっとですが以下のようなイメージです。
今後、複数のリモート会議のブースが並ぶ大規模会場が出てくるかと思います。そういった複数のリモート会議も外から覗いたような俯瞰的に見られれば、どのブースに入ろうか、というのがわかりやすいかな、という感じです。（多めの参加者をブレークアウトルームに分けるオンライン飲み会とかにも活用できないかな、と思ってみたりしています。）
![zoom可視化.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/943678/5dcf56e4-9ede-9fee-7d86-3e00e2d4e99b.jpeg)

この中で、Zoom会議の映像をAWS MediaLiveに配信し、A-Frameの仮想空間上のオブジェクトに表示することを試してみましたので、その内容を紹介させていただきます。
（video要素を[A-Frameオブジェクト(今回はa-planeを使用)のsrc](https://aframe.io/docs/1.2.0/primitives/a-plane.html#attributes_src)に指定するだけです。すでにご存じですかね。。。）


以下のgifアニメーションイメージのような感じで、映像がオブジェクトのテクスチャのように再生されるようになります。(縦横比が実際の映像と合っていないですがご了承ください)
![a-frame_video.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/943678/d66d1b7b-a3d9-7f9d-d7bf-f557b9ba911f.gif)


前提として、以下がありますので、ご注意ください。
 * 動作確認のブラウザは、Google Chromeを使いました。
 * 2021年9月頃に調べたり動作確認した内容です。


以下の各章に、順を追って流れを説明していきます。


#1. Zoom会議映像をAWS MediaServicesを活用して配信

AWS MediaServices内のMediaPackageとMediaLiveを使います。
[OBS Studio](https://obsproject.com/ja)を使って画面表示等をAWS MediaPackageに配信します。
送り込んだ映像は、AWS MediaLiveのEndpointにて複数のユーザ向けに配信できます。

すでにわかりやすい記事がありましたので、そちらを参照していただければと思います。

・[OBS Studio から クラウド上の AWS Media Services への接続](https://aws.amazon.com/jp/blogs/news/connecting-obs-studio-to-aws-media-services-in-the-cloud/)
・[OBSとAWS Elemental MediaLiveでライブ配信をしてみた](https://dev.classmethod.jp/articles/live-aws-elemental-medialive-with-obs/)



#2. AWS MediaServicesへの映像配信をA-Frameのオブジェクトに表示

[hls.js](https://hls-js.netlify.app/demo/)等を使えば、AWS MediaLiveの配信映像を表示することが可能でした。
まず、最初に試したのは、仮想空間ではなく、表に並べて表示することでした。以下のような感じになりました。
![2d_table.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/943678/c89623fa-d5b3-1c99-90b3-538e3805cb9f.jpeg)


3DのVRに表示させるには、A-Frameのsrc属性に、このvideo要素のidを「#」付きで指定(例：src="#xxx_id")すればよいようです。

グーグル検索すると、以下のリンク先の[ソースコード(github)](https://github.com/drnoir/Streaming-Video-textures-and-Webcam-Feed-with-A-Frame)に例がありましたので、参考にさせてもらい試してみました。（Live versionは、私のPC環境からはアクセスできなかったです）
[Streaming Video Textures and Webcam Feed with A-Frame - WebVR](
https://dev.to/drnoir/streaming-video-textures-and-webcam-feed-with-a-frame-webvr-146n)



#3. ソースの紹介

今回検証したコードは、以下のようになりました。

<details open="false"><summary>コードサンプル：AWS MediaLive映像A-Frame表示ソース
（この行をクリックするとソースコードが表示されます）</summary>
<div>
<pre><code>
&lt;html&gt;
&lt;head&gt;
  &lt;script src="https://cdn.jsdelivr.net/gh/aframevr/aframe@v1.2.0/dist/aframe-master.min.js"&gt;&lt;/script&gt;
  &lt;script src="https://cdn.jsdelivr.net/npm/hls.js@latest"&gt;&lt;/script&gt;
&lt;/head&gt;
&lt;body style="background-color: black; color:white;"&gt;
  &lt;a-scene&gt;
    &lt;a-assets&gt;
      &lt;video id="video1" autoplay loop="true" src="https://192.168.0.100:8000/data/video1.mp4" crossorigin="anonymous"&gt;&lt;/video&gt;
      &lt;video id="camera1" crossorigin="anonymous"&gt;&lt;/video&gt;
      &lt;video id="mediaservice1" autoplay crossorigin="anonymous"&gt;&lt;/video&gt;
    &lt;/a-assets&gt;
    &lt;a-plane src="#video1" position="-3 0 -5" rotation="0 0 0" width="3" height="3" &gt;&lt;/a-plane&gt;
    &lt;a-plane src="#camera1" position="3 0 -5" rotation="0 0 0" width="3" height="3" &gt;&lt;/a-plane&gt;
    &lt;a-plane src="#mediaservice1" position="0 3 -5" rotation="0 0 0" width="3" height="3" &gt;&lt;/a-plane&gt;
    &lt;a-camera id="camera" cursor="rayOrigin: mouse;" position="0 0.6 2"&gt;&lt;/a-camera&gt;
    &lt;a-tetrahedron id="icon_play" radius="1" position="-1 0 -5" rotation="45 0 0" color="#0F0"&gt;&lt;/a-tetrahedron&gt;
    &lt;a-box id="icon_stop" width="1" height="1" depth="1" position="1 0 -5" rotation="0 0 0" color="#F00"&gt;&lt;/a-box&gt;
  &lt;/a-scene&gt;
  &lt;script&gt;
    // video
    function play_video(video_id) {
      const video = document.getElementById(video_id);
      video.play();
    }
    function stop_video(video_id) {
      const video = document.getElementById(video_id);
      video.pause();
      video.currentTime = 0;
    }
    // camera
    function play_camera(camera_id) {
      const camera = document.getElementById(camera_id);
      navigator.mediaDevices.getUserMedia({audio: false, video: true})
      .then(stream =&gt; {
        camera.srcObject = stream
        camera.onloadedmetadata = () =&gt; {
          camera.play()
        }
      })
    }
    function stop_camera(camera_id) {
      const camera = document.getElementById(camera_id);
      let stream = camera.srcObject;
      if (stream == null) {
        return;
      }
      let tracks = stream.getTracks();
      tracks.forEach(function(track) {
        track.stop();
      });
      camera.srcObject = null;
    }
    // media service
    function play_mediaservice(media_id, media_url) {
      const media_element = document.getElementById(media_id);
      if (media_url.includes(".m3u8") == true) {
        if(Hls.isSupported() == true) {
          const hls = new Hls();
          hls.loadSource(media_url);
          hls.attachMedia(media_element);
          hls.on(Hls.Events.MEDIA_ATTACHED, function () {
            //音声ありと自動再生はどちらも設定するとうまくいかない
            //media_element.muted = true;
            //media_element.play();
            //setTimeout(function(){no_mute_media(media_element)}, 3000);
          });
          //setTimeout(function(){play_media(media_element)}, 3000); // error ユーザが操作して再生させないといけない?
        } else {
          console.log("Hls is not support, cannot play media: " + media_url);
        }
      } else {
        media_element.src = media_url;
      }
    }
    function stop_mediaservice(media_id) {
      const media_element = document.getElementById(media_id);
      media_element.pause();
    }
    function play_media(media) {
      media.play();
    }
    function no_mute_media(media) {
      media.muted = false;
    }
    // control
    var video_id = "video1";
    var camera_id = "camera1";
    var mediaservice_id = "mediaservice1";
    var mediaservice_url = "https://xxx.mediapackage.ap-northeast-1.amazonaws.com/out/v1/yyy/index.m3u8";
    var is_play = false;
    function play() {
      if (is_play == true) {
        return;
      }
      play_video(video_id);
      play_camera(camera_id);
      play_mediaservice(mediaservice_id, mediaservice_url);
      is_play = true;
    }
    function stop() {
      if (is_play == false) {
        return;
      }
      stop_video(video_id);
      stop_camera(camera_id);
      stop_mediaservice(mediaservice_id);
      is_play = false;
    }
    // a-frame control set
    var icon_play = document.getElementById("icon_play");
    var icon_stop = document.getElementById("icon_stop");
    icon_play.addEventListener('click', function (event) {
      play();
    });
    icon_stop.addEventListener('click', function (event) {
      stop();
    });
  &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
</div>
</details>

いくつか補足しますが、
動画の自動再生（HTMLロード完了と同時に再生させること）はうまくできない、という情報がインターネット上にあったので、三角（a-tetrahedron）と四角（a-box）のオブジェクトを用意して、三角をクリックすると再生を開始し、四角をクリックすると停止する。という操作オブジェクトを入れてみました。

AWS MediaPackage（video id="mediaservice1"やa-plane src="#mediaservice1"）以外に、
ビデオファイル（video id="video1"やa-plane src="#video1"）やカメラ映像（video id="camera1"やa-plane src="#camera1"）の表示も試したので、ソース内に一緒に入っています。

あと、現状、終了処理があまいので、MediaServiceを停止して再生させると、再生は行われますがエラーが発生してしまいます。



#4. さいごに

A-Frameはブラウザで簡単にVR表示ができるのでお手軽ですが、姿勢連携や色々な機能を詰め込んでいくと処理が結構重たくなってきました。
ある程度やりたい機能連携ができたら、ユースケースに有効な機能や粒度（表示する映像の数や人の数等）をしぼれば、スマートフォンでも使える範囲に落とし込めるかとは思うので、ターゲットに合わせて結合しようと思います。

あと、リモートワーク向けのVR表示はどんどん新しいシステムが出てきているので、今後の動向が気になります。
同僚の方に教えてもらったのですが、以下のようなすごそうなVRシステムも出てきているので、今後身近なVRシステムが自分の職場に導入される日も近いのかもしれません。
・[「Horizon Workrooms」を発表：リモートでの共同作業を再構築](https://about.fb.com/ja/news/2021/08/horizon-workrooms/)
・[Facebookが火をつけた「メタバース革命」は、スマホの次の時代の扉を開くか](https://news.yahoo.co.jp/byline/tokurikimotohiko/20210829-00255514?s=09)