iPadのWebアプリの関門、加速度センサ、audio、videoの制限をJavascriptで突破するまで

# これはなに
* 藝大との合同プログラムで以下のような作品を作った。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">最近暑さが増してますね。<br>藝大との合同プログラムで制作した作品です。<br>ホログラムに映し出された美しい波紋と、水琴窟の独特の音色で清涼感を感じてください！<br><br>ぜひ音声ありでどうぞ〜 <a href="https://t.co/iHn952uChx">pic.twitter.com/iHn952uChx</a></p>&mdash; M1YamA (@M1YamA00) <a href="https://twitter.com/M1YamA00/status/1291915489975795712?ref_src=twsrc%5Etfw">August 8, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


* iPadでアニメーションを表示し、それを下のピラミッドに映している。また、上に乗った竹筒を持ち上げることによる加速度の変化を読み取ってアニメーションを変えるなどもしている。それらをJavaScriptを用いたWebアプリで作成したのだが、とてもチャレンジングな経験をできたのでここに残したい。
* ちなみにiPadの画面はこんな感じ。
![IMG_1736.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/475530/8d52b2ef-4da7-962b-779f-476fa32d8143.jpeg)

* 動機としては、大きな画面と加速度センサなどを用いたかったが、iOS Certificateの年会費を払いたくなかったから。
    * もっといい方法はあったかもしれない……教えていただけたら幸いです。
    
* **iPadの大きな画面を使いたいが、iOS Certificateに登録はしたくないという人には参考になると思う。**
* 準備は以下のページを参考にすると良さそう。
* [スマホアプリを作ろう(こくぶん研究室)](https://kkblab.com/make/javascript/spapp.html#step1)

# 自分のアプリの構成と関門
* 自分のアプリは以下のような構成。
![すい.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/475530/49ef07a9-6999-7a47-57aa-cf68af441597.png)
* iOS13でWebアプリを構成するにあたって、大きく分けて3つの問題があった。1つ目が加速度センサの読み取りに関するもの、2つ目がaudioの再生に関するもの、3つ目がvideoの再生に関するものだ。

* それ以前の問題として、キャッシュの処理の関するものがあるのでそれから述べていく。

# 第0の関門、キャッシュの処理
* audioもvideoもgifアニメーションも、更新して再び再生を行おうとしてもできない。
* iOSのキャッシュの強固さは悪名高く、Qiitaにもいくつか記事にされていた。
    * [iPhone等の頑固なキャッシュ対策(Wordpress)](https://qiita.com/ooba1192/items/62c5a9832e56ab73b533)
* そこで、タイムスタンプを用いて表示の度に新しいファイルとして、キャッシュの衝突の問題を回避した。

```javascript
var timestamp = new Date().getTime();
gif.src = "hoge.gif?"+timestamp;
document.getElementById('gif').innerHTML = '<img src="hoge.gif">';
```


# 第1の関門、加速度センサ
* 加速度の変化で水滴の頻度を変える部分が肝なので、まずはiPadで加速度センサが正常に動作するかを確かめようとした。
* [このサイト](https://kkblab.com/make/javascript/acc.html)をコピペしてiPadで確かめたところ、値が0のまま動かない。
    * 手持ちのAndroidスマホでは綺麗に値を示しているのに。
* 少し調べてみると、iOSのバージョンに原因があるようだった。以下、簡単な変遷。
    * iOS 12.1以前：Andoroidと同様で何の工夫もなく使えた。
    * iOS 12.2：Safariだけユーザーの同意が必要になった。端末の設定をユーザーが変えてくれていれば特に問題はない。
    * iOS 13以降：全てのブラウザで制限が課せられた。コンテンツ側でユーザーにアクションを促し、同意を得られた場合にのみセンサが利用できる。

* ダウングレードしてやろうかと思った。
* 特に参考にしたのはこの記事。([iOS13+のSafariでdevicemotionやdeviceorientationイベントの許可を取得する方法](https://qiita.com/okumura_daiki/items/16a09c9c0d0b2509d261))
* これによると、
    * HTTPSである
    * 以下のようにクリックイベントから許可のポップアップを呼び出す

   ```javascript:
document.addEventListener("click", function() { DeviceMotionEvent.requestPermission();)
```
* キャッシュメモリは爆発する。4秒毎の再生を数時間続けて600MBを超えていた。軽めの音声ファイルとgifファイルだから良いが、重いものなら止まりそう。
    * もっといい方法はあるだろうなあ……

の2つの条件が必要らしい。

* お手軽にhttpsのサイトにできる[Netlify Drop](https://app.netlify.com/drop)を用いて、コードを追加することでうまく動いた。ばんざい。
    * Nitify Dropは、htmlが入ったフォルダごとドロップすると公開できるようになるホスティングサービス。以下の記事に詳しく載っている。
    * [【Netlify】ドラッグ＆ドロップでWEBサイトを公開する](https://qiita.com/NaokiIshimura/items/40ed08203b35aa0b527c)
    
# 第2の関門、audioの再生
* 音声の再生にとりかかったところ、かなり難しくて辛かった。

* iOSではユーザーのアクション無しにaudioを再生することはできない。つまり、毎回タップをしなければ音を流せないのだ。
    * これは後述のvideoの再生も同じ。
    * preloadで配置されているaudioをplay()で再生することはできず、何らかのクリックイベントの後にload()されたものをplay()で再生することができる。
* コンセプト的にも致命傷な仕様だった。
* まずは初回のタップだけでaudioを再生してみたが、setTimeoutを挟んでの次の音声が再生されない。
* setTimeoutの実行前にload()することで回避できる。
    * [iOS/Android で HTML5 の audio/video を任意のタイミングで再生する方法](http://dsuket.hatenablog.com/entry/2013/05/05/101430)

* 最終的に実行できるようになったものを以下に示す。

```javascript
var t = 5000;
document.addEventListener('click', clicked, false); 
function clicked(){
    var num=0;
    sui1.load();
    sui2.load();
    sui3.load();
    sui4.load();

    slideshow_timer();
 }

 function slideshow_timer(){
     switch (num) {
         case 0:
            sui1.play();
            num++;
             break;
         case 1:
            sui2.play();
            num++;
            break;
        case 2:
            sui3.play();
            num++;
            break;
         case 3:
            sui4.play();
            num=0;
            break;
         default:
             break;
     }
     var timeout = setTimeout("slideshow_timer()",t); 
 }
```
* clicked()関数はクリックイベントによって呼ばれる。
    * `document.addEventListener('click', clicked, false);`みたいな。
* 自分は水滴の再生をswitch文で分けていたが、load()の位置も重要だった。

* 当初1つの音声が再生されたcase文の中に次の音声用のload()を配置していたが、それだとクリックイベント後のload()として認識されないらしく、再生できなかった。
    * そのため、関数を2重にして解決した。
    
# 第3の関門、videoの再生
* audioと同様に、videoもユーザーのアクションが必要になっている。
* しかし、自分は加速度の変化によってvideoの再生を行うため、それらの連携が必要になってくる。
* 以下、videoの再生と加速度センサとの連携の2つに分けて述べる。

###videoの再生に関して
* ほとんど以下のサイトに助けられた。
    * [【HTML5】iOSのSafariでvideoタグ埋め込みのmp4が再生できないのはインターレースだから](https://deep-space.blue/web/822)
* 内容を簡潔に説明すると、iOSで動画を再生するためには以下の2つが必要。
    * video要素にplaysinline属性を追加しなければならないこと
    * videoファイルがプログレッシブ方式であること
    
```html
    <video id="video"  playsinline autoplay>
        <source src="video.mp4" type="video/mp4">
        <p>※ご利用のブラウザでは再生することができません。</p>
     </video>
```
* こんな感じの宣言を行う。
* 自分はプログレッシブ方式への変換はPremire Proを用いて行った。無料アプリでも可能だと思う。
    * YouTubeの動画は全てプログレッシブ方式なので、一旦アップしてダウンロードするとかも良さそう(ほんまか？)

###加速度センサの連携に関して
* 加速度センサの差が閾値を超えたことを読み取ってからvideoを再生するまでについて述べる。

```javascript
 var event = document.createEvent( "MouseEvents" ); 
 event.initEvent("click", false, true); 
 document.addEventListener("click", function() { DeviceMotionEvent.requestPermission(); });
 var aX = 0, aY = 0, aZ = 0, aZ2 = 0;     
 video.load();

 video.addEventListener("click",function(){ video.play(); },false);

 window.addEventListener("devicemotion", (dat) => {
        aX = dat.accelerationIncludingGravity.x;    // x軸の重力加速度（Android と iOSでは正負が逆）
        aY = dat.accelerationIncludingGravity.y;    // y軸の重力加速度（Android と iOSでは正負が逆）
        aZ = dat.accelerationIncludingGravity.z;    // z軸の重力加速度（Android と iOSでは正負が逆）

        if(Math.abs(aZ-aZ2)>1){
            video.dispatchEvent(event);
        }
        aZ2=aZ;
        }
    );
```
* ducumentとvideoの2つのクリックイベントを作成し、ジャイロセンサのユーザーによる承認とaudioの再生をdocumentのイベントで、動画再生をvideoのイベントで行うようにした。
* videoはdispachEventによってクリックイベントが発火される。
    * 1回でもクリックされたら再生し放題なの怖くない？怖いけど修正しないで下さい、アプリが動かなくなるので。

* 以下のサイトを参考にした。
    * [JavaScriptのクリックイベントを発火させる方法](http://yuuxxxx.hatenablog.com/entry/2013/09/20/224801)

# おわりに
* iOSに恨みが募る開発だった。iPad、ハードウェアとしては最高なんだけどな……

* 波紋とその音の美しさをインタラクティブに感じられる作品を作りたいという動機で作ったので、素人ながら形にできて良かった。