このページでは、Android TV Receiver アプリのカスタマイズに使用できる機能のコード スニペットと説明について説明します。
ライブラリの構成
Android TV アプリで Cast Connect API を使用できるようにするには:
-
アプリケーション モジュール ディレクトリ内の
build.gradle
ファイルを開きます。 -
google()
がリストにあるrepositories
に含まれていることを確認します。repositories { google() }
-
アプリのターゲット デバイスタイプに応じて、最新バージョンのライブラリを依存関係に追加します。
-
Android レシーバー アプリの場合:
dependencies { implementation 'com.google.android.gms:play-services-cast-tv:21.0.0' implementation 'com.google.android.gms:play-services-cast:21.3.0' }
-
Android Sender アプリの場合:
dependencies { implementation 'com.google.android.gms:play-services-cast:21.0.0' implementation 'com.google.android.gms:play-services-cast-framework:21.3.0' }
-
Android レシーバー アプリの場合:
-
変更を保存して、ツールバーで
Sync Project with Gradle Files
をクリックします。
-
Podfile
がgoogle-cast-sdk
4.7.0 以降をターゲットとしていることを確認します。 -
iOS 12 以降を対象としている。詳細については、リリースノートをご覧ください。
platform: ios, '12' def target_pods pod 'google-cast-sdk', '~>4.7.0' 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 Cast ライブラリでも使用されます。
メディア セッションとメディア セッションを初期化する方法の詳細については、メディア セッション ガイドをご覧ください。
メディア セッションのライフサイクル
アプリでは、再生の開始時にメディア セッションを作成し、制御できなくなるときは解放する必要があります。たとえば、アプリが動画アプリの場合は、ユーザーが再生アクティビティを終了したときにセッションを解放する必要があります。[戻る] を選択して他のコンテンツをブラウジングするか、アプリをバックグラウンド処理する必要があります。アプリが音楽アプリの場合は、アプリがメディアを再生しなくなった場合にリリースする必要があります。
セッション ステータスの更新
メディア セッションのデータは、プレーヤーのステータスで最新の状態に保つ必要があります。たとえば、再生が一時停止したときは、再生状態とサポートされているアクションを更新する必要があります。次の表に、最新の状態を維持する責任のある状態を示します。
MediaMetadataCompat
メタデータ フィールド | 説明 |
---|---|
METADATA_KEY_TITLE (必須) | メディアのタイトル。 |
METADATA_KEY_DISPLAY_SUBTITLE | サブタイトル。 |
METADATA_KEY_DISPLAY_ICON_URI | アイコンの URL。 |
METADATA_KEY_DURATION (必須) | メディア持続時間。 |
METADATA_KEY_MEDIA_URI | Content ID |
メタデータのキーのアーティスト | アーティスト。 |
メタデータのキーのアルバム | アルバム。 |
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
Actions | 説明 |
---|---|
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 アプリの実行中に Cast を操作するために必要です。このオブジェクトにより、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(); } }
Cast Connect で Exoplayer を使用する
Exoplayer
を使用している場合、変更を手動で追跡する代わりに、MediaSessionConnector
を使用して、セッションや関連情報(再生状態など)を自動的に管理できます。
MediaSessionConnector.MediaButtonEventHandler
を使用すると、デフォルトでは setMediaButtonEventHandler(MediaButtonEventHandler)
によって処理される setMediaButtonEventHandler(MediaButtonEventHandler)
を呼び出して、MediaButton イベントを処理できます。
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 に設定することで、その readiness を宣言できます。
play-services-cast-framework
バージョン 19.0.0
以降が必要です。
androidReceiverCompatible
フラグは、CastOptions
の一部である LaunchOptions
に設定されます。
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
以降が必要です。
androidReceiverCompatible
フラグは、GCKCastOptions
の一部である GCKLaunchOptions
に設定されます。
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 Console の設定
Android TV アプリを設定する
Android TV アプリのパッケージ名を Cast Developer Console に追加して、キャストアプリ ID に関連付けます。
デベロッパー デバイスを登録する
Cast Developer Console で開発に使用する Android TV デバイスのシリアル番号を登録します。
登録しない場合、セキュリティ上の理由により、Cast Connect は Google Play ストアからインストールしたアプリに対してのみ機能します。
Cast 開発用に Cast デバイスまたは 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 認証情報の設定
Web Receiver アプリと 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);
Web Receiver アプリが起動された場合、読み込みリクエストで entity
と credentials
が使用されます。ただし、Android TV アプリが起動された場合、SDK は entity
と credentials
を atvEntity
と atvCredentials
(指定された場合)でオーバーライドします。
Content ID または MediaQueueData による読み込み
entity
または atvEntity
を使用せずに、メディア情報に Content ID または Content 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()
が呼び出される前に登録される必要があります(Activity または Application の 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());
キャスト コントロール コマンドのサポート
MediaSession
では利用できないキャスト コマンドがいくつかあります(skipAd()
や setActiveMediaTracks()
など)。また、一部のキューコマンドは、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());
サポートされているメディア コマンドを指定する
Android TV アプリは、キャスト レシーバと同様に、サポートされているコマンドを指定して、送信者が特定の UI コントロールを有効または無効にできるようにする必要があります。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 アプリにキャストする際に、送信側アプリが正しく動作することを確認する必要があります。たとえば、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();
クライアント ライブラリは MediaSession
からベース MediaStatus
を取得します。Android TV アプリは、MediaStatus
修飾子を使用して追加のステータスを指定し、ステータスをオーバーライドできます。
一部の状態とメタデータは MediaSession
と MediaStatusModifier
の両方で設定できます。MediaSession
のみに設定することを強くおすすめします。この修飾子は、引き続き MediaSession
で状態をオーバーライドできます。ただし、修飾子のステータスは常に MediaSession
で提供される値よりも優先度が高いため、おすすめしません。
送信する前に MediaStatus をインターセプトする
Web Receiver SDK と同様に、送信前に最後の仕上げを行いたい場合は、送信される MediaStatus
を処理する MediaStatusInterceptor
を指定します。送信する前に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.7.0
以降が必要です。
オプションが設定された後、いつでも呼び出すことができます: 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
がウェブレシーバー アプリを起動します。
カスタム メッセージを送受信する
キャスト プロトコルを使用すると、送信者と受信者のアプリケーション間でカスタム文字列メッセージを送信できます。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());