このページでは、Android TV Receiver アプリのカスタマイズに使用できるコード スニペットと機能について説明します。
ライブラリの構成
Android TV アプリで Cast Connect API を利用できるようにするには:
-
アプリケーション モジュール ディレクトリ内の
build.gradle
ファイルを開きます。 -
記載されている
repositories
にgoogle()
が含まれていることを確認します。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 センダーアプリの場合:
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 Connect ライブラリでも使用され、一時停止などの送信者から特定のメッセージを受け取ったことを通知します。
メディア セッションとメディア セッションを初期化する方法の詳細については、メディア セッション ガイドの操作をご覧ください。
メディア セッションのライフサイクル
アプリでは、再生の開始時にメディア セッションを作成し、制御できなくなったときに解放する必要があります。たとえば、アプリが動画アプリの場合は、ユーザーが再生アクティビティを終了したら [戻る] を選択して他のコンテンツを参照するか、アプリをバックグラウンドに移行することで、セッションを解放する必要があります。アプリが音楽アプリの場合は、アプリがメディアを再生しなくなったらリリースする必要があります。
セッションのステータスを更新しています
メディア セッションのデータは、プレーヤーのステータスに合わせて最新の状態にする必要があります。たとえば、再生が一時停止している場合は、再生状態とサポートされているアクションを更新する必要があります。次の表に、最新の状態に保つ役割を示します。
MediaMetadataCompat
メタデータ フィールド | 説明 |
---|---|
METADATA_KEY_TITLE(必須) | メディアのタイトル。 |
METADATA_KEY_DISPLAY_SUBTITLE | サブタイトル。 |
METADATA_KEY_DISPLAY_ICON_URI | アイコンの URL。 |
METADATA_KEY_DURATION (必須) | メディアの再生時間。 |
メタデータのキーの 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 アプリの実行中にキャストを操作するために必要です。このオブジェクトを使用すると、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
を使用すると、デフォルトでは MediaSessionCompat.Callback
によって処理される 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 に設定することで、その準備状況を宣言できます。
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 Developer Console の設定
Android TV アプリを構成する
Android TV アプリのパッケージ名を Cast Developer Console に追加して、キャストアプリ ID に関連付けます。
デベロッパー デバイスを登録する
キャスト デベロッパー コンソールで、開発に使用する 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 認証情報の設定
ウェブ レシーバ アプリと 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()
が呼び出される前に登録する必要があります(アクティビティまたはアプリの 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());
サポートされているメディア コマンドを指定する
キャスト レシーバーと同様に、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 と同じく、送信前に最後の仕上げを行う場合は、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.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
により 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());