手順8: ディレクティブに応答する
手順8: ディレクティブに応答する
アプリのみのVSK統合
→
→
→
→
→
→
→
→
→
マニフェストにBroadcastReceiverを追加すると、指定したBroadcastReceiverクラス(AlexaDirectiveReceiver
など)に、Alexaディレクティブを含むインテントがFire TVから送信されるようになります。このBroadcastReceiverクラスで、Alexaからアプリに送信されるディレクティブを受け取り、処理します。このクラスの処理のロジックは、開発者自身で開発する必要があります。
サンプルアプリに関する注意事項サンプルアプリでは既にディレクティブに応答しているため、この手順でコーディングを行う必要はありません。サンプルアプリでのディレクティブの処理は、
AlexaDirectiveReceiver
クラス(
java/com/example/vskfiretv/company/receiver
にあります)で確認できます。サンプルアプリの処理ロジックの説明については、このトピックの末尾付近にある
[サンプルアプリでのディレクティブの処理] ボタンをクリックして、セクションを展開してください。
VSKディレクティブ
ユーザーがAlexaを介して音声でリクエストを行うと、VSKディレクティブがVSK Agentを介してアプリに送信されます。ディレクティブの詳細については、APIリファレンスを参照してください。
BroadcastReceiverでディレクティブを受け取ったら、アプリでディレクティブのリクエストに対応する必要があります。これは、一般的な設計のMVVM(Model-View-ViewModel)アプリでは通常、非常に単純です。というのも、ほとんどのアプリにマスタービューモデルがあり、リモコンのボタン押下や検索クエリがアプリにルーティングされるためです。通常、アプリでVSKディレクティブを実行するために必要なことは、BroadcastReceiverからビューモデルに関連文字列を渡すことだけです。
たとえば、ユーザーが「コメディ映画を探して」と発話すると、SearchAndDisplayResults
ディレクティブを受け取るでしょう。これは、MediaType: Movie
およびGenre: Comedies
のディレクティブです。アプリで検索がサポートされている場合は、ユーザーがリモコンで入力フィールドにテキストを入力後、入力された内容を検索するためのメカニズムが既にあります。同じ検索クエリのメカニズムをVSKでも使用して、ディレクティブから解析された「コメディ映画」に対して検索を実行することができます。
ディレクティブへの応答
Alexaが送信するディレクティブは、宣言した機能によって異なります。BroadcastReceiverクラスをカスタマイズして、アプリ内でディレクティブを処理します(手順7: BroadcastReceiverを追加するを参照してください)。具体的な方法は次のとおりです。
- ディレクティブの
payload
(ディレクティブで受け取ったインテント内にあります)を解析して、リクエストされた動作を実行するロジックをアプリに追加します。
- 正常に処理されたかどうかを示すディレクティブをVSK Agentに返します。ブロードキャストインテント内のPendingIntentにこのステータスを含めます。このステータスはブール型で、
true
(成功の場合)またはfalse
(失敗の場合)のいずれかの値を取ります。レスポンスコードのサンプルは、手順7のBroadcastReceiver Javaクラスの追加で確認できます。
認定に必須のディレクティブ
Alexaはさまざまなリクエストに対応するディレクティブを送信できます。そのすべてに対応する必要があるでしょうか。 アプリに処理できないディレクティブがある場合、そのアプリは認定されないのでしょうか。 各ディレクティブの発話のリストで、サポートする必要のある発話には、その発話の下に「認定に必須」と記載されています。
必須の発話には、タイトル、俳優、ジャンル、シリーズの検索に加え、トランスポートコントロールの発話が含まれます。発話に必須のマークが付いていても、アプリがその機能をサポートしていない場合、そのアプリが認定を受けられないということはなく、その要件は無視することができます。ChangeChannel
のようにオプションのディレクティブの場合、そのディレクティブに対応する選択をした場合にのみ、必須のディレクティブが要件となります。
APIリファレンス
上記の各ディレクティブの実装と処理について求められるロジックは、APIリファレンスに記載されています。Alexaでは、受信するディレクティブごとに特定のレスポンスとアクションが想定されています。詳細については、次のリンク先を参照してください。
その他のインターフェース(非推奨)を実装する場合は、以下を参照してください。
発話リファレンス
Fire TVデバイスがサポートするさまざまな発話(ロケールごとの各フレーズを含む)の詳細については、発話リファレンスを参照してください。RemoteVideoPlayer
の発話については、以下を参照してください。
その他のインターフェースの発話については、以下を参照してください。
サンプルアプリに関する注意事項
サンプルアプリでのディレクティブの処理方法の詳しい説明については、次のセクションを展開してください。
サンプルアプリでのディレクティブの処理
VSKディレクティブを処理するコードを記述するうえで、サンプルアプリのAlexaDirectiveReceiver
クラスではこれらのディレクティブをどのように処理しているかを詳しく確認しておくと役立つ可能性があります。
VSK Agentでは、ディレクティブをインテントにパッケージ化して、onReceive
メソッドに渡しています。
public void onReceive(final Context context, final Intent intent) {
Log.i(TAG, MessageFormat.format("VSK Agentからのインテントの処理: {0}", intent));
if(context == null || intent == null) {
return;
}
//...
}
このコードでは、ディレクティブからいくつかのプロパティを取得し、それぞれをdirectiveNameSpace
、directiveName
、directivePayload
、directivePlayloadVersion
という名前の文字列に設定しています。
if (VSKIntentConstants.ACTION_ALEXA_DIRECTIVE.equals(intent.getAction())) {
final String directiveNameSpace = intent.getStringExtra(VSKIntentConstants.EXTRA_DIRECTIVE_NAMESPACE);
final String directiveName = intent.getStringExtra(VSKIntentConstants.EXTRA_DIRECTIVE_NAME);
final String directivePayload = intent.getStringExtra(VSKIntentConstants.EXTRA_DIRECTIVE_PAYLOAD);
final String directivePayloadVersion = intent.getStringExtra(VSKIntentConstants.EXTRA_DIRECTIVE_PAYLOAD_VERSION);
//...
}
EXTRA_DIRECTIVE_NAMESPACE
には、VSK APIインターフェースの名前(RemoteVideoPlayer
など)が格納されています。EXTRA_DIRECTIVE_NAME
は、インターフェース(SearchAndDisplayResults
など)のディレクティブです。EXTRA_DIRECTIVE_PAYLOAD
では、ディレクティブのpayload
プロパティを取得します。EXTRA_DIRECTIVE_PAYLOAD_VERSION
では、インターフェースのバージョン(3
など)を取得します(ディレクティブ全体を出力することはできません。出力できるのは、VSK Agentが公開するさまざまなプロパティのみです)。
一連のifの条件文で適切な関数を呼び出して、受け取ったディレクティブの種類に応じてディレクティブを処理します。
if (directiveName != null) {
if ("SearchAndPlay".equals(directiveName)) {
handleSearchAndPlay(directivePayload);
} else if ("SearchAndDisplayResults".equals(directiveName)) {
handleSearchAndDisplayResults(directivePayload);
} else if ("pause".equals(directiveName)) {
handlePause();
} else if ("play".equals(directiveName)) {
handlePlay();
} else if ("stop".equals(directiveName)) {
handleStop();
} else if ("next".equals(directiveName)) {
handleNext();
} else if ("previous".equals(directiveName)) {
handlePrevious();
} else if ("fastForward".equals(directiveName)) {
handleFastForward();
} else if ("rewind".equals(directiveName)) {
handleRewind();
} else if ("startOver".equals(directiveName)) {
handleStartOver();
} else if ("adjustSeekPosition".equals(directiveName)) {
handleAdjustSeekPosition(directivePayload);
} else if ("ChangeChannel".equals(directiveName)) {
handleChangeChannel();
} else if ("SendKeystroke".equals(directiveName)) {
handleSendKeystroke();
} else if("Test".equals(directiveName)) {
handleTestDirective();
} else {
Log.i(TAG, "不明なAlexaディレクティブです。エラーレスポンスのインテントをVSK Agentに送信します");
sendPendingIntentResponse(context, intent, false);
return;
}
//...
}
SearchAndPlay
ディレクティブを受信した場合、handleSearchAndPlay
関数が呼び出され、directivePayload
が関数のパラメーターとして渡されます。SearchAndDisplayResults
ディレクティブを受信した場合は、handleSearchAndDisplayResults
関数が呼び出され、directivePayload
が関数のパラメーターとして渡されます。以下同様です。
directivePayload
の名前はすべて、インターフェース間で一意になっているため、directiveNameSpace
とdirectivePayload
を組み合わせる必要はありません。
SearchAndPlayディレクティブの処理
サンプルアプリでは、handleSearchAndPlay
関数でdirectivePayload
をgetMovieFromDirectivePayload
という別のメソッドに渡して、映画のタイトルmovieName
を取り出し、このmovieName
を再生のインテントに渡します。このインテントがPlaybackActivity
に送られ、メディアが再生されます。
private void handleSearchAndPlay(final String directivePayload) {
Log.i(TAG, "SearchAndPlayディレクティブの処理...");
final Movie movieToBePlayed = getMovieFromDirectivePayload(directivePayload);
final String movieName = movieToBePlayed.getTitle();
Log.d(TAG, "ランダムに選択した映画を再生します" + movieName);
final Intent playIntent = new Intent();
final String packageName = VSKReferenceApplication.getInstance().getApplicationContext().getPackageName();
playIntent.setClassName(packageName, packageName + ".PlaybackActivity");
playIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// PlaybackActivityは、映画が現在選択されていることを想定しています。選択されていない場合に備えてここで設定します
playIntent.putExtra(DetailsActivity.MOVIE, movieToBePlayed);
VSKReferenceApplication.getInstance().getApplicationContext().startActivity(playIntent);
Log.i(TAG, "SearchAndPlayディレクティブの処理が完了しました");
}
通常、getMovieFromDirectivePayload
関数では、データソースを照合して該当する映画の名前を見つけますが、このサンプルアプリでは、コードを単純にするため、MovieList
の一覧にある最初の映画を取得します。
private Movie getMovieFromDirectivePayload(final String directivePayload) {
// ここでディレクティブのペイロードを処理し、再生対象の適切な映画のオブジェクトを作成します
// デモンストレーションのために、サンプルの映画リストの最初のアイテムを取得します。movie IDには対応していません
final Movie someMovie = MovieList.getList().get(0);
return someMovie;
}
MovieList
は、約5本の映画が格納された配列を1つ持つクラスです(java/com/example/vskfiretv/company
にあります)。
SearchAndDisplayResultsディレクティブの処理
SearchAndDisplayResults
を受信した場合、handleSearchAndDisplayResults
関数が呼び出され、ディレクティブのdirectivePayload
が関数として渡されます。handleSearchAndDisplayResults
関数では、JsonParser
を使用して、ユーザーの検索リクエストの文字起こし(transcribed
)とentities
オブジェクトを取得します。entitiesオブジェクトには、再生するentityオブジェクトの配列が格納されています(Title、Franchise、Actor、Team、MediaTypeなど)。
このコードでは次に、MovieList
クラスからランダムに映画を取得し、その映画からすべてのメタデータを収集して、searchedMovies
オブジェクトに格納します。これをmainActivity
に渡して、ユーザーの検索結果を表示します。
private void handleSearchAndDisplayResults(final String searchPayload) {
Log.i(TAG, "SearchAndDisplayResultsディレクティブの処理...");
final JsonParser jsonParser = new JsonParser();
final JsonElement searchPayloadJsonTree = jsonParser.parse(searchPayload);
if(searchPayloadJsonTree.isJsonObject()) {
final JsonObject searchPayloadJsonObject = searchPayloadJsonTree.getAsJsonObject();
final JsonObject searchTermJsonObject = searchPayloadJsonObject.getAsJsonObject("searchText");
final String searchText = searchTermJsonObject.get("transcribed").getAsString();
final JsonArray searchEntitiesJsonArray = searchPayloadJsonObject.getAsJsonArray("entities");
final Iterator<JsonElement> searchEntitiesIterator = searchEntitiesJsonArray.iterator();
final Random rand = new Random();
final List<Movie> movieList = MovieList.getList();
int moviesCount = movieList.size();
final List<Movie> searchedMovies = new ArrayList<>();
while (searchEntitiesIterator.hasNext()) {
final JsonElement entityElement = searchEntitiesIterator.next();
final JsonObject entity = entityElement.getAsJsonObject();
final String entityValue = entity.get("value").getAsString();
final String entityJsonString = entityElement.toString();
final Movie movie = movieList.get(rand.nextInt(moviesCount));
movie.setTitle(entityValue);
movie.setDescription(entityJsonString);
searchedMovies.add(MovieList.buildMovieInfo(movie.getMovieId(), movie.getTitle(),
movie.getDescription(), movie.getStudio(), movie.getVideoUrl(),
movie.getCardImageUrl(), movie.getBackgroundImageUrl()));
}
final VSKReferenceApplication myReferenceApp = VSKReferenceApplication.getInstance();
final Activity currentActivity = myReferenceApp.getCurrentActivity();
try {
final MainActivity mainActivity = (MainActivity) currentActivity;
Log.i(TAG, MessageFormat.format("{0} の検索結果を表示", searchText));
// これはUIスレッドからのみ呼び出すことができます
mainActivity.runOnUiThread(() -> mainActivity.showResults(searchText, searchedMovies));
} catch (final Exception ex) {
Log.e(TAG, "検索結果をホーム画面に表示できませんでした", ex);
return;
}
} else {
Log.i(TAG, "検索のペイロードに対して無効なJSONです");
}
Log.i(TAG, "SearchAndDisplayResultsディレクティブの処理が完了しました");
}
重要: サンプルアプリのロジックは、簡単なデモを目的としたものであり、実際のアプリで使用する処理ロジックとは異なります。このコード例は、ディレクティブから情報を取得する方法、および、リクエストを処理してアプリ内でアクションを実行し、リクエストに対応する関数にその情報を渡す方法を実際に示すことを目的としています。
アプリでリクエストの処理が完了したら、ディレクティブの処理が正常に完了したことを示すレスポンスをVSK Agentに送信する必要があります。サンプルアプリでは、AlexaDirectiveReceiver
クラスのディレクティブ処理の後にある条件文の中でこのロジックを確認できます。intent
とtrue
のステータスがsendPendingIntentResponse
関数に渡されます。
// ディレクティブの処理が成功した場合は、PendingIntentをVSK Agentに送信します
Log.i(TAG, "成功レスポンスのインテントをVSK Agentに送信します");
sendPendingIntentResponse(context, intent, true);
} else {
Log.i(TAG, "VSK Agentから空のディレクティブを受信しました");
}
ステータスはブール型で、値はtrue
またはfalse
となります(ほかのステータスは使用できません)。
ステータスは、VSKIntentConstants.EXTRA_DIRECTIVE_STATUS
の値として追加され、PendingIntentのextraとして追加されて、VSK Agentに送信されます。
private void sendPendingIntentResponse(final Context context, final Intent intent, final boolean directiveExecutionStatus) {
final PendingIntent pendingIntent = intent.getParcelableExtra(VSKIntentConstants.EXTRA_DIRECTIVE_RESPONSE_PENDING_INTENT);
if(pendingIntent != null) {
final Intent responseIntent = new Intent().putExtra(VSKIntentConstants.EXTRA_DIRECTIVE_STATUS, directiveExecutionStatus);
try {
pendingIntent.send(context, 0, responseIntent);
} catch (final PendingIntent.CanceledException ex) {
Log.e(TAG, "VSK AgentへのPendingIntentの送信でエラーが発生しました", ex);
}
}
}
次のステップ
次の 手順9: アプリに署名してセキュリティプロファイルを構成するに進みます。