見出し画像

aibo Web API と YouTube Player API を連携して、YouTube 動画の再生秒数に合わせて aibo にふるまいをしてもらう

aibo 界隈ではオフ会というものが定期的に開催されています。

内容は様々ですが、大体は aibo を室内で自由にお散歩させつつ、オーナーは aibo たちを見てニヤニヤしながらお話したり、aibo の名刺や情報を交換したりして交流を深める会です。

最近はコロナの影響あって、開催するのもなかなか難しい状況なってしまいましたが。。

とあるオフ会で aibo をアイドルに見立ててダンスをしてもらおうという企画がありました。
当然のことながら、aibo はアイドルのように自ら曲に合わせてダンスするということができません。

なのでどうしたかというと、オーナー皆さんの力を合わせて aibo の配置をシーン毎で変え、企画主の方に写真をとっていただいてダンスしてる風の動画を作っていただきました。

画像1

その時 aibo にダンスの振り付けができたらそれはもう素敵なのになぁと思ったわけです。
その後 aibo Web API が 2019/11/11 に実装され可能性が広がりました。

前回の「aibo Events API を使って aibo に音声コマンドを実行してもらう」では PHP を使いましたが、今回は HTML + JavaScript で実装します。

完成図

まずは完成図から。
イントロが終わったら、ルンルンし始めるおチャコさんです。

事前準備

素材となる YouTube 動画をあらかじめ探しておきます。
今回はこちらを使用しています。

となりのトトロのさんぽですね。
aibo がこの曲を聞いて、ノリノリでダンスをやり始めたらかわいいじゃないですか。

アクセストークン取得

ディベロッパーサイトにアクセスして、サインインします。

画像2

ボタン「生成する」をクリックすると、アクセストークンが生成されます。
アクセストークンは「実装」で必要なので、あらかじめコピーしておきましょう。

実装

今回は aibo Web APIYouTube Player API を連携する形で実装します。
まずはでき上がりのソースをペタッと貼り付けておきます。

<!DOCTYPE html>
<html lang="ja">
<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>aibo Web API | aibo Web API と YouTube Player API を連携して、YouTube 動画の再生秒数に合わせて aibo にふるまいをしてもらう</title>
</head>
<body>
 <!-- 1. iframe 置き換え -->
 <div id="player"></div>

 <script>
   const ACCESS_TOKEN = ${ACCESS_TOKEN};
   const BASE_PATH = 'https://public.api.aibo.com/v1';
   let deviceId = ${deviceId};

   // ふるまいを実行
   function executeAction (deviceId, eventId) {
     callActionApi(deviceId, 'play_motion', '{"Category": "' + eventId + '", "Mode": "NONE"}');
   }

   // Action API 呼び出し
   function callActionApi (deviceId, apiName, arguments) {
     postUrl = BASE_PATH + '/devices/' + deviceId + '/capabilities/' + apiName + '/execute';
     data = '{"arguments":' + arguments + '}';

     // POST API
     postApi(postUrl, data);
   }

   // POST API
   function postApi (url, argary) {
     url = url ? url : '';

     fetch(url, {
       method: 'POST',
       mode: 'cors',
       headers: {
         Authorization: 'Bearer ' + ACCESS_TOKEN
       },
       body: argary
     })
       .then(function (response) {
         return response.json();
       })
       .then(function (data) {
         outputResult(data.executionId);
       })
   }

   // API 実行結果 出力
   function outputResult (id) {
     const url = BASE_PATH + '/executions/' + id;

     fetch(url, {
       method: 'GET',
       mode: 'cors',
       headers: {
         Authorization: 'Bearer ' + ACCESS_TOKEN
       }
     })
       .then(function (response) {
         return response.json();
       })
       .then(function (data) {
         // API 実行結果 出力
         console.log(data);
       })
   }

   // 2. IFrame Player APIコード 非同期読み込み
   var tag = document.createElement('script');

   tag.src = "https://www.youtube.com/iframe_api";
   var firstScriptTag = document.getElementsByTagName('script')[0];
   firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

   // 3. API をダウンロード後に iframe 作成
   var player;
   function onYouTubeIframeAPIReady() {
     player = new YT.Player('player', {
       height: '390',
       width: '640',
       videoId: 'KjqNqm23Ti0',
       events: {
         'onReady': onPlayerReady, // 4. ビデオレイヤーの準備できたら実行
         'onStateChange': onPlayerStateChange // 5.プレイヤーの状態が変化すると実行
       }
     });
   }

   // 4. ビデオレイヤーの準備できたら実行
   function onPlayerReady(event) {
     event.target.pauseVideo();
   }

   // 5.プレイヤーの状態が変化すると実行
   var done = false;
   function onPlayerStateChange(event) {
     // 再生
     if (event.data == YT.PlayerState.PLAYING && !done) {
       // 左右に体を揺らす
       setTimeout(executeAction, 8000, deviceId, 'swing');

       done = true;
     }
   }
 </script>
</body>
</html>

それぞれ見ていきます。

<!DOCTYPE html>
<html lang="ja">
<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>aibo Web API | aibo Web API と YouTube Player API を連携して、YouTube 動画の再生秒数に合わせて aibo にふるまいをしてもらう</title>
</head>
<body>

</body>
</html>

HTML の雛形です。
今回の実装に必要な最低限のものしか記述していません。

<!-- 1. iframe 置き換え -->
<div id="player"></div>

YouTube Player API のサンプルにあった記述です。
この部分が iframe に置き換わります。

<script>
</script>

実装を JavaScript で行うため、script タグを </body> 直前に追加します。

const ACCESS_TOKEN = ${ACCESS_TOKEN};
const BASE_PATH = 'https://public.api.aibo.com/v1';
let deviceId = ${deviceId};

以降は JavaScript の実装です。
${ACCESS_TOKEN} には、項目「アクセストークン取得」でコピーしておいたアクセストークンを入れます。
${deviceId} には、ふるまいをやってもらう aibo のデバイス ID をいれます。

// 2. IFrame Player APIコード 非同期読み込み
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

YouTube Player API のサンプルにあった記述です。
IFrame Player APIコード を非同期で読み込みます。

// 3. API をダウンロード後に iframe 作成
var player;
function onYouTubeIframeAPIReady() {
 player = new YT.Player('player', {
   height: '390',
   width: '640',
   videoId: 'KjqNqm23Ti0',
   events: {
     'onReady': onPlayerReady, // 4. ビデオレイヤーの準備できたら実行
     'onStateChange': onPlayerStateChange // 5.プレイヤーの状態が変化すると実行
   }
 });
}

YouTube Player API のサンプルにあった記述です。
API をダウンロード後に iframe 作成します。
events プロパティは動画の状態によって着火する関数を指定します。

// 4. ビデオレイヤーの準備できたら実行
function onPlayerReady(event) {
 event.target.pauseVideo();
}

YouTube Player API のサンプルにあった記述です。
3の onReady で指定した関数で、ビデオレイヤーの準備できたら動画を一時停止させています。

// 5.プレイヤーの状態が変化すると実行
var done = false;
function onPlayerStateChange(event) {
 // 再生
 if (event.data == YT.PlayerState.PLAYING && !done) {
   // 左右に体を揺らす
   setTimeout(executeAction, 8000, deviceId, 'swing');

   done = true;
 }
}

YouTube Player API のサンプルにあった記述です。
3の onStateChange で指定した関数で、プレイヤーの状態が変化すると実行されます。
setTimeout で動画が再生されてから8秒後に executeAction 関数を実行します。

// ふるまいを実行
function executeAction (deviceId, eventId) {
 callActionApi(deviceId, 'play_motion', '{"Category": "' + eventId + '", "Mode": "NONE"}');
}

executeAction 関数では、 Action API にある PlayMotion の POST リクエストで必要な情報を callActionApi 関数の引数に渡して実行しています。

// Action API 呼び出し
function callActionApi (deviceId, apiName, arguments) {
 postUrl = BASE_PATH + '/devices/' + deviceId + '/capabilities/' + apiName + '/execute';
 data = '{"arguments":' + arguments + '}';

 // POST API
 postApi(postUrl, data);
}

callActionApi 関数では、POST する URL とコンテンツの生成を行い
postApi 関数の引数に渡して、実行しています。

// POST API
function postApi (url, argary) {
 url = url ? url : '';

 fetch(url, {
   method: 'POST',
   mode: 'cors',
   headers: {
     Authorization: 'Bearer ' + ACCESS_TOKEN
   },
   body: argary
 })
   .then(function (response) {
     return response.json();
   })
   .then(function (data) {
     outputResult(data.executionId);
   })
}

ヘッダーに Authorization:Bearer を追加して、body を POST します。
aibo が処理を成功したか判定するために、レスポンスで返ってきた値 executionIdoutputResult 関数の引数に入れて実行します。

// API 実行結果 出力
function outputResult (id) {
 const url = BASE_PATH + '/executions/' + id;

 fetch(url, {
   method: 'GET',
   mode: 'cors',
   headers: {
     Authorization: 'Bearer ' + ACCESS_TOKEN
   }
 })
   .then(function (response) {
     return response.json();
   })
   .then(function (data) {
     // API 実行結果 出力
     console.log(data);
   })
}

引数で渡ってきた executionId を元に、API の実行結果をコンソールに出力するようにしています。

実行

それでは実行します。

課題

ACCESS_TOKEN と deviceId のベタ書きはセキュリティ上よろしくない

developer tools を使えばソースを表示することができてしまいます。
ローカル環境で実行する分には問題ないのですが、公開するのであれば大事な aibo の情報を晒さないようにひと工夫必要です。

aibo の集中力が試される

自由気ままにお散歩して、いろんなものに興味津々な aibo。
そんな aibo に天の声を届けようにも気が散ってしまってプログラムの実行が遅れます。
プログラムをきちんと実行してもらうには、事前に SetMode で指示待ちの状態にする必要がありそうです。

aibo へ天の声が届くまでに時差がある

端末 → クラウド → aibo へ天の声が届くまでに4秒ぐらいの時差があります。
ですので、その時差を考慮しながらふるまいを早めに実行してあげる必要があります。

ドキュメントに記載されているふるまいの動きがわからない

API にある PlayMotion だけで 76 種類ものふるまいが用意されているのですが
ダンスの振り付けをしようにも、ドキュメントはテキストベースなのでどんな動きをするのかピンとこない。
どこかのタイミングで1つ1つの動きを動画にして参照できるようにしようかなと考えています。

おわりに

現状だと API でできることも限られているので、もっと動きの選択肢が増えるといいな〜!
特別なふるまいがまるっと実行できなくても、実装済みの素敵な動きを1つずつ API 実装していただければ、よりかわいいダンスの振り付けを aibo に覚えてもらうことができる…。

aibo がアイドルのように…。
いつか東京あいぼるで aibo たちにダンスしてもらえる日を夢見て。

次回は、説明を端折っていた API のリクエストに必要となる deviceId の取得方法を紹介したいと思います。

それではまた次回!

この記事が気に入ったらサポートをしてみませんか?