MetaQuestでローカルwebサーバを立ててスタンドアロンでWebXRアプリを動かす


Meta Quest上にローカルwebサーバを立てて、そこでWebXRアプリを動かそうという記事です。
これで完全スタンドアロンでWebXRアプリを動かすことができます。

:::note info
以下MetaQuestで記述ですが、PICO4でもほぼ同様に動作します。
:::


Quest上でのWebサーバは、TermuxというAndroidのターミナルアプリを使います。これはターミナルエミュレータではあるのですが、なんとLinuxのコマンド類も普通に動かすことができます。phpも動くので、phpのデバッグ用webサーバ機能を使えば簡単にローカルサーバを立てることができるというわけです。

Termux自体が普通のAndroidアプリなので、ルート化等も必要なく、サンドボックスで安全に動かすことができます。

Termuxについては以下の記事も参考にどうぞ。

https://pc.watch.impress.co.jp/docs/column/nishikawa/1409411.html



手順はおよそ以下のようになりなす。

 1. F-Droidをインストール
 1. F-DroidからTermuxをインストール
 1. 起動してシェルが使えることを確認
 1. termux-change-repo でリポジトリを更新
 1. pkg install で必要なツールをインストール
 1. php は pkg install phpでok
 1. phpのdebugモードでwebサーバを起動
 1. ストレージのアクセスを許可
 1. ストレージをdocument rootにsymlinkすると見えるようになる
 1. file一覧を取得するapiを作る
 1. ファイル一覧ページが動いた
 1. WebXRアプリの動作

## Termuxのインストール

Termuxの最新版は、AndroidのOSSサイトであるF-Droidで配布されているので、まずF-Droidアプリをインストールします。

https://f-droid.org/ja/

apkがダウンロードできるので、sideQuestやMQDH等でQuest本体にインストールしてください。

F-Droidがインストールできたら、Termuxを検索して以下のアプリをインストールします。

![518dc4b716d05ca983d3cc92e6756630.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74838/e8ce56ff-8690-843e-aaec-550389b3cf1e.jpeg)

Termuxをインストールして起動すると、以下のような画面でシェルが起動します。

![a53daff4154d5626d0c5cae92e1208ea.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74838/ee24ae2f-543c-e1e9-c36b-43494384c58d.jpeg)

ここで、実はすでにLinuxライクのシステムが動いていて、シェル内蔵コマンドや基本的なコマンドはそのまま動作します。
バーチャルキーボードも使えますが、BTキーボードを繋いだほうが実用的でしょう。

Linuxのプログラムはpkgコマンドでインストールできます。
最初にリポジトリを最新にするために、以下のコマンドを実行します。

```
$ termux-change-repo
```

リポジトリを選択する画面になるので、適当に選びます。

これで、pkg install　で各種プログラムがインストールできるようになります。
vimやgit,unzip,curl等いれておくと良いでしょう。


## phpとWebサーバの設定

phpは

```
$ pkg install php
```
でインストールできます。

phpの内蔵デバッグ用Webサーバを動かします。デバッグ用ではありますが、一人でローカルで使う分には十分です。

document root となる適当なディレクトリを作って、そこに移動して

```
$ php -S localhost:8080 2> /dev/null &
```

でwebサーバが起動します。リダイレクトをファイルに落とせばアクセスログになります。

document rootに

``` info.php
<?php
phpinfo() ;
```
を作って、Questブラウザで http://localhost:8080/info.php を開いてみましょう。

![08483633387e772a83f187079ec614cd.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74838/2b777b7b-bab5-c5ee-08cc-ac69c84d8d3e.jpeg)

これでwebサーバとphpが動きました。ここまでくればあとは何でもできますねｗ

phpはサーバとして動いてはいるものの、Termuxアプリが何らかの原因で停止すると、当然ながらphpサーバも止まります。バックグラウンドで動いてるアプリは、メモリ不足等によりいつ殺されるかわからないのですが、実際には結構しぶとく残ります。他のVRアプリを起動しても残っているので、システムレベルで落ちるようなことがない限り動き続けるのかもしれません。

:::note 
2024/2/23 追記。
:::

Questメニューの「通知」にあるTermuxのプロセスのところで、"Acquire wakelock"ができることを発見しました。これをセットしておくと、バックグラウンドでの動作が保証されるはずなので、webサーバも落ちなくなることが期待されます。

![com.oculus.shellenv-20240223-183206.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74838/b62726b3-fd23-0e77-bd5d-658519d6644d.jpeg)



## 共有ストレージエリアにアクセスする

Termuxのhome以下は、Termuxアプリのサンドボックス領域にあり、このままでは、共有ストレージにはアクセスできません。

アクセスするためには、Questの設定のアプリの項目で、ストレージアクセスを許可する必要があります。

![b57396f6c617a16c86376a63bad048e6.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74838/240a9275-6344-1379-fa4c-0ba94efe1b4c.jpeg)

これをonにすると、 /storage/emulated/0 のパスで、DownloadやMoviesのフォルダのある共有ストレージにアクセスすることができます。

Webからアクセスする場合は、document rootの下に

```
$ ln -s /storage/emulated/0 share
```
のようにリンクを張っておくとよいでしょう。これでブラウザから共有ストレージに直接アクセスすることができるようになります。

## ファイル一覧アプリを作ってみる

ストレージの中身をリストして、動画や静止画の簡単なビュアーを作ってみます。

まずフォルダの一覧を取得するための、APIを作ります。 pathで指定したフォルダの一覧をjsonで返します。

``` dir.php
<?php
$path = $_GET['path']	;
$dp = @opendir($path) ;
if(!$dp) {
	echo json_encode(array('status:-1'));
	exit;
}
$files = [] ;
while(($f = readdir($dp))!==false) {
	if(substr($f,0,1)==".") continue ;
	$st = stat($path."/".$f) ;
	$files[] = array(
		'name'=>$f,
		'isdir'=>(($st['mode']&0770000)==040000),
		'size'=>$st['size'],
		'mtime'=>$st['mtime']
	); 
}
echo json_encode(array('status'=>0,'path'=>$path,'files'=>$files),JSON_UNESCAPED_UNICODE) ;
```

このdir.phpを使って、指定したフォルダにあるメディアファイルを一覧してリンクするwebアプリを作りました。

``` files.html
<!DOCTYPE html>
<html  lang="jp">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width">
<title>files</title>
<script type="text/javascript">
const path = location.search.substring(1)
onload = function() {
	let result = ""
	api("dir.php",{'path':path}).then(d=>{
		if(d.status==0) {
			d = d.files.filter(d=>{
				return d.name.match(/\.mp4|\.mp3|\.jpg|\.png|\.webm$/)
			})
			console.log(d) ;
			const l = [] 
			for(let i in d) {
				l.push(`<a href="${path+d[i].name}">${d[i].name}</a>`)
			}
			result = "<ul><li>"+l.join("</li><li>")+"</li></ul>"
		} else {
			result = "no files"
		}
		document.getElementById("list").innerHTML = result

	})
}
function api(path,param) {
		return new Promise((resolve,reject) => {
			const req = new XMLHttpRequest();
			const p = [] 
			for(let k in param) {
				p.push(`${k}=${encodeURIComponent(param[k])}`)
			}
			req.open("get",path+"?"+p.join("&"),true) ;
			req.responseType = "json" ;
			req.onload = () => {
				if(req.status==200) {
					resolve(req.response) ;
				} else {
					reject("Ajax error:"+req.statusText) ;
				}
			}
			req.onerror = ()=> {
				reject("Ajax error:"+req.statusText)
			}
			req.send() ;
		})
}
</script>
</head>
<body>
<div id=list></div>
</body>
</html>
```

これで、Downloadフォルダにある動画や静止画を一覧して、ブラウザで開くことができました。

![e28cc809e547dce90406a28fa0f4c1ef.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74838/61b80f23-c7d1-9f96-90b4-7df0f5943d48.jpeg)


## WebXR アプリを動かす

さていよいよ本題のWebXRアプリです。といっても、ここまでできていれば、あとは普通にWebXRのアプリをドキュメントルートに配置するだけです。

WebXRは、本来https環境でなければ動作しませんが、localhostは例外的にhttpでも動作します。

URLに注目。

![e510bee43d330b2dcb7ded287f76e5c1.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74838/17e50551-44c5-7c75-8714-6a5c5af9468d.jpeg)

実際の開発では、さすがにTermux上でコードを編集するのはつらいので、別なサーバで開発して、githubを経由して同期するのが良いでしょう。QuestのTermux上ではgit pullするだけで更新することができます。