Android アプリにキャストを統合する

このデベロッパー ガイドでは、Android Sender SDK を使用して Android 送信側アプリに Google Cast のサポートを追加する方法について説明します。

モバイル デバイスまたはノートパソコンは、再生を制御する送信元 であり、Google Cast デバイスは、テレビにコンテンツを表示する受信側 です。

送信元フレームワーク は、送信元で実行時に存在する Cast クラス ライブラリのバイナリと関連リソースを指します。送信元アプリ またはキャストアプリ は、送信元で実行されているアプリを指します。ウェブ レシーバー アプリ は、Cast 対応デバイスで実行されている HTML アプリケーションを指します。

送信元フレームワークは、非同期コールバック設計を使用して、送信元アプリにイベントを通知し、キャストアプリのライフサイクルのさまざまな状態を遷移させます。

アプリケーションの流れ

送信元 Android アプリの一般的な実行フローは次のとおりです。

  • キャスト フレームワークは、 MediaRouter デバイスの検出をActivityのライフサイクルに基づいて自動的に開始します。
  • ユーザーがキャスト アイコンをクリックすると、フレームワークは検出されたキャスト デバイスのリストを含むキャスト ダイアログを表示します。
  • ユーザーがキャスト デバイスを選択すると、フレームワークはキャスト デバイスでウェブ レシーバー アプリを起動しようとします。
  • フレームワークは、ウェブ レシーバー アプリが起動したことを確認するために、送信元アプリでコールバックを呼び出します。
  • フレームワークは、送信元アプリとウェブ レシーバー アプリの間に通信チャネルを作成します。
  • フレームワークは、通信チャネルを使用して、ウェブ レシーバーでのメディア再生を読み込んで制御します。
  • フレームワークは、送信元とウェブ レシーバーの間でメディア再生の状態を同期します。ユーザーが送信元 UI で操作を行うと、フレームワークはメディア コントロール リクエストをウェブ レシーバーに渡します。ウェブ レシーバーがメディア ステータスの更新を送信すると、フレームワークは送信元 UI の状態を更新します。
  • ユーザーがキャスト アイコンをクリックしてキャスト デバイスから切断すると、フレームワークは送信元アプリをウェブ レシーバーから切断します。

Google Cast Android SDK のすべてのクラス、メソッド、イベントの包括的なリストについては、Google Cast Sender API Reference for Android をご覧ください。 以降のセクションでは、Android アプリに Cast を追加する手順について説明します。

Android マニフェストを構成する

アプリの AndroidManifest.xml ファイルでは、Cast SDK 用に次の要素を構成する必要があります。

uses-sdk

Cast SDK がサポートする最小 Android API レベルとターゲット Android API レベルを設定します。 現在の最小値は API レベル 24、ターゲットは API レベル 35 です。

<uses-sdk
        android:minSdkVersion="24"
        android:targetSdkVersion="35" />

android:theme

最小 Android SDK バージョンに基づいてアプリのテーマを設定します。たとえば、独自のテーマを実装していない場合は、Lollipop より前の最小 Android SDK バージョンを対象とする場合に、Theme.AppCompat のバリアントを使用する必要があります。

<application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme=">;@style/Them<e.AppCompat&>quot; 
       ...
/application

Cast Context を初期化する

フレームワークには、フレームワークのすべてのインタラクションを調整するグローバル シングルトン オブジェクト CastContext があります。

アプリは、 OptionsProvider インターフェースを実装して、 CastContext シングルトンを初期化するために必要なオプションを提供する必要があります。OptionsProvider は、フレームワークの動作に影響するオプションを含む CastOptions のインスタンスを提供します。最も重要なオプションはウェブ レシーバー アプリケーション ID で、検出結果をフィルタし、Cast セッションの開始時にウェブ レシーバー アプリを起動するために使用されます。

Kotlin
class CastOptionsProvider : OptionsProvider {
    override fun getCastOptions(context: Context): CastOptions {
        return Builder()
            .setReceiverApplicationId(context.getString(R.string.app_id))
            .build()
    }

    override fun getAdditionalSessionProviders(context: Context): List<SessionProvider>? {
        return null
    }
}
Java
public class CastOptionsProvider implements OptionsProvider {
    @Override
    public CastOptions getCastOptions(Context context) {
        CastOptions castOptions = new CastOptions.Builder()
            .setReceiverApplicationId(context.getString(R.string.app_id))
            .build();
        return castOptions;
    }
    @Override
    public List<SessionProvider> getAdditionalSessionProviders(Context context) {
        return null;
    }
}

実装された OptionsProvider の完全修飾名を、送信元アプリの AndroidManifest.xml ファイルのメタデータ フィールドとして宣言する必要があります。

<application>
    ...
    <meta-data
        android:name=
            "com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
        android:value="com.foo.CastOpt>i<onsProvider&>quot; /
/application

CastContext は、CastContext.getSharedInstance() が呼び出されたときに遅延初期化されます。

Kotlin
class MyActivity : FragmentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        val castContext = CastContext.getSharedInstance(this)
    }
}
Java
public class MyActivity extends FragmentActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        CastContext castContext = CastContext.getSharedInstance(this);
    }
}

Cast UX ウィジェット

キャスト フレームワークには、Cast デザイン チェックリストに準拠したウィジェットが用意されています。

  • イントロダクション オーバーレイ: フレームワークには、レシーバーが初めて利用可能になったときにキャスト アイコンに注意を促すためにユーザーに表示されるカスタムビュー IntroductoryOverlayが用意されています。送信元アプリでは、 タイトル テキストのテキストと位置をカスタマイズできます。

  • キャスト アイコン: キャスト デバイスの可用性に関係なく、キャスト アイコンが表示されます。 ユーザーがキャスト アイコンを初めてクリックすると、検出されたデバイスのリストが表示されるキャスト ダイアログが表示されます。デバイスが接続されているときにユーザーがキャスト アイコンをクリックすると、現在のメディア メタデータ(タイトル、録音スタジオの名前、サムネイル画像など)が表示されるか、キャスト デバイスから切断できます。「キャスト アイコン」は「キャスト アイコン」と呼ばれることもあります。

  • ミニ コントローラ: ユーザーがコンテンツをキャストしていて、現在の コンテンツ ページまたは拡張コントローラから送信元アプリの別の画面に移動した場合、 画面の下部にミニ コントローラが表示され、現在キャストしているメディア メタデータを確認して 再生を制御できます。

  • 拡張コントローラ: ユーザーがコンテンツをキャストしているときに、メディア通知 またはミニ コントローラをクリックすると、拡張コントローラが起動し、 現在再生中のメディア メタデータが表示され、メディア再生を制御するための ボタンがいくつか表示されます。

  • 通知: Android のみ。ユーザーがコンテンツをキャストしていて、送信元アプリから移動すると、現在キャストしているメディア メタデータと再生コントロールを示すメディア通知が表示されます。

  • ロック画面: Android のみ。ユーザーがコンテンツをキャストしていて、ロック画面に移動すると(またはデバイスがタイムアウトすると)、現在キャストしているメディア メタデータと再生コントロールを示すメディア ロック画面コントロールが表示されます。

次のガイドでは、これらのウィジェットをアプリに追加する方法について説明します。

キャスト アイコンを追加する

Android MediaRouter API は、セカンダリ デバイスでのメディアの表示と再生を可能にするように設計されています。 MediaRouter API を使用する Android アプリには、ユーザーがメディア ルートを選択して、キャスト デバイスなどのセカンダリ デバイスでメディアを再生できるように、ユーザー インターフェースの一部としてキャスト アイコンを含める必要があります。

フレームワークを使用すると、 MediaRouteButtonCast button として簡単に追加できます。まず、メニューを定義する XML ファイルにメニュー項目または MediaRouteButton を追加し、 CastButtonFactory を使用してフレームワークに接続します。

// To add a Cast button, add the following snippet.
// menu.xml
<item
    android:id="@+id/media_route_menu_item"
    android:title="@string/media_route_menu_title"
    app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
 >   app:showAsAction="always" /
Kotlin
// Then override the onCreateOptionMenu() for each of your activities.
// MyActivity.kt
override fun onCreateOptionsMenu(menu: Menu): Boolean {
    super.onCreateOptionsMenu(menu)
    menuInflater.inflate(R.menu.main, menu)
    CastButtonFactory.setUpMediaRouteButton(
        applicationContext,
        menu,
        R.id.media_route_menu_item
    )
    return true
}
Java
// Then override the onCreateOptionMenu() for each of your activities.
// MyActivity.java
@Override public boolean onCreateOptionsMenu(Menu menu) {
    super.onCreateOptionsMenu(menu);
    getMenuInflater().inflate(R.menu.main, menu);
    CastButtonFactory.setUpMediaRouteButton(getApplicationContext(),
                                            menu,
                                            R.id.media_route_menu_item);
    return true;
}

次に、ActivityFragmentActivityを継承している場合は、 MediaRouteButton をレイアウトに追加できます。

// activity_layout.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:gravity="center_vertical&qu>ot;
 <  android:orientation="horizontal" 

   androidx.mediarouter.app.MediaRouteButton
       android:id="@+id/media_route_button"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_wei>gh<t="1&quo>t;
       android:mediaRouteTypes="user"
       android:visibility="gone" /

/LinearLayout
Kotlin
// MyActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_layout)

    mMediaRouteButton = findViewById<View>(R.id.media_route_button) as MediaRouteButton
    CastButtonFactory.setUpMediaRouteButton(applicationContext, mMediaRouteButton)

    mCastContext = CastContext.getSharedInstance(this)
}
Java
// MyActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_layout);

   mMediaRouteButton = (MediaRouteButton) findViewById(R.id.media_route_button);
   CastButtonFactory.setUpMediaRouteButton(getApplicationContext(), mMediaRouteButton);

   mCastContext = CastContext.getSharedInstance(this);
}

テーマを使用してキャスト アイコンの外観を設定するには、 キャスト アイコンをカスタマイズするをご覧ください。

デバイスの検出を構成する

デバイスの検出は CastContextによって完全に管理されます。 送信元アプリは、CastContext を初期化するときにウェブ レシーバー アプリケーション ID を指定します。また、名前空間のフィルタリングをリクエストすることもできます。 supportedNamespacesCastOptionsで設定します。CastContextMediaRouter への参照を内部的に保持し、次の条件で検出プロセスを開始します。

  • デバイスの検出レイテンシと バッテリー使用量のバランスを取るように設計されたアルゴリズムに基づいて、送信元アプリがフォアグラウンドに入ると、検出が自動的に開始されることがあります。
  • キャスト ダイアログが開いています。
  • Cast SDK がキャスト セッションの復元を試行しています。

キャスト ダイアログが閉じられるか、送信元アプリがバックグラウンドに移行すると、検出プロセスは停止します。

Kotlin
class CastOptionsProvider : OptionsProvider {
    companion object {
        const val CUSTOM_NAMESPACE = "urn:x-cast:custom_namespace"
    }

    override fun getCastOptions(appContext: Context): CastOptions {
        val supportedNamespaces: M<utable>ListString = ArrayList()
        supportedNamespaces.add(CUSTOM_NAMESPACE)

        return CastOptions.Builder()
            .setReceiverApplicationId(context.getString(R.string.app_id))
            .setSupportedNamespaces(supportedNamespaces)
            .build()
    }

    override fun getAdditionalSessionProviders(context: Cont<ext): ListSessi>onProvider? {
        return null
    }
}
Java
class CastOptionsProvider implements OptionsProvider {
    public static final String CUSTOM_NAMESPACE = "urn:x-cast:custom_namespace";

    @Override
    public CastOptions getCastOptions(Context appContext) {
  <      >ListString supportedNamespaces = new<> ArrayList();
        supportedNamespaces.add(CUSTOM_NAMESPACE);

        CastOptions castOptions = new CastOptions.Builder()
            .setReceiverApplicationId(context.getString(R.string.app_id))
            .setSupportedNamespaces(supportedNamespaces)
            .build();
        return castOptions;
    }

    @Override
    p<ublic ListSessi>onProvider getAdditionalSessionProviders(Context context) {
        return null;
    }
}

セッション管理の仕組み

Cast SDK では、キャスト セッションという概念が導入されています。キャスト セッションの確立には、デバイスへの接続、ウェブ レシーバー アプリの起動(または参加)、そのアプリへの接続、メディア コントロール チャネルの初期化という手順が含まれます。キャスト セッションとウェブ レシーバーのライフサイクルについて詳しくは、ウェブ レシーバー アプリケーションのライフサイクル ガイド をご覧ください。

セッションはクラス SessionManagerによって管理され、アプリは CastContext.getSessionManager()を介してアクセスできます。個々のセッションは、クラス Sessionのサブクラスで表されます。 たとえば、 CastSession はキャスト デバイスとのセッションを表します。アプリは、現在アクティブな キャスト セッションに SessionManager.getCurrentCastSession()を介してアクセスできます。

アプリは SessionManagerListener クラスを使用して、作成、停止、再開、終了などの セッションイベントをモニタリングできます。セッションがアクティブなときに異常終了または突然終了した場合、フレームワークは自動的に再開を試みます。

セッションは、MediaRouter ダイアログでのユーザー操作に応じて自動的に作成、終了されます。

キャストの開始エラーをより深く理解するために、アプリは CastContext#getCastReasonCodeForCastStatusCode(int) を使用して、セッション開始エラーを CastReasonCodesに変換できます。 セッション開始エラー(CastReasonCodes#CAST_CANCELLEDなど)は意図された動作であり、エラーとして記録しないでください。

セッションの状態の変化を把握する必要がある場合は、SessionManagerListener を実装できます。この例では、ActivityCastSession の可用性をリッスンします。

Kotlin
class MyActivity : Activity() {
    private var mCastSession: CastSession? = null
    private lateinit var mCastContext: CastContext
    private lateinit var mSessionManager: SessionManager
    private val mSessionManagerListener: SessionManagerListener<CastSession> =
        SessionManagerListenerImpl()

    private inner class SessionManagerListenerImpl : SessionManagerListener<CastSession?> {
        override fun onSessionStarting(session: CastSession?) {}

        override fun onSessionStarted(session: CastSession?, sessionId: String) {
            invalidateOptionsMenu()
        }

        override fun onSessionStartFailed(session: CastSession?, error: Int) {
            val castReasonCode = mCastContext.getCastReasonCodeForCastStatusCode(error)
            // Handle error
        }

        override fun onSessionSuspended(session: CastSession?, reason Int) {}

        override fun onSessionResuming(session: CastSession?, sessionId: String) {}

        override fun onSessionResumed(session: CastSession?, wasSuspended: Boolean) {
            invalidateOptionsMenu()
        }

        override fun onSessionResumeFailed(session: CastSession?, error: Int) {}

        override fun onSessionEnding(session: CastSession?) {}

        override fun onSessionEnded(session: CastSession?, error: Int) {
            finish()
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mCastContext = CastContext.getSharedInstance(this)
        mSessionManager = mCastContext.sessionManager
        mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession::class.java)
    }

    override fun onResume() {
        super.onResume()
        mCastSession = mSessionManager.currentCastSession
    }

    override fun onDestroy() {
        super.onDestroy()
        mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession::class.java)
    }
}
Java
public class MyActivity extends Activity {
    private CastContext mCastContext;
    private CastSession mCastSession;
    private SessionManager mSessionManager;
    private SessionManagerListener<CastSession> mSessionManagerListener =
            new SessionManagerListenerImpl();

    private class SessionManagerListenerImpl implements SessionManagerListener<CastSession> {
        @Override
        public void onSessionStarting(CastSession session) {}
        @Override
        public void onSessionStarted(CastSession session, String sessionId) {
            invalidateOptionsMenu();
        }
        @Override
        public void onSessionStartFailed(CastSession session, int error) {
            int castReasonCode = mCastContext.getCastReasonCodeForCastStatusCode(error);
            // Handle error
        }
        @Override
        public void onSessionSuspended(CastSession session, int reason) {}
        @Override
        public void onSessionResuming(CastSession session, String sessionId) {}
        @Override
        public void onSessionResumed(CastSession session, boolean wasSuspended) {
            invalidateOptionsMenu();
        }
        @Override
        public void onSessionResumeFailed(CastSession session, int error) {}
        @Override
        public void onSessionEnding(CastSession session) {}
        @Override
        public void onSessionEnded(CastSession session, int error) {
            finish();
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mCastContext = CastContext.getSharedInstance(this);
        mSessionManager = mCastContext.getSessionManager();
        mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession.class);
    }

    @Override
    protected void onResume() {
        super.onResume();
        mCastSession = mSessionManager.getCurrentCastSession();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession.class);
    }
}

ストリーミング転送

セッションの状態を維持することは、ストリーミング転送の基本です。ユーザーは、音声コマンド、Google Home アプリ、スマート ディスプレイを使用して、既存の音声ストリームと動画ストリームをデバイス間で移動できます。メディアは 1 つのデバイス(送信元)での再生を停止し、別のデバイス(宛先)で続行されます。最新のファームウェアを搭載したキャスト デバイスは、ストリーミング転送の送信元または宛先として使用できます。

ストリーミング転送または拡張中に新しい宛先デバイスを取得するには、 Cast.ListenerCastSession#addCastListenerを使用して登録します。 次に、onDeviceNameChanged コールバックで CastSession#getCastDevice() を呼び出します。

詳しくは、 ウェブ レシーバーでのストリーミング転送 をご覧ください。

自動再接続

フレームワークには ReconnectionService が用意されています。送信元アプリで有効にすると、次のような微妙な コーナーケースでの再接続を処理できます。

  • 一時的な Wi-Fi の切断から復旧する
  • デバイスのスリープから復旧する
  • アプリのバックグラウンド化から復旧する
  • アプリがクラッシュした場合に復旧する

このサービスはデフォルトでオンになっており、 CastOptions.Builder でオフにできます。

Gradle ファイルで自動マージが有効になっている場合、このサービスはアプリのマニフェストに自動的にマージされます。

フレームワークは、メディア セッションがある場合はサービスを開始し、メディア セッションが終了すると停止します。

メディア コントロールの仕組み

Cast フレームワークでは、Cast 2.x の RemoteMediaPlayer クラスが非推奨となり、新しいクラス RemoteMediaClientが推奨されています。 このクラスは、より便利な API セットで同じ機能を提供し、 GoogleApiClient を渡す必要がありません。

アプリがメディア名前空間をサポートするウェブ レシーバー アプリとの CastSession を確立すると、フレームワークによって RemoteMediaClientのインスタンスが自動的に作成されます。アプリは、CastSession インスタンスで access it by calling getRemoteMediaClient() method を呼び出すことでアクセスできます。

ウェブ レシーバーにリクエストを発行する RemoteMediaClient のすべてのメソッドは、そのリクエストの追跡に使用できる PendingResult オブジェクトを返します。

RemoteMediaClient のインスタンスは、アプリの複数の部分で共有される可能性があります。実際、永続的な ミニ コントローラ通知サービス など、フレームワークの内部コンポーネントでも共有されます。そのため、このインスタンスは RemoteMediaClient.Listener の複数のインスタンスの登録をサポートしています。

メディア メタデータを設定する

MediaMetadata クラスは、キャストするメディア アイテムに関する情報を表します。次の例では、映画の新しい MediaMetadata インスタンスを作成し、タイトル、サブタイトル、2 つの画像を設定します。

Kotlin
val movieMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE)

movieMetadata.putString(MediaMetadata.KEY_TITLE, mSelectedMedia.getTitle())
movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, mSelectedMedia.getStudio())
movieMetadata.addImage(WebImage(Uri.parse(mSelectedMedia.getImage(0))))
movieMetadata.addImage(WebImage(Uri.parse(mSelectedMedia.getImage(1))))
Java
MediaMetadata movieMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);

movieMetadata.putString(MediaMetadata.KEY_TITLE, mSelectedMedia.getTitle());
movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, mSelectedMedia.getStudio());
movieMetadata.addImage(new WebImage(Uri.parse(mSelectedMedia.getImage(0))));
movieMetadata.addImage(new WebImage(Uri.parse(mSelectedMedia.getImage(1))));

メディア メタデータでの画像の使用については、 画像の選択 をご覧ください。

メディアを読み込む

アプリは、次のコードに示すようにメディア アイテムを読み込むことができます。まず、メディアのメタデータを使用して MediaInfo.BuilderMediaInfo インスタンスを作成します。現在の CastSession から RemoteMediaClient を取得し、その RemoteMediaClientMediaInfoを読み込みます。RemoteMediaClient を使用して、ウェブ レシーバーで実行されているメディア プレーヤー アプリを再生、一時停止、その他の方法で制御します。

Kotlin
val mediaInfo = MediaInfo.Builder(mSelectedMedia.getUrl())
    .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
    .setContentType("videos/mp4")
    .setMetadata(movieMetadata)
    .setStreamDuration(mSelectedMedia.getDuration() * 1000)
    .build()
val remoteMediaClient = mCastSession.getRemoteMediaClient()
remoteMediaClient.load(MediaLoadRequestData.Builder().setMediaInfo(mediaInfo).build())
Java
MediaInfo mediaInfo = new MediaInfo.Builder(mSelectedMedia.getUrl())
        .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
        .setContentType("videos/mp4")
        .setMetadata(movieMetadata)
        .setStreamDuration(mSelectedMedia.getDuration() * 1000)
        .build();
RemoteMediaClient remoteMediaClient = mCastSession.getRemoteMediaClient();
remoteMediaClient.load(new MediaLoadRequestData.Builder().setMediaInfo(mediaInfo).build());

メディア トラックの使用に関するセクションもご覧ください。

4K 動画形式

メディアの動画形式を確認するには、MediaStatus で getVideoInfo() を使用して、 VideoInfoの現在のインスタンスを取得します。 このインスタンスには、HDR TV 形式のタイプと、ピクセル単位の表示の高さと幅が含まれます。4K 形式のバリアントは、定数 HDR_TYPE_*で示されます。

複数のデバイスへのリモコン通知

ユーザーがキャストしている場合、同じネットワーク上の他の Android デバイスにも通知が届き、再生を制御できるようになります。デバイスでこのような通知を受け取ったユーザーは、[設定] アプリの [Google] > [Google Cast] > [リモコン通知を表示] で、そのデバイスの通知をオフにできます (通知には [設定] アプリへのショートカットが含まれています)。詳しくは、 キャスト リモコン通知をご覧ください。

ミニ コントローラを追加する

Cast デザイン チェックリストによると、 送信元アプリは、ユーザーが現在のコンテンツ ページから 送信元アプリの別の部分に移動したときに表示されるミニ コントローラ と呼ばれる永続的なコントロールを提供する必要があります。ミニ コントローラは、現在のキャスト セッションをユーザーに視覚的に通知します。ユーザーはミニ コントローラをタップして、キャストの全画面表示の拡張コントローラ ビューに戻ることができます。

フレームワークには、ミニ コントローラを表示する各アクティビティのレイアウト ファイルの下部に追加できるカスタムビュー MiniControllerFragment が用意されています。

<fragment
    android:id="@+id/castMiniController"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:visibility="gone"
    class="com.google.android.gm>s.cast.framework.media.widget.MiniControllerFragment" /

送信元アプリが動画または音声のライブ ストリームを再生している場合、SDK はミニ コントローラの再生/一時停止ボタンの代わりに再生/停止ボタンを自動的に表示します。

このカスタムビューのタイトルとサブタイトルのテキストの外観を設定し、 ボタンを選択するには、 ミニ コントローラをカスタマイズするをご覧ください。

拡張コントローラを追加する

Google Cast デザイン チェックリストでは、送信側アプリに、キャストするメディアの拡張 コントローラ を表示するよう規定されています。拡張コントローラは、ミニ コントローラの全画面バージョンです。

Cast SDK には、拡張コントローラ用のウィジェットとして ExpandedControllerActivityが用意されています。 これは、キャスト アイコンを追加するためにサブクラス化する必要がある抽象クラスです。

まず、拡張コントローラにキャスト アイコンを用意するための新しいメニュー リソース ファイルを作成します。

<menu xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.co>m/apk/<res-auto"

    item
            android:id="@+id/media_route_menu_item"
            android:title="@string/media_route_menu_title"
            app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionPro>vi<der&q>uot;
            app:showAsAction="always"/

/menu

ExpandedControllerActivity を拡張する新しいクラスを作成します。

Kotlin
class ExpandedControlsActivity : ExpandedControllerActivity() {
    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        super.onCreateOptionsMenu(menu)
        menuInflater.inflate(R.menu.expanded_controller, menu)
        CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.media_route_menu_item)
        return true
    }
}
Java
public class ExpandedControlsActivity extends ExpandedControllerActivity {
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        getMenuInflater().inflate(R.menu.expanded_controller, menu);
        CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.media_route_menu_item);
        return true;
    }
}

次に、アプリ マニフェストの application タグ内で新しいアクティビティを宣言します。

<application>
...
<activity
        android:name=".expandedcontrols.ExpandedControlsActivity"
        android:label="@string/app_name"
        android:launchMode="singleTask"
        android:theme="@style/Theme.CastVideosDark"
        android:screenOrientation="portrait"
        android:parentActivityName=">;com.<google.sample>.cast.ref<player.VideoBrowserActivity"
    intent-filt>er
  <      action a>n<droid:nam>e=&qu<ot;android.i>ntent.action.MAIN"/
    /intent-filter
/activity
...
/application

CastOptionsProvider を編集して NotificationOptionsCastMediaOptions を変更し、ターゲット アクティビティを新しいアクティビティに設定します。

Kotlin
override fun getCastOptions(context: Context): CastOptions? {
    val notificationOptions = NotificationOptions.Builder()
        .setTargetActivityClassName(ExpandedControlsActivity::class.java.name)
        .build()
    val mediaOptions = CastMediaOptions.Builder()
        .setNotificationOptions(notificationOptions)
        .setExpandedControllerActivityClassName(ExpandedControlsActivity::class.java.name)
        .build()

    return CastOptions.Builder()
        .setReceiverApplicationId(context.getString(R.string.app_id))
        .setCastMediaOptions(mediaOptions)
        .build()
}
Java
public CastOptions getCastOptions(Context context) {
    NotificationOptions notificationOptions = new NotificationOptions.Builder()
            .setTargetActivityClassName(ExpandedControlsActivity.class.getName())
            .build();
    CastMediaOptions mediaOptions = new CastMediaOptions.Builder()
            .setNotificationOptions(notificationOptions)
            .setExpandedControllerActivityClassName(ExpandedControlsActivity.class.getName())
            .build();

    return new CastOptions.Builder()
            .setReceiverApplicationId(context.getString(R.string.app_id))
            .setCastMediaOptions(mediaOptions)
            .build();
}

リモート メディアが読み込まれたときに新しいアクティビティを表示するように、LocalPlayerActivity loadRemoteMedia メソッドを更新します。

Kotlin
private fun loadRemoteMedia(position: Int, autoPlay: Boolean) {
    val remoteMediaClient = mCastSession?.remoteMediaClient ?: return

    remoteMediaClient.registerCallback(object : RemoteMediaClient.Callback() {
        override fun onStatusUpdated() {
            val intent = Intent(this@LocalPlayerActivity, ExpandedControlsActivity::class.java)
            startActivity(intent)
            remoteMediaClient.unregisterCallback(this)
        }
    })

    remoteMediaClient.load(
        MediaLoadRequestData.Builder()
            .setMediaInfo(mSelectedMedia)
            .setAutoplay(autoPlay)
            .setCurrentTime(position.toLong()).build()
    )
}
Java
private void loadRemoteMedia(int position, boolean autoPlay) {
    if (mCastSession == null) {
        return;
    }
    final RemoteMediaClient remoteMediaClient = mCastSession.getRemoteMediaClient();
    if (remoteMediaClient == null) {
        return;
    }
    remoteMediaClient.registerCallback(new RemoteMediaClient.Callback() {
        @Override
        public void onStatusUpdated() {
            Intent intent = new Intent(LocalPlayerActivity.this, ExpandedControlsActivity.class);
            startActivity(intent);
            remoteMediaClient.unregisterCallback(this);
        }
    });
    remoteMediaClient.load(new MediaLoadRequestData.Builder()
            .setMediaInfo(mSelectedMedia)
            .setAutoplay(autoPlay)
            .setCurrentTime(position).build());
}

送信元アプリが動画または音声のライブ ストリームを再生している場合、SDK は拡張コントローラの再生/一時停止ボタンの代わりに再生/停止ボタンを自動的に表示します。

テーマを使用して外観を設定し、表示するボタンを選択して、 カスタムボタンを追加するには、 拡張コントローラをカスタマイズするをご覧ください。

音量の調整

フレームワークは、送信元アプリの音量を自動的に管理します。フレームワークは、送信元アプリとウェブ レシーバー アプリを自動的に同期し、送信元 UI が常にウェブ レシーバーで指定された音量を報告するようにします。

物理ボタンの音量調節

Android では、Jelly Bean 以降を使用しているデバイスの場合、送信元デバイスの物理ボタンを使用して、ウェブ レシーバーのキャスト セッションの音量をデフォルトで変更できます。

Jelly Bean より前の物理ボタンの音量調節

Jelly Bean より前の Android デバイスで物理的な音量キーを使用してウェブ レシーバー デバイスの音量を制御するには、送信元アプリでアクティビティの dispatchKeyEvent をオーバーライドし、 CastContext.onDispatchVolumeKeyEventBeforeJellyBean()を呼び出す必要があります。

Kotlin
class MyActivity : FragmentActivity() {
    override fun dispatchKeyEvent(event: KeyEvent): Boolean {
        return (CastContext.getSharedInstance(this)
            .onDispatchVolumeKeyEventBeforeJellyBean(event)
                || super.dispatchKeyEvent(event))
    }
}
Java
class MyActivity extends FragmentActivity {
    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        return CastContext.getSharedInstance(this)
            .onDispatchVolumeKeyEventBeforeJellyBean(event)
            || super.dispatchKeyEvent(event);
    }
}

通知とロック画面にメディア コントロールを追加する

Android のみで、Google Cast デザイン チェックリストでは、送信元アプリが フォーカスされていない場合に、送信元アプリがキャストしている通知とロック画面にメディア コントロールを実装する必要があります。 フレームワークには、送信元アプリが通知とロック画面にメディア コントロールを作成するのに役立つMediaNotificationServiceMediaIntentReceiverが用意されています。

MediaNotificationService は、送信元がキャストしているときに実行され、現在のキャスト アイテムに関する画像のサムネイルと情報、再生/一時停止ボタン、停止ボタンを含む通知を表示します。

MediaIntentReceiver は、通知からのユーザー操作を処理する BroadcastReceiver です。

アプリは、通知とロック画面からのメディアコントロールを NotificationOptionsを使用して構成できます。 アプリは、通知に表示するコントロール ボタンと、ユーザーが通知をタップしたときに開く Activity を構成できます。アクションが明示的に指定されていない場合は、デフォルト値の MediaIntentReceiver.ACTION_TOGGLE_PLAYBACKMediaIntentReceiver.ACTION_STOP_CASTING が使用されます。

Kotlin
// Example showing 4 buttons: "rewind", "play/pause", "forward" and "stop casti<ng&quo>t;.
val buttonActions: MutableListString = ArrayList()
buttonActions.add(MediaIntentReceiver.ACTION_REWIND)
buttonActions.add(MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK)
buttonActions.add(MediaIntentReceiver.ACTION_FORWARD)
buttonActions.add(MediaIntentReceiver.ACTION_STOP_CASTING)

// Showing "play/pause" and "stop casting" in the compat view of the notification.
val compatButtonActionsIndices = intArrayOf(1, 3)

// Builds a notification with the above actions. Each tap on the "rewind" and "forward" buttons skips 30 seconds.
// Tapping on the notification opens an Activity with class VideoBrowserActivity.
val notificationOptions = NotificationOptions.Builder()
    .setActions(buttonActions, compatButtonActionsIndices)
    .setSkipStepMs(30 * DateUtils.SECOND_IN_MILLIS)
    .setTargetActivityClassName(VideoBrowserActivity::class.java.name)
    .build()
Java
// Example showing 4 buttons: "rewind", "play/pause", "forward&<quot; >and "stop casting".
<>ListString buttonActions = new ArrayList();
buttonActions.add(MediaIntentReceiver.ACTION_REWIND);
buttonActions.add(MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK);
buttonActions.add(MediaIntentReceiver.ACTION_FORWARD);
buttonActions.add(MediaIntentReceiver.ACTION_STOP_CASTING);

// Showing "play/pause" and "stop casting" in the compat view of the notification.
int[] compatButtonActionsIndices = new int[]{1, 3};

// Builds a notification with the above actions. Each tap on the "rewind" and "forward" buttons skips 30 seconds.
// Tapping on the notification opens an Activity with class VideoBrowserActivity.
NotificationOptions notificationOptions = new NotificationOptions.Builder()
    .setActions(buttonActions, compatButtonActionsIndices)
    .setSkipStepMs(30 * DateUtils.SECOND_IN_MILLIS)
    .setTargetActivityClassName(VideoBrowserActivity.class.getName())
    .build();

通知とロック画面からのメディア コントロールの表示はデフォルトでオンになっており、 null を指定して CastMediaOptions.Builder setNotificationOptions を呼び出すことで無効にできます。 現在、ロック画面機能は、通知がオンになっている限りオンになります。

Kotlin
// ... continue with the NotificationOptions built above
val mediaOptions = CastMediaOptions.Builder()
    .setNotificationOptions(notificationOptions)
    .build()
val castOptions: CastOptions = Builder()
    .setReceiverApplicationId(context.getString(R.string.app_id))
    .setCastMediaOptions(mediaOptions)
    .build()
Java
// ... continue with the NotificationOptions built above
CastMediaOptions mediaOptions = new CastMediaOptions.Builder()
        .setNotificationOptions(notificationOptions)
        .build();
CastOptions castOptions = new CastOptions.Builder()
        .setReceiverApplicationId(context.getString(R.string.app_id))
        .setCastMediaOptions(mediaOptions)
        .build();

送信元アプリが動画または音声のライブ ストリームを再生している場合、SDK は通知コントロールの再生/一時停止ボタンの代わりに再生/停止ボタンを自動的に表示しますが、ロック画面コントロールには表示しません。

: Lollipop より前のデバイスにロック画面コントロールを表示するには、 RemoteMediaClient が自動的に音声フォーカスをリクエストします。

エラーを処理する

送信元アプリがすべてのエラー コールバックを処理し、キャスト ライフサイクルの各段階で最適なレスポンスを決定することは非常に重要です。アプリは、ユーザーにエラー ダイアログを表示することも、ウェブ レシーバーへの接続を終了することもできます。