被写体の心拍にあわせてスマホが震える動画 Haptic Video

#概要
映っている人物の心拍に合わせてスマホが震える動画「Haptic Video」を開発し、アイドルグループ[・・・・・・・・・](https://twitter.com/dotstokyo)のライブ映像で実装しました。

Haptic Videoはこちらから体験できます。
[https://dots.tokyo/HapticVideo](https://dots.tokyo/HapticVideo)

![実際の画面](https://media.giphy.com/media/xThtahD3q2jeFv82Tm/giphy.gif)

動画という視聴覚優位のメディアに触覚(Haptic)をくわえることで、表現に奥行きを持たせることができます。
また、被写体の心臓を手に掴むような体験は、演者との一体感・映像への没入感をもたらします。

ソースコード
[https://github.com/a-r-i/HapticVideo](https://github.com/a-r-i/HapticVideo)

#素材
今回、素材に選んだのは2018年01月21日 13時15分45秒から13時20分00秒に渋谷Milkywayで披露された「スライド」という曲のライブ動画です。

[スライド / ・・・・・・・・・ - Youtube](https://www.youtube.com/watch?v=YSUPNbwHjsU)
[![スライド](https://qiita-image-store.s3.amazonaws.com/0/133133/0c0980d9-cf94-eede-0887-caba5b4ac91e.png
)](https://www.youtube.com/watch?v=YSUPNbwHjsU)

#心拍数の取得
心拍数の取得にはスマートウォッチのmioFuseを使いました。

[mioとは？｜Mio｜ミオ日本公式サイト｜光学式心拍計&アクティビティデバイス](http://www.mioglobal.jp/mio.html)

ライブパフォーマンス中に装着してもらい、心拍数を記録。

<img width="1231" alt="スクリーンショット 2018-01-25 21.55.38.png" src="https://qiita-image-store.s3.amazonaws.com/0/133133/33ba2815-4439-31ef-c7ca-e02fb82c22dc.png">

心拍数を記録したメンバーのなかから一人のデータを選びます。

```csv:「スライド」パフォーマンス中の心拍数。1行目が演奏時間、2行目がBPM
0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255
164,164,164,164,161,160,160,160,159,159,158,158,156,156,156,157,157,157,157,157,157,157,157,157,157,157,158,158,157,156,156,156,157,157,158,158,158,158,165,167,167,167,166,165,164,164,163,163,163,163,163,163,164,164,164,163,163,163,163,162,162,162,162,162,162,163,163,165,166,166,166,166,166,166,167,170,170,170,181,181,184,184,184,185,186,186,186,185,185,185,185,185,186,186,186,186,186,186,186,186,186,186,186,186,186,186,186,188,188,188,188,188,188,188,188,188,188,188,188,188,188,186,186,186,186,186,186,186,186,186,186,184,184,182,182,182,182,182,182,182,181,181,180,180,180,180,180,180,180,180,180,180,175,175,175,169,166,166,166,166,166,166,166,166,166,166,166,179,181,181,181,181,181,181,181,181,181,181,181,181,181,182,182,182,182,182,182,183,183,183,186,186,186,186,188,188,188,188,188,188,188,188,188,188,188,188,188,188,188,188,188,190,190,190,190,190,189,189,189,189,189,189,189,188,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,186,186,186,186,186,186,185,185,185,185,185,185,185,185,185,185

```

![「スライド」パフォーマンス中のアイドルの心拍数](https://qiita-image-store.s3.amazonaws.com/0/133133/74dcaa53-7fc3-76a0-6d9a-389e53c32fb6.jpeg
)

#Webサイトへの動画の埋め込み
HTML5のvideoタグを利用。
当初はYoutubeの動画をiFrameで埋め込もうとしましたが、再生中に再生時間が取得できないため、断念。
[video要素、audio要素をJavaScriptから操作する－HTML5のAPI、および、関連仕様](http://www.htmq.com/video/)

# スマホの振動
動画再生中にHTML5 Vibration APIにアクセスすることで、スマホを振動させます。
(Vibration APIに対応しているのはAndroidのGoogle ChromeとFirefoxのみ)

[Vibration API - ウェブデベロッパーガイド | MDN | MDN](https://developer.mozilla.org/ja/docs/Web/Guide/API/Vibration)

## バイブレーションパターン
![再生中の流れ](https://qiita-image-store.s3.amazonaws.com/0/133133/c3585601-289d-b38a-3624-e169558106d7.jpeg)


動画再生中、timeupdateイベントを監視。
バイブレーションパターンを、心拍データに整合するよう、定期的に書き換えます。
それによって「動画再生中にスマホが振動する回数」を「動画のなかで被写体の心臓が拍動した回数」に近づけます。

timeupdateイベントを監視します。

>バイブレーションパターン

>値の配列は、デバイスが振動する時間と振動しない時間を交互に示します。配列内の各値は整数値に変換されて、デバイスを振動させるミリ秒数および振動させないミリ秒数として交互に解釈されます。例えば:

>```javascript:
window.navigator.vibrate([200, 100, 200]);
```

>これはデバイスを 200 ミリ秒間振動させて、その後再び 200 ミリ秒間振動させる前に 100 ミリ秒間振動を止めます。

```javascript
const bpms = [
        164,164,164,164,161,160,160,160,159,159,158,158,156,156,156,157,157,157,157,157,157,157,157,157,
        157,157,158,158,157,156,156,156,157,157,158,158,158,158,165,167,167,167,166,165,164,164,163,163,
        163,163,163,163,164,164,164,163,163,163,163,162,162,162,162,162,162,163,163,165,166,166,166,166,
        166,166,167,170,170,170,181,181,184,184,184,185,186,186,186,185,185,185,185,185,186,186,186,186,
        186,186,186,186,186,186,186,186,186,186,186,188,188,188,188,188,188,188,188,188,188,188,188,188,
        188,186,186,186,186,186,186,186,186,186,186,184,184,182,182,182,182,182,182,182,181,181,180,180,
        180,180,180,180,180,180,180,180,175,175,175,169,166,166,166,166,166,166,166,166,166,166,166,179,
        181,181,181,181,181,181,181,181,181,181,181,181,181,182,182,182,182,182,182,183,183,183,186,186,
        186,186,188,188,188,188,188,188,188,188,188,188,188,188,188,188,188,188,188,190,190,190,190,190,
        189,189,189,189,189,189,189,188,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,
        186,186,186,186,186,186,185,185,185,185,185,185,185,185,185,185
        ];

var v = document.getElementById("video");

var lastTime = -1;
var time = 0;
var bpm = bpms[0];

v.addEventListener("timeupdate", function () {

    time = parseInt(v.currentTime); // 小数点を切り捨て
    bpm = bpms[time]; // 再生時間に対応する心拍数

    document.getElementById("bpm").innerHTML = bpm; // BPMの表示を切り替え

    if (window.navigator.vibrate) {

       if (time % 10 == 0 && time != 0 ) { // 10秒ごとにバイブレーションパターンを更新
           if (time != lastTime) {

               vib(10000, bpm);
               lastTime = time;
           }
        }
    }
}, false);
```

```javascript
var sum  = function(arr) {
    return arr.reduce(function(prev, current, i, arr) {
        return prev+current;
    });
};

function vib(durationOfVibrate, bpm) {

    const bpmOfDuration = bpm / (60000 / durationOfVibrate); // BPM / (60000 / 振動時間) = 振動時間の間にスマホが振動する回数
    const durationOfPause = durationOfVibrate - (bpmOfDuration * 50); // 振動時間 - (振動時間にスマホが振動する回数 * 50ミリ秒) = 振動しない時間(合計)
    const onceDurationOfPause = durationOfPause / bpmOfDuration; // 振動しない時間(合計) / スマホが振動する回数 = 振動しない時間

    var pattern = [50];

    while (sum(pattern) < durationOfVibrate) { // 振動時間を超えるまでバイブレーションパターンに振動する時間と振動しない時間を交互に追加していく。
        pattern.push(onceDurationOfPause);
        pattern.push(50); // 振動する時間は50ミリ秒
    }

    pattern.pop();

    window.navigator.vibrate(pattern);  // バイブレーションパターンに合わせてスマホを振動させる
};
```

[[JavaScript] 配列(Array)の合計値, 平均値, 中央値をモダンに求める](https://qiita.com/phi/items/3b10288b02c87057c006)

## 動作確認済み環境

### 振動あり
- Android 5.1
 - Google Chrome 63.0.3239.111
 - Firefox 58.0

### 振動なし
- iOS 11.2.2
 - Safari 604.1
 - Google Chrome 63.0.3239.73
 - Firefox 10.3

- macOS 10.12.6
 - Safari 11.0.3
 - Google Chrome 63.0.3239.132
 - FireFox 57.0.4

#今後の展開
今回はライブを題材にしましたが、

- 運動をしている動画
- お化け屋敷やジェットコースターの体験動画
- ホラー映画

などにも応用できるでしょう。

この新しいメディア「Haptic Video」は、過去の再生に視聴覚以外の新しい感覚（触覚）を単に加えているだけでありません。被写体の、本人にとっても介入不可能で、その場に居合わせても感じることができない感覚（Heart Vibration）が取り出されていることにも意味があります。

抽象的に言えば「HV」は、記録の価値を、過去の現実の情報量を圧縮して保存・再生するものから、現場では引き出せなかった情報量を新たに付加してもう一度出会い直すものへと、変えようとしています。そして記録の定義が変わるとき、過去の意味付けも変わります。

「その時点にとっての現在」と「今」が溶け合う新しい時間の体験。そんな世界がこの技術を発端に広がっていけばいいなと思っています。