このページでは、Android TV レシーバー アプリのカスタマイズに使用できる機能のコード スニペットと説明を掲載しています。
ライブラリの構成
Android TV アプリで Cast Connect API を利用できるようにするには:
-
アプリケーション モジュール ディレクトリ内の
build.gradle
ファイルを開きます。 -
リストされた
repositories
にgoogle()
が含まれていることを確認します。repositories { google() }
-
アプリのターゲット デバイスタイプに応じて、ライブラリの最新バージョンを依存関係に追加します。
-
Android Receiver アプリの場合:
dependencies { implementation 'com.google.android.gms:play-services-cast-tv:21.0.1' implementation 'com.google.android.gms:play-services-cast:21.4.0' }
-
Android 送信者アプリの場合:
dependencies { implementation 'com.google.android.gms:play-services-cast:21.0.1' implementation 'com.google.android.gms:play-services-cast-framework:21.4.0' }
-
Android Receiver アプリの場合:
-
変更を保存し、ツールバーの
Sync Project with Gradle Files
をクリックします。
-
Podfile
の対象がgoogle-cast-sdk
4.8.1 以降であることを確認します。 -
iOS 14 以降をターゲットとしていること。詳細については、リリースノートをご覧ください。
platform: ios, '14' def target_pods pod 'google-cast-sdk', '~>4.8.1' end
- Chromium ブラウザ バージョン M87 以降が必要です。
-
Web Sender API ライブラリをプロジェクト
<script src="//www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1"></script>
に追加する
AndroidX の要件
新しいバージョンの Google Play 開発者サービスでは、androidx
名前空間を使用するようにアプリをアップデートする必要があります。AndroidX への移行の手順に沿って操作します。
Android TV アプリ - 前提条件
Android TV アプリで Cast Connect をサポートするには、メディア セッションからイベントを作成してサポートする必要があります。メディア セッションによって提供されるデータから、メディア ステータスに関する基本情報(位置、再生状態など)が提供されます。また、メディア セッションは Cast Connect ライブラリによっても使用され、一時停止など、センダーから特定のメッセージを受信したことを通知します。
メディア セッションと、メディア セッションを初期化する方法の詳細については、メディア セッションの操作ガイドをご覧ください。
メディア セッションのライフサイクル
アプリは、再生の開始時にメディア セッションを作成し、操作できなくなったら解放する必要があります。たとえば動画アプリの場合は、ユーザーが再生アクティビティを終了したときにセッションを解放する必要があります。[戻る] を選択して他のコンテンツを閲覧するか、アプリをバックグラウンド処理します。音楽アプリの場合は、メディアを再生しなくなったときにセッションを解放する必要があります。
セッション ステータスを更新しています
メディア セッションのデータは、プレーヤーのステータスに合わせて最新の状態に保つ必要があります。たとえば、再生が一時停止されたら、再生状態とサポートされているアクションを更新する必要があります。次の表に、ユーザーが最新の状態に維持する必要がある状態を示します。
MediaMetadataCompat
メタデータ フィールド | 説明 |
---|---|
METADATA_KEY_TITLE(必須) | メディアのタイトル。 |
METADATA_KEY_DISPLAY_SUBTITLE | サブタイトル。 |
METADATA_KEY_DISPLAY_ICON_URI | アイコンの URL。 |
METADATA_KEY_DURATION(必須) | メディアの再生時間。 |
METADATA_KEY_MEDIA_URI | Content ID。 |
METADATA_KEY_ARTIST | アーティスト。 |
METADATA_KEY_ALBUM | アルバム。 |
PlaybackStateCompat
必要なメソッド | 説明 |
---|---|
setActions() | サポートされているメディア コマンドを設定します。 |
setState() | 再生状態と現在の位置を設定します。 |
MediaSessionCompat
必要なメソッド | 説明 |
---|---|
setRepeatMode() | 繰り返しモードを設定します。 |
setShuffleMode() | シャッフル モードを設定します。 |
setMetadata() | メディア メタデータを設定します。 |
setPlaybackState() | 再生状態を設定します。 |
private fun updateMediaSession() { val metadata = MediaMetadataCompat.Builder() .putString(MediaMetadataCompat.METADATA_KEY_TITLE, "title") .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "subtitle") .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI, mMovie.getCardImageUrl()) .build() val playbackState = PlaybackStateCompat.Builder() .setState( PlaybackStateCompat.STATE_PLAYING, player.getPosition(), player.getPlaybackSpeed(), System.currentTimeMillis() ) .build() mediaSession.setMetadata(metadata) mediaSession.setPlaybackState(playbackState) }
private void updateMediaSession() { MediaMetadataCompat metadata = new MediaMetadataCompat.Builder() .putString(MediaMetadataCompat.METADATA_KEY_TITLE, "title") .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "subtitle") .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI,mMovie.getCardImageUrl()) .build(); PlaybackStateCompat playbackState = new PlaybackStateCompat.Builder() .setState( PlaybackStateCompat.STATE_PLAYING, player.getPosition(), player.getPlaybackSpeed(), System.currentTimeMillis()) .build(); mediaSession.setMetadata(metadata); mediaSession.setPlaybackState(playbackState); }
トランスポート制御の処理
アプリにメディア セッション トランスポート コントロール コールバックを実装する必要があります。次の表に、処理する必要があるトランスポート制御アクションを示します。
MediaSessionCompat.Callback
アクション | 説明 |
---|---|
onPlay() | 再開 |
onPause() | 一時停止 |
onSeekTo() | 特定の位置に移動 |
onStop() | 現在のメディアを停止します |
class MyMediaSessionCallback : MediaSessionCompat.Callback() { override fun onPause() { // Pause the player and update the play state. ... } override fun onPlay() { // Resume the player and update the play state. ... } override fun onSeekTo (long pos) { // Seek and update the play state. ... } ... } mediaSession.setCallback( MyMediaSessionCallback() );
public MyMediaSessionCallback extends MediaSessionCompat.Callback { public void onPause() { // Pause the player and update the play state. ... } public void onPlay() { // Resume the player and update the play state. ... } public void onSeekTo (long pos) { // Seek and update the play state. ... } ... } mediaSession.setCallback(new MyMediaSessionCallback());
キャスト サポートを設定する
センダーアプリによって起動リクエストが送信されると、アプリの名前空間を持つインテントが作成されます。TV アプリの起動時に、アプリがこれを処理し、CastReceiverContext
オブジェクトのインスタンスを作成します。CastReceiverContext
オブジェクトは、TV アプリの実行中にキャストとやり取りするために必要です。このオブジェクトにより、TV アプリは接続済みのセンダーからのキャスト メディア メッセージを受信できます。
Android TV のセットアップ
起動インテント フィルタの追加
センダーアプリからの起動インテントを処理するアクティビティに新しいインテント フィルタを追加します。
<activity android:name="com.example.activity">
<intent-filter>
<action android:name="com.google.android.gms.cast.tv.action.LAUNCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
レシーバー オプション プロバイダを指定する
CastReceiverOptions
を提供するには、ReceiverOptionsProvider
を実装する必要があります。
class MyReceiverOptionsProvider : ReceiverOptionsProvider { override fun getOptions(context: Context?): CastReceiverOptions { return CastReceiverOptions.Builder(context) .setStatusText("My App") .build() } }
public class MyReceiverOptionsProvider implements ReceiverOptionsProvider { @Override public CastReceiverOptions getOptions(Context context) { return new CastReceiverOptions.Builder(context) .setStatusText("My App") .build(); } }
次に、AndroidManifest
でオプション プロバイダを指定します。
<meta-data
android:name="com.google.android.gms.cast.tv.RECEIVER_OPTIONS_PROVIDER_CLASS_NAME"
android:value="com.example.mysimpleatvapplication.MyReceiverOptionsProvider" />
ReceiverOptionsProvider
は、CastReceiverContext
の初期化時に CastReceiverOptions
を提供するために使われます。
キャスト レシーバーのコンテキスト
アプリの作成時に CastReceiverContext
を初期化します。
override fun onCreate() { CastReceiverContext.initInstance(this) ... }
@Override public void onCreate() { CastReceiverContext.initInstance(this); ... }
アプリがフォアグラウンドに移動したら、CastReceiverContext
を起動します。
CastReceiverContext.getInstance().start()
CastReceiverContext.getInstance().start();
動画アプリやバックグラウンド再生をサポートしていないアプリのバックグラウンドに移行したら、CastReceiverContext
で stop()
を呼び出します。
// Player has stopped. CastReceiverContext.getInstance().stop()
// Player has stopped. CastReceiverContext.getInstance().stop();
また、アプリがバックグラウンドでの再生をサポートしている場合は、バックグラウンドでの再生が停止したときに CastReceiverContext
で stop()
を呼び出します。
特にネイティブ アプリに複数のアクティビティがある場合は、androidx.lifecycle
ライブラリの LifecycleObserver を使用して CastReceiverContext.start()
と CastReceiverContext.stop()
の呼び出しを管理することを強くおすすめします。これにより、異なるアクティビティから start()
と stop()
を呼び出す際の競合状態を回避できます。
// Create a LifecycleObserver class. class MyLifecycleObserver : DefaultLifecycleObserver { override fun onStart(owner: LifecycleOwner) { // App prepares to enter foreground. CastReceiverContext.getInstance().start() } override fun onStop(owner: LifecycleOwner) { // App has moved to the background or has terminated. CastReceiverContext.getInstance().stop() } } // Add the observer when your application is being created. class MyApplication : Application() { fun onCreate() { super.onCreate() // Initialize CastReceiverContext. CastReceiverContext.initInstance(this /* android.content.Context */) // Register LifecycleObserver ProcessLifecycleOwner.get().lifecycle.addObserver( MyLifecycleObserver()) } }
// Create a LifecycleObserver class. public class MyLifecycleObserver implements DefaultLifecycleObserver { @Override public void onStart(LifecycleOwner owner) { // App prepares to enter foreground. CastReceiverContext.getInstance().start(); } @Override public void onStop(LifecycleOwner owner) { // App has moved to the background or has terminated. CastReceiverContext.getInstance().stop(); } } // Add the observer when your application is being created. public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); // Initialize CastReceiverContext. CastReceiverContext.initInstance(this /* android.content.Context */); // Register LifecycleObserver ProcessLifecycleOwner.get().getLifecycle().addObserver( new MyLifecycleObserver()); } }
// In AndroidManifest.xml set MyApplication as the application class
<application
...
android:name=".MyApplication">
MediaSession を MediaManager に接続する
MediaSession
を作成するときに、現在の MediaSession
トークンを CastReceiverContext
に提供する必要もあります。これにより、コマンドの送信先とメディアの再生状態の取得先を把握できます。
val mediaManager: MediaManager = receiverContext.getMediaManager() mediaManager.setSessionCompatToken(currentMediaSession.getSessionToken())
MediaManager mediaManager = receiverContext.getMediaManager(); mediaManager.setSessionCompatToken(currentMediaSession.getSessionToken());
再生がアクティブでないために MediaSession
を解放する場合は、MediaManager
に null トークンを設定する必要があります。
myPlayer.stop() mediaSession.release() mediaManager.setSessionCompatToken(null)
myPlayer.stop(); mediaSession.release(); mediaManager.setSessionCompatToken(null);
アプリがバックグラウンドで実行されている間のメディア再生をサポートしている場合は、アプリがバックグラウンドに移動したときに CastReceiverContext.stop()
を呼び出すのではなく、アプリがバックグラウンドにあり、メディアを再生していないときにのみ呼び出す必要があります。次に例を示します。
class MyLifecycleObserver : DefaultLifecycleObserver { ... // App has moved to the background. override fun onPause(owner: LifecycleOwner) { mIsBackground = true myStopCastReceiverContextIfNeeded() } } // Stop playback on the player. private fun myStopPlayback() { myPlayer.stop() myStopCastReceiverContextIfNeeded() } // Stop the CastReceiverContext when both the player has // stopped and the app has moved to the background. private fun myStopCastReceiverContextIfNeeded() { if (mIsBackground && myPlayer.isStopped()) { CastReceiverContext.getInstance().stop() } }
public class MyLifecycleObserver implements DefaultLifecycleObserver { ... // App has moved to the background. @Override public void onPause(LifecycleOwner owner) { mIsBackground = true; myStopCastReceiverContextIfNeeded(); } } // Stop playback on the player. private void myStopPlayback() { myPlayer.stop(); myStopCastReceiverContextIfNeeded(); } // Stop the CastReceiverContext when both the player has // stopped and the app has moved to the background. private void myStopCastReceiverContextIfNeeded() { if (mIsBackground && myPlayer.isStopped()) { CastReceiverContext.getInstance().stop(); } }
ExoPlayer と Cast Connect を併用する
Exoplayer
を使用している場合、MediaSessionConnector
を使用すると、変更を手動で追跡する代わりに、セッションと再生状態を含むすべての関連情報を自動的に維持できます。
MediaSessionConnector.MediaButtonEventHandler
は、setMediaButtonEventHandler(MediaButtonEventHandler)
を呼び出して MediaButton イベントを処理するために使用できます。それ以外の場合は、デフォルトで MediaSessionCompat.Callback
によって処理されます。
MediaSessionConnector
をアプリに統合するには、プレーヤーのアクティビティ クラスまたはメディア セッションを管理する場所に、次のコードを追加します。
class PlayerActivity : Activity() { private var mMediaSession: MediaSessionCompat? = null private var mMediaSessionConnector: MediaSessionConnector? = null private var mMediaManager: MediaManager? = null override fun onCreate(savedInstanceState: Bundle?) { ... mMediaSession = MediaSessionCompat(this, LOG_TAG) mMediaSessionConnector = MediaSessionConnector(mMediaSession!!) ... } override fun onStart() { ... mMediaManager = receiverContext.getMediaManager() mMediaManager!!.setSessionCompatToken(currentMediaSession.getSessionToken()) mMediaSessionConnector!!.setPlayer(mExoPlayer) mMediaSessionConnector!!.setMediaMetadataProvider(mMediaMetadataProvider) mMediaSession!!.isActive = true ... } override fun onStop() { ... mMediaSessionConnector!!.setPlayer(null) mMediaSession!!.release() mMediaManager!!.setSessionCompatToken(null) ... } }
public class PlayerActivity extends Activity { private MediaSessionCompat mMediaSession; private MediaSessionConnector mMediaSessionConnector; private MediaManager mMediaManager; @Override protected void onCreate(Bundle savedInstanceState) { ... mMediaSession = new MediaSessionCompat(this, LOG_TAG); mMediaSessionConnector = new MediaSessionConnector(mMediaSession); ... } @Override protected void onStart() { ... mMediaManager = receiverContext.getMediaManager(); mMediaManager.setSessionCompatToken(currentMediaSession.getSessionToken()); mMediaSessionConnector.setPlayer(mExoPlayer); mMediaSessionConnector.setMediaMetadataProvider(mMediaMetadataProvider); mMediaSession.setActive(true); ... } @Override protected void onStop() { ... mMediaSessionConnector.setPlayer(null); mMediaSession.release(); mMediaManager.setSessionCompatToken(null); ... } }
送信者アプリのセットアップ
Cast Connect のサポートを有効にする
Cast Connect をサポートするようにセンダーアプリを更新したら、LaunchOptions
の androidReceiverCompatible
フラグを true に設定して、準備状況を宣言できます。
play-services-cast-framework
バージョン 19.0.0
以降が必要です。
LaunchOptions
(CastOptions
の一部)で androidReceiverCompatible
フラグを設定します。
class CastOptionsProvider : OptionsProvider { override fun getCastOptions(context: Context?): CastOptions { val launchOptions: LaunchOptions = Builder() .setAndroidReceiverCompatible(true) .build() return CastOptions.Builder() .setLaunchOptions(launchOptions) ... .build() } }
public class CastOptionsProvider implements OptionsProvider { @Override public CastOptions getCastOptions(Context context) { LaunchOptions launchOptions = new LaunchOptions.Builder() .setAndroidReceiverCompatible(true) .build(); return new CastOptions.Builder() .setLaunchOptions(launchOptions) ... .build(); } }
google-cast-sdk
バージョン v4.4.8
以降が必要です。
GCKLaunchOptions
(GCKCastOptions
の一部)で androidReceiverCompatible
フラグを設定します。
let options = GCKCastOptions(discoveryCriteria: GCKDiscoveryCriteria(applicationID: kReceiverAppID)) ... let launchOptions = GCKLaunchOptions() launchOptions.androidReceiverCompatible = true options.launchOptions = launchOptions GCKCastContext.setSharedInstanceWith(options)
Chromium ブラウザのバージョン M87
以降が必要です。
const context = cast.framework.CastContext.getInstance(); const castOptions = new cast.framework.CastOptions(); castOptions.receiverApplicationId = kReceiverAppID; castOptions.androidReceiverCompatible = true; context.setOptions(castOptions);
Cast デベロッパー コンソールのセットアップ
Android TV アプリを構成する
Cast のデベロッパー コンソールに Android TV アプリのパッケージ名を追加して、キャスト アプリ ID に関連付けます。
デベロッパー デバイスを登録する
Cast Developer Console で、開発に使用する Android TV デバイスのシリアル番号を登録します。
セキュリティ上の理由により、Cast Connect は Google Play ストアからインストールされたアプリでのみ機能します。
キャスト開発用にキャスト デバイスや Android TV デバイスを登録する方法について詳しくは、登録ページをご覧ください。
メディアの読み込み
Android TV アプリにディープリンク サポートをすでに実装している場合は、Android TV マニフェストで同様の定義を構成する必要があります。
<activity android:name="com.example.activity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="https"/>
<data android:host="www.example.com"/>
<data android:pathPattern=".*"/>
</intent-filter>
</activity>
送信者のエンティティ別に読み込む
センダーでは、読み込みリクエストのメディア情報で entity
を設定することで、ディープリンクを渡すことができます。
val mediaToLoad = MediaInfo.Builder("some-id") .setEntity("https://example.com/watch/some-id") ... .build() val loadRequest = MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") ... .build() remoteMediaClient.load(loadRequest)
MediaInfo mediaToLoad = new MediaInfo.Builder("some-id") .setEntity("https://example.com/watch/some-id") ... .build(); MediaLoadRequestData loadRequest = new MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") ... .build(); remoteMediaClient.load(loadRequest);
let mediaInfoBuilder = GCKMediaInformationBuilder(entity: "https://example.com/watch/some-id") ... mediaInformation = mediaInfoBuilder.build() let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder() mediaLoadRequestDataBuilder.mediaInformation = mediaInformation mediaLoadRequestDataBuilder.credentials = "user-credentials" ... let mediaLoadRequestData = mediaLoadRequestDataBuilder.build() remoteMediaClient?.loadMedia(with: mediaLoadRequestData)
Chromium ブラウザのバージョン M87
以降が必要です。
let mediaInfo = new chrome.cast.media.MediaInfo('some-id"', 'video/mp4'); mediaInfo.entity = 'https://example.com/watch/some-id'; ... let request = new chrome.cast.media.LoadRequest(mediaInfo); request.credentials = 'user-credentials'; ... cast.framework.CastContext.getInstance().getCurrentSession().loadMedia(request);
読み込みコマンドは、ディープリンクとデベロッパー コンソールで定義したパッケージ名を含むインテントを介して送信されます。
センダーでの ATV 認証情報の設定
ウェブ レシーバー アプリと Android TV アプリが異なるディープリンクと credentials
をサポートしている可能性があります(2 つのプラットフォームで認証の処理方法が異なる場合など)。これに対処するには、Android TV 用に次のように別の entity
と credentials
を指定します。
val mediaToLoad = MediaInfo.Builder("some-id") .setEntity("https://example.com/watch/some-id") .setAtvEntity("myscheme://example.com/atv/some-id") ... .build() val loadRequest = MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") .setAtvCredentials("atv-user-credentials") ... .build() remoteMediaClient.load(loadRequest)
MediaInfo mediaToLoad = new MediaInfo.Builder("some-id") .setEntity("https://example.com/watch/some-id") .setAtvEntity("myscheme://example.com/atv/some-id") ... .build(); MediaLoadRequestData loadRequest = new MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") .setAtvCredentials("atv-user-credentials") ... .build(); remoteMediaClient.load(loadRequest);
let mediaInfoBuilder = GCKMediaInformationBuilder(entity: "https://example.com/watch/some-id") mediaInfoBuilder.atvEntity = "myscheme://example.com/atv/some-id" ... mediaInformation = mediaInfoBuilder.build() let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder() mediaLoadRequestDataBuilder.mediaInformation = mediaInformation mediaLoadRequestDataBuilder.credentials = "user-credentials" mediaLoadRequestDataBuilder.atvCredentials = "atv-user-credentials" ... let mediaLoadRequestData = mediaLoadRequestDataBuilder.build() remoteMediaClient?.loadMedia(with: mediaLoadRequestData)
Chromium ブラウザのバージョン M87
以降が必要です。
let mediaInfo = new chrome.cast.media.MediaInfo('some-id"', 'video/mp4'); mediaInfo.entity = 'https://example.com/watch/some-id'; mediaInfo.atvEntity = 'myscheme://example.com/atv/some-id'; ... let request = new chrome.cast.media.LoadRequest(mediaInfo); request.credentials = 'user-credentials'; request.atvCredentials = 'atv-user-credentials'; ... cast.framework.CastContext.getInstance().getCurrentSession().loadMedia(request);
ウェブ レシーバー アプリが起動すると、読み込みリクエストで entity
と credentials
が使用されます。ただし、Android TV アプリが起動されると、SDK は entity
と credentials
を atvEntity
と atvCredentials
(指定されている場合)でオーバーライドします。
Content ID または MediaQueueData による読み込み
entity
または atvEntity
を使用せず、メディア情報で Content ID またはコンテンツ URL を使用している場合、またはより詳細なメディア読み込みリクエスト データを使用する場合は、Android TV アプリに次の定義済みインテント フィルタを追加する必要があります。
<activity android:name="com.example.activity">
<intent-filter>
<action android:name="com.google.android.gms.cast.tv.action.LOAD"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
送信側では、エンティティによる読み込みと同様に、コンテンツ情報を使用して読み込みリクエストを作成し、load()
を呼び出すことができます。
val mediaToLoad = MediaInfo.Builder("some-id").build() val loadRequest = MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") ... .build() remoteMediaClient.load(loadRequest)
MediaInfo mediaToLoad = new MediaInfo.Builder("some-id").build(); MediaLoadRequestData loadRequest = new MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") ... .build(); remoteMediaClient.load(loadRequest);
let mediaInfoBuilder = GCKMediaInformationBuilder(contentId: "some-id") ... mediaInformation = mediaInfoBuilder.build() let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder() mediaLoadRequestDataBuilder.mediaInformation = mediaInformation mediaLoadRequestDataBuilder.credentials = "user-credentials" ... let mediaLoadRequestData = mediaLoadRequestDataBuilder.build() remoteMediaClient?.loadMedia(with: mediaLoadRequestData)
Chromium ブラウザのバージョン M87
以降が必要です。
let mediaInfo = new chrome.cast.media.MediaInfo('some-id"', 'video/mp4'); ... let request = new chrome.cast.media.LoadRequest(mediaInfo); ... cast.framework.CastContext.getInstance().getCurrentSession().loadMedia(request);
読み込みリクエストの処理
アクティビティでこれらの読み込みリクエストを処理するには、アクティビティのライフサイクル コールバックでインテントを処理する必要があります。
class MyActivity : Activity() { override fun onStart() { super.onStart() val mediaManager = CastReceiverContext.getInstance().getMediaManager() // Pass the intent to the SDK. You can also do this in onCreate(). if (mediaManager.onNewIntent(intent)) { // If the SDK recognizes the intent, you should early return. return } // If the SDK doesn't recognize the intent, you can handle the intent with // your own logic. ... } // For some cases, a new load intent triggers onNewIntent() instead of // onStart(). override fun onNewIntent(intent: Intent) { val mediaManager = CastReceiverContext.getInstance().getMediaManager() // Pass the intent to the SDK. You can also do this in onCreate(). if (mediaManager.onNewIntent(intent)) { // If the SDK recognizes the intent, you should early return. return } // If the SDK doesn't recognize the intent, you can handle the intent with // your own logic. ... } }
public class MyActivity extends Activity { @Override protected void onStart() { super.onStart(); MediaManager mediaManager = CastReceiverContext.getInstance().getMediaManager(); // Pass the intent to the SDK. You can also do this in onCreate(). if (mediaManager.onNewIntent(getIntent())) { // If the SDK recognizes the intent, you should early return. return; } // If the SDK doesn't recognize the intent, you can handle the intent with // your own logic. ... } // For some cases, a new load intent triggers onNewIntent() instead of // onStart(). @Override protected void onNewIntent(Intent intent) { MediaManager mediaManager = CastReceiverContext.getInstance().getMediaManager(); // Pass the intent to the SDK. You can also do this in onCreate(). if (mediaManager.onNewIntent(intent)) { // If the SDK recognizes the intent, you should early return. return; } // If the SDK doesn't recognize the intent, you can handle the intent with // your own logic. ... } }
MediaManager
は、そのインテントが読み込みインテントであることを検出すると、そのインテントから MediaLoadRequestData
オブジェクトを抽出し、MediaLoadCommandCallback.onLoad()
を呼び出します。読み込みリクエストを処理するには、このメソッドをオーバーライドする必要があります。コールバックは、MediaManager.onNewIntent()
が呼び出される前に登録する必要があります(アクティビティまたはアプリの onCreate()
メソッドで行うことが推奨されます)。
class MyActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val mediaManager = CastReceiverContext.getInstance().getMediaManager() mediaManager.setMediaLoadCommandCallback(MyMediaLoadCommandCallback()) } } class MyMediaLoadCommandCallback : MediaLoadCommandCallback() { override fun onLoad( senderId: String?, loadRequestData: MediaLoadRequestData ): Task{ return Tasks.call { // Resolve the entity into your data structure and load media. val mediaInfo = loadRequestData.getMediaInfo() if (!checkMediaInfoSupported(mediaInfo)) { // Throw MediaException to indicate load failure. throw MediaException( MediaError.Builder() .setDetailedErrorCode(DetailedErrorCode.LOAD_FAILED) .setReason(MediaError.ERROR_REASON_INVALID_REQUEST) .build() ) } myFillMediaInfo(MediaInfoWriter(mediaInfo)) myPlayerLoad(mediaInfo.getContentUrl()) // Update media metadata and state (this clears all previous status // overrides). castReceiverContext.getMediaManager() .setDataFromLoad(loadRequestData) ... castReceiverContext.getMediaManager().broadcastMediaStatus() // Return the resolved MediaLoadRequestData to indicate load success. return loadRequestData } } private fun myPlayerLoad(contentURL: String) { myPlayer.load(contentURL) // Update the MediaSession state. val playbackState: PlaybackStateCompat = Builder() .setState( player.getState(), player.getPosition(), System.currentTimeMillis() ) ... .build() mediaSession.setPlaybackState(playbackState) }
public class MyActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); MediaManager mediaManager = CastReceiverContext.getInstance().getMediaManager(); mediaManager.setMediaLoadCommandCallback(new MyMediaLoadCommandCallback()); } } public class MyMediaLoadCommandCallback extends MediaLoadCommandCallback { @Override public TaskonLoad(String senderId, MediaLoadRequestData loadRequestData) { return Tasks.call(() -> { // Resolve the entity into your data structure and load media. MediaInfo mediaInfo = loadRequestData.getMediaInfo(); if (!checkMediaInfoSupported(mediaInfo)) { // Throw MediaException to indicate load failure. throw new MediaException( new MediaError.Builder() .setDetailedErrorCode(DetailedErrorCode.LOAD_FAILED) .setReason(MediaError.ERROR_REASON_INVALID_REQUEST) .build()); } myFillMediaInfo(new MediaInfoWriter(mediaInfo)); myPlayerLoad(mediaInfo.getContentUrl()); // Update media metadata and state (this clears all previous status // overrides). castReceiverContext.getMediaManager() .setDataFromLoad(loadRequestData); ... castReceiverContext.getMediaManager().broadcastMediaStatus(); // Return the resolved MediaLoadRequestData to indicate load success. return loadRequestData; }); } private void myPlayerLoad(String contentURL) { myPlayer.load(contentURL); // Update the MediaSession state. PlaybackStateCompat playbackState = new PlaybackStateCompat.Builder() .setState( player.getState(), player.getPosition(), System.currentTimeMillis()) ... .build(); mediaSession.setPlaybackState(playbackState); }
読み込みインテントを処理するには、インテントを解析して、定義したデータ構造(読み込みリクエストの場合は MediaLoadRequestData
)に変換します。
サポートするメディア コマンド
基本的な再生コントロールのサポート
基本的な統合コマンドには、メディア セッションと互換性のあるコマンドが含まれます。これらのコマンドは、メディア セッション コールバックを介して通知されます。これをサポートするには、メディア セッションへのコールバックを登録する必要があります(すでに登録している可能性があります)。
private class MyMediaSessionCallback : MediaSessionCompat.Callback() { override fun onPause() { // Pause the player and update the play state. myPlayer.pause() } override fun onPlay() { // Resume the player and update the play state. myPlayer.play() } override fun onSeekTo(pos: Long) { // Seek and update the play state. myPlayer.seekTo(pos) } ... } mediaSession.setCallback(MyMediaSessionCallback())
private class MyMediaSessionCallback extends MediaSessionCompat.Callback { @Override public void onPause() { // Pause the player and update the play state. myPlayer.pause(); } @Override public void onPlay() { // Resume the player and update the play state. myPlayer.play(); } @Override public void onSeekTo(long pos) { // Seek and update the play state. myPlayer.seekTo(pos); } ... } mediaSession.setCallback(new MyMediaSessionCallback());
キャスト コントロール コマンドのサポート
ただし、skipAd()
や setActiveMediaTracks()
など、MediaSession
では使用できないキャスト コマンドもあります。また、キャストキューは MediaSession
キューに完全には対応していないため、一部のキューコマンドをここに実装する必要があります。
class MyMediaCommandCallback : MediaCommandCallback() { override fun onSkipAd(requestData: RequestData?): Task{ // Skip your ad ... return Tasks.forResult(null) } } val mediaManager = CastReceiverContext.getInstance().getMediaManager() mediaManager.setMediaCommandCallback(MyMediaCommandCallback())
public class MyMediaCommandCallback extends MediaCommandCallback { @Override public TaskonSkipAd(RequestData requestData) { // Skip your ad ... return Tasks.forResult(null); } } MediaManager mediaManager = CastReceiverContext.getInstance().getMediaManager(); mediaManager.setMediaCommandCallback(new MyMediaCommandCallback());
サポートされているメディア コマンドを指定する
キャスト レシーバーと同様に、センダーが特定の UI コントロールを有効または無効にすることができるように、Android TV アプリでサポートされるコマンドを指定する必要があります。MediaSession
の一部であるコマンドの場合は、PlaybackStateCompat
でコマンドを指定します。追加のコマンドは MediaStatusModifier
で指定する必要があります。
// Set media session supported commands val playbackState: PlaybackStateCompat = PlaybackStateCompat.Builder() .setActions(PlaybackStateCompat.ACTION_PLAY or PlaybackStateCompat.ACTION_PAUSE) .setState(PlaybackStateCompat.STATE_PLAYING) .build() mediaSession.setPlaybackState(playbackState) // Set additional commands in MediaStatusModifier val mediaManager = CastReceiverContext.getInstance().getMediaManager() mediaManager.getMediaStatusModifier() .setMediaCommandSupported(MediaStatus.COMMAND_QUEUE_NEXT)
// Set media session supported commands PlaybackStateCompat playbackState = new PlaybackStateCompat.Builder() .setActions(PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE) .setState(PlaybackStateCompat.STATE_PLAYING) .build(); mediaSession.setPlaybackState(playbackState); // Set additional commands in MediaStatusModifier MediaManager mediaManager = CastReceiverContext.getInstance().getMediaManager(); mediaManager.getMediaStatusModifier() .setMediaCommandSupported(MediaStatus.COMMAND_QUEUE_NEXT);
サポートされていないボタンを非表示にする
Android TV アプリが基本的なメディア コントロールしかサポートしていないのに、ウェブレシーバー アプリがより高度なコントロールをサポートしている場合は、センダーアプリが Android TV アプリにキャストしたときにセンダーアプリが正しく動作することを確認する必要があります。たとえば、ウェブレシーバー アプリが再生レートの変更をサポートしていないのに、センダーアプリが各プラットフォームでサポートされているアクションを正しく設定し、センダーアプリが UI を適切にレンダリングすることを確認する必要があります。
MediaStatus の変更
トラック、広告、ライブ、キューイングなどの高度な機能をサポートするには、Android TV アプリは、MediaSession
では確認できない追加情報を提供する必要があります。
これを実現するために、MediaStatusModifier
クラスが用意されています。MediaStatusModifier
は常に CastReceiverContext
で設定した MediaSession
で動作します。
MediaStatus
を作成してブロードキャストするには:
val mediaManager: MediaManager = castReceiverContext.getMediaManager() val statusModifier: MediaStatusModifier = mediaManager.getMediaStatusModifier() statusModifier .setLiveSeekableRange(seekableRange) .setAdBreakStatus(adBreakStatus) .setCustomData(customData) mediaManager.broadcastMediaStatus()
MediaManager mediaManager = castReceiverContext.getMediaManager(); MediaStatusModifier statusModifier = mediaManager.getMediaStatusModifier(); statusModifier .setLiveSeekableRange(seekableRange) .setAdBreakStatus(adBreakStatus) .setCustomData(customData); mediaManager.broadcastMediaStatus();
Google のクライアント ライブラリは MediaSession
からベース MediaStatus
を取得します。Android TV アプリは、MediaStatus
修飾子を使用して追加のステータスを指定し、ステータスをオーバーライドできます。
一部の状態とメタデータは、MediaSession
と MediaStatusModifier
の両方で設定できます。MediaSession
でのみ設定することを強くおすすめします。修飾子を使用して MediaSession
の状態をオーバーライドすることはできますが、修飾子のステータスは MediaSession
によって指定された値よりも常に優先度が高いため、推奨されません。
送信前に MediaStatus をインターセプトする
Web Receiver SDK と同様に、送信前に最後の仕上げを行う場合は、MediaStatusInterceptor
を指定して送信する MediaStatus
を処理できます。送信前に MediaStatusWriter
を渡して MediaStatus
を操作します。
mediaManager.setMediaStatusInterceptor(object : MediaStatusInterceptor { override fun intercept(mediaStatusWriter: MediaStatusWriter) { // Perform customization. mediaStatusWriter.setCustomData(JSONObject("{data: \"my Hello\"}")) } })
mediaManager.setMediaStatusInterceptor(new MediaStatusInterceptor() { @Override public void intercept(MediaStatusWriter mediaStatusWriter) { // Perform customization. mediaStatusWriter.setCustomData(new JSONObject("{data: \"my Hello\"}")); } });
ユーザー認証情報の処理
Android TV アプリでは、特定のユーザーのみにアプリ セッションの起動やセッションへの参加を許可する場合があります。たとえば、次の場合にのみ、送信者に起動や参加を許可します。
- センダーアプリが、ATV アプリと同じアカウントとプロファイルにログインしていること。
- センダーアプリがログインしているアカウントは同じだが、ATV アプリとは異なるプロファイルである。
アプリが複数のユーザーまたは匿名ユーザーを処理できる場合は、追加のユーザーに ATV セッションへの参加を許可できます。ユーザーが認証情報を提供した場合、ATV アプリはユーザーの進捗状況やその他のユーザーデータを適切に追跡できるように、認証情報を処理する必要があります。
センダーアプリが Android TV アプリを起動したとき、または Android TV アプリに参加したとき、センダーアプリは、セッションに参加しているユーザーを表す認証情報を提供する必要があります。
センダーが Android TV アプリを起動して参加する前に、起動チェッカーを指定して、センダーの認証情報が許可されているかどうかを確認できます。設定されていない場合、Cast Connect SDK はフォールバックしてウェブ レシーバーを起動します。
送信者アプリ起動認証情報データ
センダー側では、セッションに参加するユーザーを表す CredentialsData
を指定できます。
credentials
は、ATV アプリが理解できる限り、ユーザーが定義できる文字列です。credentialsType
は、CredentialsData
の取得元プラットフォームを定義します。カスタム値にすることもできます。デフォルトでは、送信元のプラットフォームに設定されています。
CredentialsData
は、起動時または参加時にのみ Android TV アプリに渡されます。接続中に設定を再度設定すると、Android TV アプリに渡されません。接続中にセンダーがプロファイルを切り替えた場合は、セッションをそのままにするか、新しいプロファイルがセッションに対応していないと思われる場合は SessionManager.endCurrentCastSession(boolean stopCasting)
を呼び出すことができます。
各送信者の CredentialsData
は、CastReceiverContext
の getSenders
を使用して SenderInfo
を取得し、getCastLaunchRequest()
で CastLaunchRequest
を取得し、getCredentialsData()
を使用して取得できます。
play-services-cast-framework
バージョン 19.0.0
以降が必要です。
CastContext.getSharedInstance().setLaunchCredentialsData( CredentialsData.Builder() .setCredentials("{\"userId\": \"abc\"}") .build() )
CastContext.getSharedInstance().setLaunchCredentialsData( new CredentialsData.Builder() .setCredentials("{\"userId\": \"abc\"}") .build());
google-cast-sdk
バージョン v4.8.1
以降が必要です。
オプションの設定後にいつでも呼び出すことができます: GCKCastContext.setSharedInstanceWith(options)
。
GCKCastContext.sharedInstance().setLaunch( GCKCredentialsData(credentials: "{\"userId\": \"abc\"}")
Chromium ブラウザのバージョン M87
以降が必要です。
オプションの設定後にいつでも呼び出すことができます: cast.framework.CastContext.getInstance().setOptions(options);
。
let credentialsData = new chrome.cast.CredentialsData("{\"userId\": \"abc\"}"); cast.framework.CastContext.getInstance().setLaunchCredentialsData(credentialsData);
ATV 起動リクエスト チェッカーの実装
センダーが起動または参加を試みると、CredentialsData
が Android TV アプリに渡されます。LaunchRequestChecker
を実装できます。リクエストを許可または拒否できます
リクエストが拒否された場合は、ATV アプリ内でネイティブに起動するのではなく、ウェブレシーバーが読み込まれます。起動または参加をリクエストしているユーザーに ATV が対応できない場合は、リクエストを拒否する必要があります。たとえば、リクエストしているのとは異なるユーザーが ATV アプリにログインしていて、アプリが認証情報の切り替えを処理できない場合、または現在 ATV アプリにログインしているユーザーが存在しない場合などです。
リクエストが許可されると、ATV アプリが起動します。この動作は、ユーザーが ATV アプリにログインしていないときに読み込みリクエストの送信をサポートしているか、またはユーザーの不一致があるかに応じてカスタマイズできます。この動作は LaunchRequestChecker
でカスタマイズできます。
CastReceiverOptions.LaunchRequestChecker
インターフェースを実装するクラスを作成します。
class MyLaunchRequestChecker : LaunchRequestChecker { override fun checkLaunchRequestSupported(launchRequest: CastLaunchRequest): Task{ return Tasks.call { myCheckLaunchRequest( launchRequest ) } } } private fun myCheckLaunchRequest(launchRequest: CastLaunchRequest): Boolean { val credentialsData = launchRequest.getCredentialsData() ?: return false // or true if you allow anonymous users to join. // The request comes from a mobile device, e.g. checking user match. return if (credentialsData.credentialsType == CredentialsData.CREDENTIALS_TYPE_ANDROID) { myCheckMobileCredentialsAllowed(credentialsData.getCredentials()) } else false // Unrecognized credentials type. }
public class MyLaunchRequestChecker implements CastReceiverOptions.LaunchRequestChecker { @Override public TaskcheckLaunchRequestSupported(CastLaunchRequest launchRequest) { return Tasks.call(() -> myCheckLaunchRequest(launchRequest)); } } private boolean myCheckLaunchRequest(CastLaunchRequest launchRequest) { CredentialsData credentialsData = launchRequest.getCredentialsData(); if (credentialsData == null) { return false; // or true if you allow anonymous users to join. } // The request comes from a mobile device, e.g. checking user match. if (credentialsData.getCredentialsType().equals(CredentialsData.CREDENTIALS_TYPE_ANDROID)) { return myCheckMobileCredentialsAllowed(credentialsData.getCredentials()); } // Unrecognized credentials type. return false; }
ReceiverOptionsProvider
で設定します。
class MyReceiverOptionsProvider : ReceiverOptionsProvider { override fun getOptions(context: Context?): CastReceiverOptions { return CastReceiverOptions.Builder(context) ... .setLaunchRequestChecker(MyLaunchRequestChecker()) .build() } }
public class MyReceiverOptionsProvider implements ReceiverOptionsProvider { @Override public CastReceiverOptions getOptions(Context context) { return new CastReceiverOptions.Builder(context) ... .setLaunchRequestChecker(new MyLaunchRequestChecker()) .build(); } }
LaunchRequestChecker
で true
を解決すると、ATV アプリが起動し、false
によって Web Receiver アプリが起動します。
カスタム メッセージの送受信
キャスト プロトコルを使用すると、センダーとレシーバー アプリの間でカスタム文字列メッセージを送信できます。CastReceiverContext
を初期化する前に、メッセージを送信する名前空間(チャンネル)を登録する必要があります。
Android TV - カスタム名前空間を指定する
設定時に、CastReceiverOptions
でサポートされている名前空間を指定する必要があります。
class MyReceiverOptionsProvider : ReceiverOptionsProvider { override fun getOptions(context: Context?): CastReceiverOptions { return CastReceiverOptions.Builder(context) .setCustomNamespaces( Arrays.asList("urn:x-cast:com.example.cast.mynamespace") ) .build() } }
public class MyReceiverOptionsProvider implements ReceiverOptionsProvider { @Override public CastReceiverOptions getOptions(Context context) { return new CastReceiverOptions.Builder(context) .setCustomNamespaces( Arrays.asList("urn:x-cast:com.example.cast.mynamespace")) .build(); } }
Android TV - メッセージの送信
// If senderId is null, then the message is broadcasted to all senders. CastReceiverContext.getInstance().sendMessage( "urn:x-cast:com.example.cast.mynamespace", senderId, customString)
// If senderId is null, then the message is broadcasted to all senders. CastReceiverContext.getInstance().sendMessage( "urn:x-cast:com.example.cast.mynamespace", senderId, customString);
Android TV - カスタム名前空間メッセージを受信する
class MyCustomMessageListener : MessageReceivedListener { override fun onMessageReceived( namespace: String, senderId: String?, message: String ) { ... } } CastReceiverContext.getInstance().setMessageReceivedListener( "urn:x-cast:com.example.cast.mynamespace", new MyCustomMessageListener());
class MyCustomMessageListener implements CastReceiverContext.MessageReceivedListener { @Override public void onMessageReceived( String namespace, String senderId, String message) { ... } } CastReceiverContext.getInstance().setMessageReceivedListener( "urn:x-cast:com.example.cast.mynamespace", new MyCustomMessageListener());