Google アシスタントとメディアアプリ

Google アシスタントを使用すると、Google Home やスマートフォンなど、さまざまなデバイスを音声コマンドで操作できます。メディア関連コマンド(「ビヨンセの曲をかけて」など)を理解する機能が組み込まれており、一時停止、スキップ、早送り、評価などのメディア コントロールが可能です。

Google アシスタントでは、Android メディアアプリとの通信にメディア セッションが使用されます。また、インテントサービスを使用してアプリの起動、再生開始を行えます。アプリの実装にあたっては、このページで説明する機能をすべて含めることをおすすめします。

メディア セッションの使用

再生を開始したら Google アシスタントによるトランスポート コントロールの操作が可能になるよう、すべてのオーディオ アプリと動画アプリでメディア セッションを実装する必要があります。

Google アシスタントで使用されるのはこのセクションに挙げたアクションのみですが、準備 API と再生 API をすべて実装して他のアプリとの互換性を確保することをおすすめします。サポートしていないアクションについては、メディア セッション コールバックで ERROR_CODE_NOT_SUPPORTED を使用してエラーを返すだけでかまいません。

メディアとトランスポートのコントロールを可能にするには、アプリの MediaSession オブジェクトのフラグを次のように設定します。

Kotlin

session.setFlags(
        MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or
        MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS
)

Java

session.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
    MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);

アプリのメディア セッションでは、サポートするアクションを宣言し、それに対応するメディア セッション コールバックを実装する必要があります。サポートするアクションは、setActions() で宣言します。

メディア セッションの設定にあたっては、Universal Android Music Player サンプル プロジェクトが参考になります。

再生のアクション

サービスから再生を開始するには、メディア セッションに以下の PLAY アクションとそのコールバックが必要です。

アクション コールバック
ACTION_PLAY onPlay()
ACTION_PLAY_FROM_SEARCH onPlayFromSearch()
ACTION_PLAY_FROM_URI(*) onPlayFromUri()

また、以下の PREPARE アクションとそのコールバックも実装する必要があります。

アクション コールバック
ACTION_PREPARE onPrepare()
ACTION_PREPARE_FROM_SEARCH onPrepareFromSearch()
ACTION_PREPARE_FROM_URI(*) onPrepareFromUri()

(*)Google アシスタントの URI ベースのアクションは、Google に URI を提供した会社のもののみが機能します。Google にメディア コンテンツの情報を提供する方法の詳細については、メディア アクションをご覧ください。

準備 API を実装することで、音声コマンド後の再生待ち時間を短縮できます。メディアアプリで再生待ち時間を短縮するには、余裕時間を利用してコンテンツのキャッシュへの保存とメディア再生の準備を開始します。

検索クエリを解析する

ユーザーが特定のメディア アイテム(「<アプリ名> でジャズを再生して」や「<曲名> を聴く」など)を検索すると、コールバック メソッド onPrepareFromSearch() または onPlayFromSearch() がクエリ パラメータと extras バンドルを受け取ります。

アプリでは音声検索クエリを解析し、次の手順で再生を開始します。

  1. 音声検索から返された extras バンドルと検索クエリ文字列を使用して結果をフィルタする。
  2. その結果に基づいて再生キューを作成する。
  3. 結果の中から最も関連性の高いメディア アイテムを再生する。

onPlayFromSearch() メソッドは、音声検索からのより詳細な情報を含む extras パラメータを受け取ります。これらの extras は、再生するアプリ内の音声コンテンツを見つけるのに役立ちます。検索結果から再生するデータが見つからない場合は、生の検索クエリを解析するロジックを実装することで、クエリに沿った曲を再生できます。

Android Automotive OS と Android Auto では、次の extras がサポートされています。

次のコード スニペットは、MediaSession.Callback の実装で onPlayFromSearch() メソッドをオーバーライドし、音声検索クエリを解析して再生を開始する方法を示しています。

Kotlin

override fun onPlayFromSearch(query: String?, extras: Bundle?) {
    if (query.isNullOrEmpty()) {
        // The user provided generic string e.g. 'Play music'
        // Build appropriate playlist queue
    } else {
        // Build a queue based on songs that match "query" or "extras" param
        val mediaFocus: String? = extras?.getString(MediaStore.EXTRA_MEDIA_FOCUS)
        if (mediaFocus == MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE) {
            isArtistFocus = true
            artist = extras.getString(MediaStore.EXTRA_MEDIA_ARTIST)
        } else if (mediaFocus == MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE) {
            isAlbumFocus = true
            album = extras.getString(MediaStore.EXTRA_MEDIA_ALBUM)
        }

        // Implement additional "extras" param filtering
    }

    // Implement your logic to retrieve the queue
    var result: String? = when {
        isArtistFocus -> artist?.also {
            searchMusicByArtist(it)
        }
        isAlbumFocus -> album?.also {
            searchMusicByAlbum(it)
        }
        else -> null
    }
    result = result ?: run {
        // No focus found, search by query for song title
        query?.also {
            searchMusicBySongTitle(it)
        }
    }

    if (result?.isNotEmpty() == true) {
        // Immediately start playing from the beginning of the search results
        // Implement your logic to start playing music
        playMusic(result)
    } else {
        // Handle no queue found. Stop playing if the app
        // is currently playing a song
    }
}

Java

@Override
public void onPlayFromSearch(String query, Bundle extras) {
    if (TextUtils.isEmpty(query)) {
        // The user provided generic string e.g. 'Play music'
        // Build appropriate playlist queue
    } else {
        // Build a queue based on songs that match "query" or "extras" param
        String mediaFocus = extras.getString(MediaStore.EXTRA_MEDIA_FOCUS);
        if (TextUtils.equals(mediaFocus,
                MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE)) {
            isArtistFocus = true;
            artist = extras.getString(MediaStore.EXTRA_MEDIA_ARTIST);
        } else if (TextUtils.equals(mediaFocus,
                MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE)) {
            isAlbumFocus = true;
            album = extras.getString(MediaStore.EXTRA_MEDIA_ALBUM);
        }

        // Implement additional "extras" param filtering
    }

    // Implement your logic to retrieve the queue
    if (isArtistFocus) {
        result = searchMusicByArtist(artist);
    } else if (isAlbumFocus) {
        result = searchMusicByAlbum(album);
    }

    if (result == null) {
        // No focus found, search by query for song title
        result = searchMusicBySongTitle(query);
    }

    if (result != null && !result.isEmpty()) {
        // Immediately start playing from the beginning of the search results
        // Implement your logic to start playing music
        playMusic(result);
    } else {
        // Handle no queue found. Stop playing if the app
        // is currently playing a song
    }
}

アプリで音声検索を使用した音声コンテンツの再生を実装する方法の詳しい例については、Universal Android Music Player サンプルをご覧ください。

空のクエリの処理

onPrepare()onPlay()onPrepareFromSearch()onPlayFromSearch() が検索クエリなしで呼び出された場合は、メディアアプリでは「現在の」メディアを再生します。現在のメディアがない場合は、アプリでは何かしらの曲(最新の再生リストやランダムキューの曲など)の再生を試みます。ユーザーが「<アプリ名> で音楽をかけて」とだけ要求した場合、Google アシスタントではこのような API が使用されます。

ユーザーが「[アプリ名] で音楽を再生」と話しかけると、Android Automotive OS または Android Auto は、アプリの onPlayFromSearch() メソッドを呼び出し、アプリを起動して音声を再生しようとします。しかし、ユーザーはメディア アイテムの名前を指定していないため、onPlayFromSearch() メソッドには空のクエリ パラメータが渡されます。このような場合、アプリではこれを受けてすぐに音楽(最新の再生リストやランダムキューの曲など)を再生するようにします。

以前の音声操作サポートの宣言

ほとんどの場合、再生アクションを上記のように処理することで、アプリでは必要な再生動作を行えます。ただし、システムによっては、アプリに検索用のインテント フィルタを含める必要があります。このインテント フィルタのサポートは、アプリのマニフェスト ファイルで宣言します。

以下のコードをスマートフォン アプリのマニフェスト ファイルに含めます。

<activity>
    <intent-filter>
        <action android:name=
             "android.media.action.MEDIA_PLAY_FROM_SEARCH" />
        <category android:name=
             "android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

トランスポート コントロール

アプリのメディア セッションがアクティブになると、Google アシスタントでの音声コマンドによる再生コントロールやメディア メタデータの更新が可能になります。これを機能させるために、コードで以下のアクションを有効にし、対応するコールバックを実装します。

アクション コールバック 説明
ACTION_SKIP_TO_NEXT onSkipToNext() 次の動画
ACTION_SKIP_TO_PREVIOUS onSkipToPrevious() 前の曲
ACTION_PAUSE, ACTION_PLAY_PAUSE onPause() 一時停止
ACTION_STOP onStop() 停止
ACTION_PLAY onPlay() 再開
ACTION_SEEK_TO onSeekTo() 30 秒巻き戻し
ACTION_SET_RATING onSetRating(android.support.v4.media.RatingCompat) 高評価/低評価。
ACTION_SET_CAPTIONING_ENABLED onSetCaptioningEnabled(boolean) 字幕オン / オフ

注:

  • 移動コマンドを機能させるには、PlaybackStatestate, position, playback speed, and update time が最新状態でなければなりません。アプリでは、状態が変化したときに setPlaybackState() を呼び出す必要があります。
  • メディアアプリでは、メディア セッション メタデータも最新状態に保つ必要があります。これにより、「今かかっている曲は何?」などの質問がサポートされます。アプリでは、該当の項目(トラックのタイトル、アーティスト、名前など)が変わった時点で setMetadata() を呼び出す必要があります。
  • MediaSession.setRatingType() は、アプリがサポートする評価のタイプを示すように設定する必要があり、アプリでは onSetRating() を実装する必要があります。アプリが評価をサポートしていない場合は、評価タイプをRATING_NONEに設定します。

サポートする音声操作は、コンテンツ タイプによって異なる可能性があります。

コンテンツ タイプ 必要な措置
音楽

サポート必須: 再生、一時停止、停止、次へスキップ、前へスキップ

サポートを強く推奨: Seek To

ポッドキャスト

サポート必須: 再生、一時停止、停止、シーク

サポートを推奨: 次へスキップ、前へスキップ

オーディオブック サポート必須: 再生、一時停止、停止、シーク
ラジオ サポート必須: 再生、一時停止、停止
ニュース サポート必須: 再生、一時停止、停止、次へスキップ、前へスキップ
動画

サポート必須: 再生、一時停止、停止、シーク、巻き戻し、早送り

強く推奨されるサポート: 次へスキップ、前へスキップ

上記の操作は、プロダクトの提供内容で可能な限りサポートする必要がありますが、他の操作にも適切に対応する必要があります。たとえば、プレミアム ユーザーのみが前のアイテムに戻ることができる場合、無料プランのユーザーがアシスタントに前のアイテムに戻るようリクエストすると、エラーが発生する可能性があります。詳細については、エラー処理のセクションをご覧ください。

試してみる音声クエリの例

次の表に、実装のテストで使用するサンプルクエリの概要を示します。

MediaSession コールバック 「OK Google」と話しかける
onPlay()

Google Play

「再開して」

onPlayFromSearch()
onPlayFromUri()
音楽

(アプリ名)で音楽や曲を再生して。」これは空のクエリです。

(曲 | アーティスト | アルバム | ジャンル | 再生リスト)(アプリ名) で再生して。」

ラジオ (周波数 | 局)(アプリ名) で再生して。」
Audiobook

(アプリ名)でオーディオブックを読んで。」

(アプリ名)(オーディオブック)を読んで。」

ポッドキャスト (アプリ名)(ポッドキャスト)を再生して。」
onPause() 「一時停止して」
onStop() 「停止して」
onSkipToNext() 「次の(曲 | エピソード | トラック)。」
onSkipToPrevious() 「前の(曲 | エピソード | トラック)。」
onSeekTo()

「再起動。」

## 秒早送りして。」

## 分戻って。」

N/A(MediaMetadata を最新の状態に保つ) 「何を再生中か教えて?」

エラー

メディア セッションで発生したエラーは、Google アシスタントにより処理されてユーザーに報告されます。メディア セッションの操作で説明されているように、メディア セッションでは PlaybackState のトランスポート状態とエラーコードを正しく更新するようにしてください。getErrorCode() から返されるエラーコードはすべて、Google アシスタントにより認識されます。

よくある誤った対応

正しく処理する必要があるエラーケースの例を次に示します。

  • ユーザーはログインする必要があります
    • PlaybackState エラーコードを ERROR_CODE_AUTHENTICATION_EXPIRED に設定します。
    • PlaybackState エラー メッセージを設定します。
    • 再生に必要な場合は、PlaybackState 状態を STATE_ERROR に設定します。それ以外の場合は、残りの PlaybackState をそのままにします。
  • ユーザーが利用できないアクションをリクエストする
    • PlaybackState エラーコードを適切に設定します。たとえば、アクションがサポートされていない場合は PlaybackStateERROR_CODE_NOT_SUPPORTED に設定し、アクションがログイン保護されている場合は ERROR_CODE_PREMIUM_ACCOUNT_REQUIRED に設定します。
    • PlaybackState エラー メッセージを設定します。
    • 残りの PlaybackState はそのままにします。
  • ユーザーがアプリで利用できないコンテンツをリクエストする
    • PlaybackState エラーコードを適切に設定します。たとえば、ERROR_CODE_NOT_AVAILABLE_IN_REGION を使用します。
    • PlaybackState エラー メッセージを設定します。
    • PlaybackSate 状態を STATE_ERROR に設定して再生を中断します。それ以外の場合は、残りの PlaybackState をそのままにします。
  • ユーザーが、完全一致するコンテンツがないコンテンツをリクエストしている。たとえば、プレミアム ティアのユーザーのみが利用できるコンテンツを無料ティアのユーザーがリクエストした場合などです。
    • エラーを返すのではなく、再生する類似のものを優先的に探すことをおすすめします。アシスタントは、再生が開始される前に最も関連性の高い音声応答を話す処理を行います。

インテントを使用した再生

Google アシスタントは、ディープリンクを含むインテントを送信することで、オーディオ アプリや動画アプリを起動して再生を開始できます。

インテントとそのディープリンクは、以下のさまざまなソースから取得できます。

  • Google アシスタントは、モバイルアプリを起動する場合、Google 検索を使用して、視聴アクションにリンクを提供するマークアップ コンテンツを取得できます。
  • Google アシスタントで TV アプリを起動する場合、アプリに TV 検索プロバイダを含めてメディア コンテンツの URI を公開するようにします。Google アシスタントからは、ディープリンクの URI とオプションのアクションを含むインテントを返すよう、コンテンツ プロバイダにクエリが送信されます。クエリによりアクションを含むインテントが返された場合は、そのアクションと URI が Google アシスタントからアプリに送られます。プロバイダからアクションが指定されなかった場合は、Google アシスタントによりそのインテントに ACTION_VIEW が追加されます。

Google アシスタントからアプリに送信されるインテントには、Google アシスタントにより値が trueEXTRA_START_PLAYBACK が追加されます。アプリが EXTRA_START_PLAYBACK を含むインテントを受信すると、再生が開始されます。

アクティブ時のインテントの処理

ユーザーは、アプリで前回リクエストされたコンテンツが再生されている間も、別のものを再生するよう Google アシスタントにリクエストできます。つまり、再生アクティビティがすでに起動されアクティブになっている間も、アプリでは再生を開始するための新たなインテントを受け取る可能性があるということです。

ディープリンクを含むインテントをサポートするアクティビティでは、新しいリクエストを処理するために、onNewIntent() をオーバーライドします。

再生を開始するとき、Google アシスタントによりインテントにフラグが追加されてアプリに送信される場合があります。特に、FLAG_ACTIVITY_CLEAR_TOPFLAG_ACTIVITY_NEW_TASK のいずれか、または両方が追加される場合があります。これらのフラグに対しては、コードで何かをする必要はありませんが、Android システム側で応答がなされます。これにより、前の URI の再生中に、アプリが 2 番目の再生リクエストを新たな URI で受け取った場合に、アプリの動作に影響が及ぶ可能性があります。この場合にアプリがどのように反応するか、テストすることをおすすめします。たとえば、adb コマンドライン ツールを使用して、状況をシミュレートできます(定数 0x14000000 は、2 つのフラグのビット単位論理和です)。

adb shell 'am start -a android.intent.action.VIEW --ez android.intent.extra.START_PLAYBACK true -d "<first_uri>"' -f 0x14000000
adb shell 'am start -a android.intent.action.VIEW --ez android.intent.extra.START_PLAYBACK true -d "<second_uri>"' -f 0x14000000

サービスからの再生

アプリに Google アシスタントから接続できる media browser service がある場合、Google アシスタントは、そのサービスの media session と通信することでアプリの起動を行えます。メディア ブラウザ サービスからはアクティビティを起動すべきではありません。setSessionActivity() で定義した PendingIntent に基づいて、Google アシスタントによりアクティビティが起動されます。

メディア ブラウザ サービスを初期化するときは、必ず MediaSession.Token を設定してください。初期化中も含め、サポートする再生アクションを常に設定しておいてください。Google アシスタントによる最初の再生コマンドの送信は、すでにメディアアプリで再生アクションの設定がなされているものとして行われます。

サービスからの起動のために、Google アシスタントによりメディア ブラウザ クライアント API が実装されます。これにより、TransportControls 呼び出しが行われ、アプリのメディア セッションで PLAY アクション コールバックがトリガーされます。

次の図は、Google アシスタントによって生成される呼び出しの順序と、対応するメディア セッション コールバックを示しています。(準備コールバックは、アプリがサポートしている場合にのみ送信されます)。すべての呼び出しは非同期です。Google アシスタントがアプリからの応答を待つことはありません。

メディア セッションを使用した再生の開始

ユーザーが再生のための音声コマンドを発行すると、Google アシスタントから短いアナウンスが返されます。アナウンスが終わるとすぐに、Google アシスタントから PLAY アクションが発行されます。特定の再生状態を待つことはありません。

アプリが ACTION_PREPARE_* アクションをサポートしている場合は、Google アシスタントではアナウンス開始前に PREPARE アクションが呼び出されます。

MediaBrowserService への接続

サービスを使用してアプリを起動するには、Google アシスタントからアプリの MediaBrowserService に接続して MediaSession.Token を取得できる必要があります。接続リクエストは、このサービスの onGetRoot() メソッドで処理されます。リクエストの処理には、次の 2 つの方法があります。

  • すべての接続リクエストを受け入れる場合
  • Google アシスタント アプリからの接続リクエストのみを受け入れる

すべての接続リクエストを受け入れる場合

Google アシスタントからメディア セッションにコマンドを送信できるようにするには、BrowserRoot を返す必要があります。最も簡単な方法は、すべての MediaBrowser アプリが MediaBrowserService に接続できるようにすることです。それには、null 以外の BrowserRoot を返す必要があります。Universal Music Player の該当部分のコードは次のとおりです。

Kotlin

override fun onGetRoot(
        clientPackageName: String,
        clientUid: Int,
        rootHints: Bundle?
): BrowserRoot? {

    // To ensure you are not allowing any arbitrary app to browse your app's contents, you
    // need to check the origin:
    if (!packageValidator.isCallerAllowed(this, clientPackageName, clientUid)) {
        // If the request comes from an untrusted package, return an empty browser root.
        // If you return null, then the media browser will not be able to connect and
        // no further calls will be made to other media browsing methods.
        Log.i(TAG, "OnGetRoot: Browsing NOT ALLOWED for unknown caller. Returning empty "
                + "browser root so all apps can use MediaController. $clientPackageName")
        return MediaBrowserServiceCompat.BrowserRoot(MEDIA_ID_EMPTY_ROOT, null)
    }

    // Return browser roots for browsing...
}

Java

@Override
public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid,
                             Bundle rootHints) {

    // To ensure you are not allowing any arbitrary app to browse your app's contents, you
    // need to check the origin:
    if (!packageValidator.isCallerAllowed(this, clientPackageName, clientUid)) {
        // If the request comes from an untrusted package, return an empty browser root.
        // If you return null, then the media browser will not be able to connect and
        // no further calls will be made to other media browsing methods.
        LogHelper.i(TAG, "OnGetRoot: Browsing NOT ALLOWED for unknown caller. "
                + "Returning empty browser root so all apps can use MediaController."
                + clientPackageName);
        return new MediaBrowserServiceCompat.BrowserRoot(MEDIA_ID_EMPTY_ROOT, null);
    }

    // Return browser roots for browsing...
}

Google アシスタント アプリのパッケージと署名を受け入れる場合

Google アシスタントからメディア ブラウザ サービスへの接続を明示的に許可するには、パッケージ名と署名を確認します。アプリでは、MediaBrowserService の onGetRoot メソッドでパッケージ名を受け取ります。Google アシスタントからメディア セッションにコマンドを送信できるようにするには、BrowserRoot を返す必要があります。Universal Music Player サンプルには、既知のパッケージ名と署名のリストが保持されています。以下は、Google アシスタントにより使用されるパッケージ名と署名です。

<signature name="Google" package="com.google.android.googlequicksearchbox">
    <key release="false">19:75:b2:f1:71:77:bc:89:a5:df:f3:1f:9e:64:a6:ca:e2:81:a5:3d:c1:d1:d5:9b:1d:14:7f:e1:c8:2a:fa:00</key>
    <key release="true">f0:fd:6c:5b:41:0f:25:cb:25:c3:b5:33:46:c8:97:2f:ae:30:f8:ee:74:11:df:91:04:80:ad:6b:2d:60:db:83</key>
</signature>

<signature name="Google Assistant on Android Automotive OS" package="com.google.android.carassistant">
    <key release="false">17:E2:81:11:06:2F:97:A8:60:79:7A:83:70:5B:F8:2C:7C:C0:29:35:56:6D:46:22:BC:4E:CF:EE:1B:EB:F8:15</key>
    <key release="true">74:B6:FB:F7:10:E8:D9:0D:44:D3:40:12:58:89:B4:23:06:A6:2C:43:79:D0:E5:A6:62:20:E3:A6:8A:BF:90:E2</key>
</signature>