2023年07月20日
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で満たせない場合はクラウドレンダリングを視野に入れてみるのはいかがでしょうか。
執筆者藤本 諭志