Fire TVでは、開発者はさまざまな方法でユーザーにコンテンツを提供できます。HTML5ベースのウェブアプリを利用することで、そのほかに提供しているサービスとの一貫性があるユーザーエクスペリエンスを提供できます。また、公開までの時間を短縮でき、複数のマーケットプレイスやデバイスへのアプリの展開が容易になります。また、アプリ全体をビルドしてリリースしなくても、簡単にアップデートを公開できるようになります。
本チュートリアルでは、D-Padとタッチ操作の両方で動作するAmazon WebView(AWV)テクノロジーを使用して、Fire TVデバイス用のラッパーアプリを作成する方法を学びます。 アプリのナビゲーションと入力の基本事項については、UXのベストプラクティスに関するFire TVドキュメントを参照してください。
本チュートリアルでは、主に以下4つの手順について説明します。
手順1: タッチスクリーンのハードウェア機能を宣言する
タップやドラッグなどの基本的なタッチ操作イベントを有効にするには、AndroidManifest.xmlファイルにandroid.hardware.touchscreen宣言を追加します。本機能は、デバイスがタッチスクリーンを備えている、またはタッチスクリーンをエミュレートしている(「疑似タッチ」インターフェイスの)場合に、アプリにそのデバイスとの互換性があることを示します。タッチスクリーン搭載Fire TVデバイスに対応するには、本機能を追加する必要があります。
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8" ?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
...>
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
...
</manifest>
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.myapplication"
tools:ignore="MissingLeanbackLauncher">
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
<uses-feature
android:name="android.software.leanback"
android:required="false" />
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:banner="@drawable/amazon_firetv"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true">
<activity
android:name=".MainActivity"
android:configChanges="fontScale"
android:label="@string/player_activity_name"
android:launchMode="singleTop"
android:theme="@style/AppTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<uses-library
android:name="com.amazon.webview"
android:required="false" />
</application>
</manifest>
手順2: Amazon WebView(AWV)のサポートを追加し、JavaScriptを有効にする
WebViewクラスは、AndroidのViewクラスの拡張で、Activityレイアウトの一部としてウェブページをレンダリングできるようにします。開発が完了したウェブブラウザの、ナビゲーションコントロールやアドレスバーといった機能は一切含まれていません。WebViewがデフォルトで処理できるのはウェブページの表示のみです。
Fire OS 5以降で動作するすべてのFire TVデバイスには、標準的なAndroid WebViewクラスの透過的な代替としてAmazon WebView(AWV)が搭載されています。AWVは、Fire TV向けに最適化されたChromiumのカスタムビルドを使用しています。これにより、高速で効率的なレンダリング、ビデオパフォーマンスの改善、リソース管理の向上が実現します。
レイアウト内でアプリにWebViewを追加するには、アクティビティのレイアウトXMLファイルに次のコードを追加します。
activity_main.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".MainActivity">
<WebView
android:id="@+id/web_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center" />
</FrameLayout>
Fire TV対応アプリ内でWebViewを含むActivityを作成し、それを使用してonCreate()でオンラインのウェブアプリを表示します。WebViewにウェブページを読み込むには、loadUrl()を使用します。以下の例では、オンラインでホストされているウェブアプリのアドレスをmBaseUriに格納します。
MainActivity.java
public class MainActivity extends Activity {
//オンラインでホストされているウェブアプリのアドレス
private final String mBaseUri = "https://mysampleapp.com";
private Context mContext = null;
private WebView mWebView;
...
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = this.getApplicationContext();
//ユーザーが実行できる単一のアクティビティにフォーカスします。
//Activityクラスは、UIを定義するレイアウトリソースを
//配置するためのウィンドウを作成します。
//こちらの例では、activity_main.xmlでレイアウトリソースが定義されています。
setContentView(R.layout.activity_main);
//操作する必要があるUI内のWebViewウィジェットを取得します。
//こちらの例では、WebViewウィジェットはactivity_main.xmlの「web_view」で指定されます。
mWebView = findViewById(R.id.web_view);
mWebView.loadUrl(mBaseUrl);
}
...
}
JavaScriptの有効化
ウェブアプリでは、さまざまなユーザー操作を可能にし、バックエンドサービスと通信するためにJavaScriptを使用する可能性が高いため、WebViewでJavaScriptを有効にする必要があります。
WebViewでは、JavaScriptはデフォルトで無効になっているので、WebViewに付属したWebSettingsを使って有効化します。それには、getSettings()を使用してWebSettingsを取得し、setJavaScriptEnabled()でJavaScriptを有効にします。
MainActivity.java
public class MainActivity extends Activity {
private final String mBaseUrl = "https://mysampleapp.com";
private Context mContext = null;
private WebView mWebView;
...
@Override
protected void onCreate(Bundle savedInstanceState) {
...
mWebView = findViewById(R.id.web_view);
WebSettings webSettings = mWebView.getSettings();
webSettings.setJavaScriptEnabled(true);
...
mWebView.loadUrl(mBaseUrl);
}
...
}
WebSettingsを使用すると、ほかにもあらゆる便利な設定にアクセスできます。たとえば、Fire TV対応アプリでWebView向けのウェブアプリを開発している場合、setUserAgentString()でカスタムユーザーエージェント文字列を定義しておけば、ウェブページでカスタムユーザーエージェントを照会することで、ウェブページをリクエストしているクライアントが自分のFire TV対応アプリかどうかを確認できます。
手順3:ページナビゲーションとデバイス構成の変更を処理する
注: ウェブアプリがSPA(シングルページアプリ)として構築されていて、単一のウェブドキュメントのみを読み込み、その単一ドキュメントの本文コンテンツをJavaScript APIで更新する場合、こちらの手順は省略可能です。
SPA以外のウェブアプリのナビゲーション
ウェブアプリでユーザーがアプリの別のセクション(ナビゲーションメニューなど)に移動するリンクをクリックする必要がある場合、ユーザーがWebView
のリンクをクリックすると、デフォルトの動作ではURLを処理するアプリをFire TVが起動します。通常は、デフォルトのウェブブラウザが開き、目的のURLが読み込まれるのですが、WebView
で本動作をオーバーライドし、WebView
内でリンクが開くように設定することができます。これにより、WebView
が管理するウェブページ履歴により、ユーザーが前のページや次のページへと移動できるようになります。
注: セキュリティ上の理由から、Fire OSブラウザアプリのデータはウェブアプリと共有されません。
ユーザーがクリックしたすべてのリンクを開くには、setWebViewClient()を使用してWebView
用のWebViewClient
を提供します。shouldOverrideUrlLoading()をオーバーライドするカスタムWebViewClient
を作成することで、使用中のWebViewにURLが読み込まれる際にホストアプリによる制御が可能になります。
次の例では、CustomWebViewClient
をActivityの内部クラスとして作成し、WebView
用にこちらの新しいWebViewClient
のインスタンスを作成します。
MainActivity.java
public class MainActivity extends Activity {
private final String mBaseUrl = "https://mysampleapp.com";
private WebView mWebView;
private CustomWebViewClient webViewClient;
...
@Override
protected void onCreate(Bundle savedInstanceState) {
...
mWebView = findViewById(R.id.web_view);
webViewClient = new CustomWebViewClient();
mWebView.setWebViewClient(webViewClient);
...
mWebView.loadUrl(mBaseUrl);
}
...
}
class CustomWebViewClient extends WebViewClient {
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
view.loadUrl(request.getUrl().toString());
return super.shouldOverrideUrlLoading(view, request);
}
}
ユーザー補助機能のカスタマイズに応じたデバイス構成の変更
Fire TVにはさまざまなユーザー補助機能があります。そのうちの1つに画面上にテキストバナーを表示するText Bannerがあります。視覚障害のあるユーザーが画面のテキストを読むのに役立ちます。アプリ実行中にユーザーがユーザー補助機能を変更すると、アクティビティ状態が変わり、デバイス構成が変更されます。これにより、WebViewオブジェクトのアクティビティが破棄され、新しいアクティビティが作成されることがあります。また、破棄されたオブジェクトのURLを読み込む新しいWebViewオブジェクトも作成されます。
特定の構成変更時にアプリがリソースを更新しなくても済むように実行時の構成変更を処理し、アクティビティの再起動を避けるには、アクティビティがfontScale構成変更そのものを処理することを宣言して、システムがアクティビティを再起動しないようにします。
AndroidManifest.xml
<activity
android:name=".MainActivity"
android:configChanges="fontScale"
android:label="@string/player_activity_name"
android:launchMode="singleTop"
android:theme="@style/AppTheme">
...
</activity>
これにより構成が変更されても、MainActivityは再起動されません。代わりに、MainActivityはonConfigurationChanged()の呼び出しを受け取ります。本メソッドでは、新しいデバイス構成を指定するConfigurationオブジェクトが渡されます。Configurationの各種フィールドを確認することで、新しい構成を特定し、インターフェイスで使用されているリソースを更新して適切な変更を加えることができます。本メソッドが呼び出されると、アクティビティのResourcesオブジェクトが更新され、新しい構成に基づくリソースが返されます。このため、システムがアクティビティを再起動しなくても簡単にUIの要素をリセットできます。
MainActivity.java
public class MainActivity extends Activity {
...
@Override
public void onConfigurationChanged(final Configuration c) {
super.onConfigurationChanged(c);
mWebView.getSettings().setTextZoom((int) (c.fontScale * 100));
}
}
以上がウェブページを表示する基本的なWebViewで必要な設定です。また、以下を変更してWebViewをカスタマイズすることもできます。
手順4: WebViewとウェブアプリの間のやり取りを処理する
WebViewからのD-PADイベントのディスパッチ
ほかのAndroid入力デバイスと同様に、Amazon Fire TVリモコンのボタンを押すとKeyEventイベントが生成されます。コントローラーのボタン入力は、標準のAndroidイベントリスナーインターフェイスとコールバックを使用して処理できます。Amazon Fire TVリモコンと音声認識リモコンではいずれの場合も(Android MotionEventクラスの)モーションイベントは発生しません。
注: Fire TV Edition(日本未対応)のリモコンには、音量アップ/ダウン、電源、特定アプリを直接開くボタンなど、いくつかのボタンが追加されています。ただし、これらのボタンはサードパーティ製アプリのイベントにはマッピングできません。
D-Padによる操作を管理することで、アプリのユーザーエクスペリエンスを大幅に向上させることができます。Androidフレームワークには、D-Pad(またはその他のコントローラー)の入力を検出して処理するためのAPIが用意されています。Activityクラスで、dispatchKeyEvent()メソッドをオーバーライドし、必要に応じてカスタマイズを追加します。以下の例では、D-Padの「戻る」キーを変更して、WebView内で常に前に戻り、移動する履歴がない場合にはアプリを終了するようにする方法を示します。dispatchKeyEvent()メソッドをオーバーライドすると同時にキーイベントをWebViewに送信可能です。これをウェブアプリでJavaScriptを使用して処理し、必要なアクションを実行できます。
MainActivity.java
public class MainActivity extends Activity {
private final String mBaseUrl = "https://mysampleapp.com";
private Context mContext = null;
private WebView mWebView;
...
@Override
protected void onCreate(Bundle savedInstanceState) {
...
}
...
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
Log.d(TAG, "dispatchKeyEvent : KeyCode = " + event.getKeyCode() + " (" + KeyEvent.keyCodeToString(event.getKeyCode()) + ")");
switch (event.getKeyCode()) {
//D-Padの戻るボタンを処理します
case KeyEvent.KEYCODE_BACK:
Log.d(TAG, "dispatchKeyEvent : mWebView.canGoBack = " + mWebView.canGoBack());
if (mWebView.canGoBack()) {
//WebViewで前に戻ります
mWebView.goBack();
} else {
//WebViewに履歴がない場合は、アプリを終了します
finish();
}
}
return super.dispatchKeyEvent(event);
}
}
D/MainActivity: dispatchKeyEvent : KeyCode = 85 (KEYCODE_MEDIA_PLAY_PAUSE)
JavaScriptでのD-Pad操作の処理
ユーザーがD-Padを操作すると、ウェブアプリのJavaScriptはKeyboardEventイベントを受け取ります。KeyboardEventを処理するには、次の手順に従います。
以下の例では、Video.jsというオープンソースのHTML5ビデオプレーヤーを使用して、基本的なメディアプレーヤーのエクスペリエンスを構築しています。keydownイベントは、D-Pad操作を開始するとすぐにトリガーされます。ここではデモンストレーションとしてドキュメント全体の背景を緑色に設定します。キーを離すと、keyupイベントがトリガーされます。カスタムのキー処理メカニズムは、handleKeyEvent()メソッドで定義されています。
index.html
// https://videojs.com/getting-started/
const player = videojs("video", {...});
//カスタムのキーイベントハンドラー
const moveTime = 10; //秒数分移動します
const handleKeyEvent = (event) => {
switch(event.code) {
case "MediaPlayPause":
case "NumpadEnter":
//再生が一時停止していることを確認します
if (player.paused()) {
videojs.log('handleKeyEvent:', 'Play', player.currentTime());
//再生を再開します
player.play();
} else {
videojs.log('handleKeyEvent:', 'Pause', player.currentTime());
//再生を一時停止します
player.pause();
}
break;
case "MediaRewind":
case "ArrowLeft":
//再生時間を10秒戻します
player.currentTime((player.currentTime() - moveTime) < 0 ? 0 : (player.currentTime() - moveTime));
videojs.log('handleKeyEvent:', 'Rewind', player.currentTime());
break;
case "MediaFastForward":
case "ArrowRight":
//再生時間を10秒進めます
player.currentTime(player.currentTime() + moveTime);
videojs.log('handleKeyEvent:', 'Fast Forward', player.currentTime());
break;
default:
videojs.log('handleKeyEvent:', 'Unhandled event.code =', event.code);
break;
}
}
//Fire TV対応アプリのWebViewからディスパッチされるキーボードイベントをリッスンします
window.addEventListener("keyup", (event) => {
//押下したキーを離すと背景色がリセットされます
document.body.style.background = "";
if (event.key !== undefined) {
console.log('keyup:', 'event.key =', event.key);
//KeyboardEvent.keyを使用してイベントを処理します
handleKeyEvent(event);
}
});
window.addEventListener("keydown", (event) => {
//キーを押すと背景色が変わります
document.body.style.background = "green";
});
WebViewからのタッチイベントのディスパッチ
タッチジェスチャー操作を利用できないFire TVデバイスもあるため、アプリの基本的な動作について完全にタッチジェスチャーに頼ることはできません。しかしながら、ラッパーアプリにタッチベースの操作を追加すると、利便性と魅力を大幅に向上させることが可能です。
タッチジェスチャーが発生するのは、ユーザーがタッチ対応デバイスの画面に1本または複数の指を置き、アプリがそのタッチパターンを特定のジェスチャーとして解釈したときです。ジェスチャーの検出には、次の2つの段階があります。
以下の例は、アプリのActivityクラスのタッチイベントをインターセプトして、onTouchEvent()コールバックをオーバーライドする方法を示しています。こちらのスニペットでは、getActionMasked()を使用して、イベントパラメーターからユーザーが実行したアクションを抽出します。これにより、処理すべきジェスチャーが発生したかどうかを判断するのに必要な未加工データを取得できます。
MainActivity.java
public class MainActivity extends Activity {
...
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getActionMasked();
switch(action) {
case (MotionEvent.ACTION_DOWN):
Log.d(TAG,"Action was DOWN");
return true;
case (MotionEvent.ACTION_MOVE):
Log.d(TAG,"Action was MOVE");
return true;
case (MotionEvent.ACTION_UP):
Log.d(TAG,"Action was UP");
return true;
case (MotionEvent.ACTION_CANCEL):
Log.d(TAG,"Action was CANCEL");
return true;
case (MotionEvent.ACTION_OUTSIDE) :
Log.d(TAG,"Movement occurred outside bounds of current screen element");
return true;
default:
return super.onTouchEvent(event);
}
}
}
JavaScriptでのタッチ操作処理
タッチイベントはマウスイベントと似ていますが、同時タッチや、タッチ面上の複数位置でのタッチをサポートしている点が違います。TouchEventインターフェイスは、現在アクティブなすべてのタッチポイントをカプセル化します。Touchインターフェイスは、単一のタッチポイントを表し、ブラウザのビューポートに対するタッチポイントの位置などの情報を保持します。
タッチイベントは、3つのインターフェイス(Touch、TouchEvent、TouchList)と次のイベントタイプで構成されます。
前述のHTML5ビデオプレーヤーの例を続けると、ここでは、プレーヤーのメディアコントロール内で定義されたボタン要素に基本的なタッチ操作を追加することができます。playPauseControl要素はプレーヤーの再生/一時停止ボタンを処理し、muteControlは音声ミュート/ミュート解除ボタンを処理します。JavaScriptタッチイベントの詳細については、MDNウェブドキュメントを参照してください。また、アプリにタッチイベントを追加する方法については、Google Web Fundamentalsを参照してください。
index.html
const player = videojs("video", {...});
//Fire TV対応アプリのWebViewからディスパッチされるタッチイベントをリッスンします
//再生/一時停止
let playPauseControl = document.getElementsByClassName('vjs-play-control')[0];
playPauseControl.addEventListener("click touchstart", (event) => {
videojs.log('playPauseControl', 'touched');
});
//ミュート/ミュート解除
let muteControl = document.getElementsByClassName('vjs-mute-control')[0];
muteControl.addEventListener("click touchstart", (event) => {
videojs.log('muteControl', 'touched');
});
まとめ
既存のHTML5ベースのウェブアプリをクライアントアプリの一部としてFire TVに配信する場合は、本ガイドを利用して、優れたユーザーエクスペリエンスの実現に必要な操作とナビゲーションを実装することができます。Fireデバイス向けにウェブアプリを最適化する方法の詳細については、Amazon開発者ポータルを参照してください。
詳細については、以下のドキュメントを参照してください。