2023年07月20日

WebGL(by Unity)×Agora Web SDKでウォッチパーティの開発

WebGL(by Unity)×Agora Web SDKでウォッチパーティの開発

ブラウザ用メタバースアプリ開発でよく利用されるWebGLとAgora Web SDKを組み合わせて音声通話+ライブ映像視聴のモックを作ったのでこの記事で紹介します。
WebGL側からJavaScriptの呼び出し、JavaScript側からWebGLへの呼び出し、UnityでのWebView表示といった技術の解説がメインになります。

お役立ち資料ダウンロード

オンライン体験におけるブイキューブの技術サポートのご案内

【図解】システム開発のお手伝い

ブイキューブのソリューションアーキテクトが、寄り添います!
各種ライブ配信システムのアーキテクチャについて わかりやすい構成図にてご紹介!

無料ダウンロード

開発環境

・macOS 13.4.1
・Unity 2021.20f1
・Visual Studio Code
・Agora Web SDK 4.16.1

実際のモックアプリ画面

実際のモックアプリ画面

Joinボタンをクリックすると友人等と通話開始、ViewLiveをクリックするとライブ映像を視聴するシンプルなアプリになります。
他拠点の入室があればそのユーザーIDがInformationに表示されます。
ライブ映像は別途Webページをホスティングし、WebViewでレンダリングを行っています。(JavaScriptとWebGLアプリとの通信はテキストデータのみの連携になっている為)

 

利用する技術

グラフィック部分(WebGL)

Unityで作成します。ButtonオブジェクトとTextオブジェクトを配置しました。

通話部分(JavaScript)

Agora Web SDKを使ってJavaScriptで実装しています。

グラフィックと通話の連携

WebGL側からJavaScriptへの呼び出しはjslib経由で行います。
JavaScript側からWebGLへの呼び出しはunityInstance.SendMessage()で行います。
詳細はこちらのUnityのドキュメントをご参照ください。

Unity用のWebView

グリー社から公開していただいているunity-webviewというアセットを利用させて頂きました。

実装の詳細

WebGL側からJavaScriptへの呼び出し

ButtonオブジェクトにはC#スクリプトを割り当てています。クリック時にJavaScriptを呼び出すようにしています。
jslibを利用する為にAssets/Plugins以下にrtc.jslibファイルを作成しました。

  Assets/Plugins/rtc.jslib

  mergeInto(LibraryManager.library, {
    CallExternalJS: function () {
      join();//JavaScript側で定義した関数
    },
  });  

C#スクリプト側の実装は以下の通り。

ButtionScript.cs

  using System.Collections; 
  using System.Collections.Generic; 
  using UnityEngine; 
  using UnityEngine.UI; 
  using System.Runtime.InteropServices; 
  public class ButtonScript : MonoBehaviour 
  {
    [DllImport("__Internal")] 
    private static extern void CallExternalJS();
    Button button; 
    void Start() {
      button = GetComponent<Button>(); 
      button.onClick.AddListener(Join); 
    }
    // 入室処理
    public void Join()
    { 
      CallExternalJS();
    }
  }
  

この段階で、UnityエディタでWebGLアプリケーションとしてビルドします。

 

次にJavaScript側の実装です。AgoraのSDKに含まれているサンプルをほぼ利用します。

rtc.js

  var client = AgoraRTC.createClient({ mode: "rtc", codec: "vp8" }); 
  var localTracks = { audioTrack: null }; 
  var remoteUsers = {}; 
  var options = { appid: YOUR APP ID, channel: YOUR CHANNEL ID, uid: null, token: null }; 
  
  async function join() { 
    client.on("user-published", handleUserPublished);
    client.on("user-unpublished", handleUserUnpublished); 
    [ options.uid, localTracks.audioTrack] = await Promise.all([ 
      client.join(options.appid, options.channel, options.token || null),
      AgoraRTC.createMicrophoneAudioTrack() ]); 
      await client.publish(Object.values(localTracks)); 
  }
  
  async function subscribe(user, mediaType) {
    const uid = user.uid; 
    await client.subscribe(user, mediaType); 
    if (mediaType === 'audio') { user.audioTrack.play(); } 
  }
  
  function handleUserPublished(user, mediaType) {
    const id = user.uid;
    remoteUsers[id] = user;
    subscribe(user, mediaType);
  }
  
  function handleUserUnpublished(user) {
    const id = user.uid; delete remoteUsers[id]; 
  }
  

WebGL側からJavaScriptの呼び出しはこれで完了です。ButtonクリックでAgoraの音声通話に参加できます。

JavaScript側からWebGLへの呼び出し

他拠点のユーザーが入室した事を知らせる為にメッセージをWebGL内に表示させます。
まずは、unityInstance.SendMessage()を利用するためにindex.htmlを改修します。このHTMLファイルはUnityからビルドされた物です。

index.html

  var myGameInstance = null; //追記 
  var script = document.createElement("script"); 
  script.src = loaderUrl; script.onload = () => { 
    createUnityInstance(canvas, config, (progress) => {
      progressBarFull.style.width = 100 * progress + "%"; 
    }).then((unityInstance) => {
      myGameInstance = unityInstance; //追記
      loadingBar.style.display = "none"; 
      fullscreenButton.onclick = () => {
        unityInstance.SetFullscreen(1); 
      }; 
    }).catch((message) => { 
      alert(message); 
    });
  };
  

 

次にAgora Web SDKで他拠点入室があった際のイベントにWebGLの呼び出しを実装します。

rtc.js

  async function subscribe(user, mediaType) {
    const uid = user.uid;
    await client.subscribe(user, mediaType);
    if (mediaType === 'audio') {
      user.audioTrack.play();
    } 
    var message = "User join:"+user.uid;//追記
    callWebGL(message);//追記
  }
  function callWebGL(message) {//追記
    myGameInstance.SendMessage('Text', 'SetText', message);
  }
  

 

次にUnityのC#スクリプト側の実装です。
Textオブジェクトに以下のスクリプトを割り当ててます。テキストを更新するだけのシンプルな実装です。

using System.Collections;
  using System.Collections.Generic;
  using UnityEngine;
  using TMPro;
  public class TextScript : MonoBehaviour
  { 
    [SerializeField] TextMeshProUGUI informationText;
    void Start(){ } 
    void Update() { } 
    void SetText(string text){ informationText.text = text; }
  }
  

 

この実装で他拠点の入室の際、ユーザーIDが表示されます。

WebViewでライブ映像の視聴

WebView部分のUnityのC#スクリプト側の実装です。

WebView.cs

  using System.Collections;
  using UnityEngine;
  using UnityEngine.Networking;
  using UnityEngine.UI;
  public class WebView : MonoBehaviour
  {
    WebViewObject webViewObject;
    Button button;
    void Start()
    {
      button = GetComponent<Button>(); 
      button.onClick.AddListener(StartViewLive); 
    } 
    void StartViewLive() 
    { 
      webViewObject = 
      (new GameObject("WebViewObject")).AddComponent(); 
      webViewObject.Init(
        ld: (msg) => Debug.Log(string.Format("CallOnLoaded[{0}]", msg)),
        enableWKWebView: true
      );
      webViewObject.SetMargins(0, 0, 5, Screen.height / 4);
      webViewObject.LoadURL("/view.html"); 
      webViewObject.SetVisibility(true);
    }
  }
  

unity-webviewアセットにおいて、WebGLに対応するにはアセットのマニュアルインポート依存ファイルのコピーが必要になります。詳細は公式のドキュメントをご確認ください。ボタンをクリックすることで、view.htmlがレンダリングされます。

 

レンダリングするview.htmlとJavascriptは映像を受信するだけの実装をします。

live.js

  var client = AgoraRTC.createClient({ mode: "live", codec: "vp8" });
  var localTracks = { audioTrack: null };
  var remoteUsers = {}; 
  var options = { appid: YOUR APP ID, channel: YOUR CHANNEL ID, uid: null, token: null}; $(() => { join(); });
  function join() {
    client.on("user-published", handleUserPublished);
    client.on("user-unpublished", handleUserUnpublished);
    options.uid = client.join(options.appid, options.channel, options.token || null);
  }
  
  async function subscribe(user, mediaType) {
    const uid = user.uid;
    await client.subscribe(user, mediaType);
    if (mediaType === 'video') {
      const player = $(` <div id="player-wrapper-${uid}">
                         <div id="player-${uid}" class="player"></div>
                         </div> `);
      $("#remote-playerlist").append(player);
      user.videoTrack.play(`player-${uid}`, { fit: "contain" }); 
    }
    if (mediaType === 'audio') { user.audioTrack.play(); }
  }
  
  function handleUserPublished(user, mediaType) {
    const id = user.uid; remoteUsers[id] = user; 
    subscribe(user, mediaType); 
  }
  
  function handleUserUnpublished(user) {
    const id = user.uid; delete remoteUsers[id];
  }
  

 

今回の実装は非常に簡単なものですがWebGLとJavaScript間の連携、ライブ映像のWebGL対応についてご参考になるかと思います。

 

WebGLで要件を満たせない場合

WebGLを採用するサービスの要件は主に以下の2点があげられます。

  • インストールレス
  • リッチコンテンツ

インストールレスについてはほとんどの場合でWebGLは要件を満たせます。

しかし、リッチコンテンツについては端末の処理能力で満たせない要件が発生する場合もあります。

では、どのように対応すればよいでしょうか?

クラウドレンダリング

クラウドレンダリングとはユーザーが使う端末に最低限の処理を実行させ、ほとんどの処理をクラウド上のコンピューターで実行させる概念です。シンクライアントという呼び方で普及してきました。

クラウドレンダリングでは処理能力が求められるアプリケーションの実行をクラウド上のサーバーで実行し、その結果を映像・音声としてストリーミングします。ユーザーはブラウザでその映像・音声をサブスクライブして、操作の情報だけをサーバー側に返します。

この技術を用いれば、WebGLで要件を満たせない場合でも対応が可能になります。

最近ではクラウドレンダリングを簡単に実装できるPaaSも提供されています。

Tencent Cloud CARが代表的なサービスになります。

特にメタバースや展示会等での実績が多く、今後はこの技術が広く活用される事が想定されます。

WebGLで満たせない場合はクラウドレンダリングを視野に入れてみるのはいかがでしょうか。

 

>Agoraの特徴をチェックする
>Agoraの無料トライアル

ガイドブックダウンロード
ビデオ通話・ライブ配信API/SDK「Agora」

超低遅延API/SDK「Agora」ガイドブック

通話・配信遅延30-200ms!100万人の視聴対応!未経験者から専門家まで、誰でも読みやすいAgoraのガイドブックをダウンロードしませんか。

無料ダウンロード
藤本 諭志

執筆者藤本 諭志

株式会社ブイキューブ 技術本部 Agora担当。 2007年ブイキューブ入社。 自社開発サービスであるV-CUBE セミナーの開発に携わる。現在はAgoraとTencent Cloudのプロダクト担当SEをしている。 スキル:Docker/AWS/Linux/DB/Ruby/PHP/JavaScript

関連記事

先頭へ戻る