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

Google アシスタントを使用すると、音声コマンドを使用して、Google Home やスマートフォンなど、多くのデバイスを制御できます。メディア コマンド(「ビヨンセで何かを再生」)を理解する機能が組み込まれており、メディア コントロール(一時停止、スキップ、早送り、高評価など)をサポートしています。

アシスタントは、メディア セッションを使用して Android メディアアプリと通信します。インテントまたはサービスを使用してアプリを起動し、再生を開始できます。最善の結果を得るには、このページに記載されている機能をすべてアプリに実装する必要があります。

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

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

なお、アシスタントはこのセクションに記載されているアクションのみを使用しますが、準備 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() コールバック メソッドがクエリ パラメータとエクストラのバンドルを受け取ります。

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

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

onPlayFromSearch() メソッドは、音声検索からのさらに詳しい情報を含むエクストラ パラメータを受け取ります。これらのエクストラは、再生するアプリ内の音声コンテンツを見つけるのに役立ちます。検索結果でこのデータを提供できない場合は、生の検索クエリを解析し、クエリに基づいて適切なトラックを再生するロジックを実装できます。

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() が呼び出された場合、メディアアプリは「現在の」メディアを再生する必要があります。現在のメディアがない場合、アプリは何かを再生しようと試みます。たとえば、最新のプレイリストやランダムキューの曲などです。ユーザーが「[アプリ名] で音楽を再生して」と尋ねると、アシスタントはこれらの 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>

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

アプリのメディア セッションがアクティブになると、アシスタントは音声コマンドを発行して再生を操作し、メディア メタデータを更新できます。これを行うには、コードで以下のアクションを有効にして、対応するコールバックを実装する必要があります。

行動 コールバック 説明
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()

「再生して」

「再開して」

onPlayFromSearch()
onPlayFromUri()
音楽

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

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

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

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

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

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

「再起動して」

## 秒早送りして」

## 分前に戻って」

該当なし(MediaMetadata を最新の状態に維持してください) 「何を再生中か教えて?」

エラー

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

よくあるケース

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

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

アシスタントは、アプリに送信するインテントに、値 true を持つ追加の EXTRA_START_PLAYBACK を追加します。アプリは、EXTRA_START_PLAYBACK のインテントを受信したときに再生を開始する必要があります。

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

ユーザーは、アプリが以前のリクエストのコンテンツを再生している間に、アシスタントに何かを再生するよう要求できます。つまり、再生アクティビティがすでに起動されてアクティブになっている間、アプリは新しいインテントを受信して再生を開始できます。

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

アシスタントは、再生の開始時に、アプリに送信するインテントに追加のフラグを追加する場合があります。具体的には、FLAG_ACTIVITY_CLEAR_TOPFLAG_ACTIVITY_NEW_TASK、またはその両方を追加できます。これらのフラグをコードで処理する必要はありませんが、Android システムはそれらに応答します。これは、前の URI が再生されている間に新しい URI を持つ 2 つ目の再生リクエストが届いた場合、アプリの動作に影響する可能性があります。この場合にアプリがどのように反応するか、テストすることをおすすめします。adb コマンドライン ツールを使用して状況をシミュレートできます(定数 0x14000000 は、2 つのフラグのブール値のビット単位 OR です)。

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

サービスからの再生

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

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

サービスから開始するために、アシスタントはメディア ブラウザ クライアント API を実装します。アプリのメディア セッションで PLAY アクション コールバックをトリガーする TransportControls 呼び出しを実行します。

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

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

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

アプリが ACTION_PREPARE_* アクションをサポートしている場合、アシスタントはお知らせを開始する前に PREPARE アクションを呼び出します。

MediaBrowserService への接続

サービスを使用してアプリを起動するには、アシスタントがアプリの 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>