このページでは、Android TV レシーバー アプリのカスタマイズに使用できる機能のコード スニペットと説明について説明します。
ライブラリの構成
Android TV アプリで Cast Connect API を利用できるようにするには:
-
アプリケーション モジュール ディレクトリ内の
build.gradle
ファイルを開きます。 -
google()
がリストされたrepositories
に含まれていることを確認します。repositories { google() }
-
アプリの対象デバイスの種類に応じて、ライブラリの最新バージョンを依存関係に追加します。
-
Android レシーバー アプリの場合:
dependencies { implementation 'com.google.android.gms:play-services-cast-tv:21.1.1' implementation 'com.google.android.gms:play-services-cast:22.0.0' }
-
Android センダーアプリの場合:
dependencies { implementation 'com.google.android.gms:play-services-cast:21.1.1' implementation 'com.google.android.gms:play-services-cast-framework:22.0.0' }
-
Android レシーバー アプリの場合:
-
変更を保存し、ツールバーの
Sync Project with Gradle Files
をクリックします。
-
Podfile
がgoogle-cast-sdk
4.8.3 以降をターゲットにしていることを確認します。 -
iOS 14 以降をターゲットに設定します。詳細については、リリースノートをご覧ください。
platform: ios, '14' def target_pods pod 'google-cast-sdk', '~>4.8.3' 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 | コンテンツ 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());
Cast サポートの構成
送信元のアプリから起動リクエストが送信されると、アプリケーション名前空間を持つインテントの作成がトリガーされます。アプリは、これを処理し、テレビアプリの起動時に CastReceiverContext
オブジェクトのインスタンスを作成します。CastReceiverContext
オブジェクトは、TV アプリの実行中に Cast を操作するために必要です。このオブジェクトを使用すると、接続された送信者からの 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)
を呼び出して 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
以降が必要です。
androidReceiverCompatible
フラグは LaunchOptions
(CastOptions
の一部)で設定されます。
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
フラグは GCKLaunchOptions
(GCKCastOptions
の一部)で設定されます。
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 アプリを構成する
Cast Developer Console で Android TV アプリのパッケージ名を追加して、Cast アプリ ID に関連付けます。
デベロッパー デバイスを登録する
開発に使用する Android TV デバイスのシリアル番号を Cast Developer Console に登録します。
登録しないと、セキュリティ上の理由により、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);
ウェブ レシーバー アプリが起動すると、読み込みリクエストで entity
と credentials
が使用されます。ただし、Android TV アプリが起動されると、SDK は entity
と credentials
を atvEntity
と atvCredentials
(指定されている場合)でオーバーライドします。
Content ID または MediaQueueData による読み込み
entity
または atvEntity
を使用しておらず、メディア情報でコンテンツ 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()
が呼び出される前に登録する必要があります(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());
Cast コントロール コマンドのサポート
skipAd()
や setActiveMediaTracks()
など、MediaSession
では使用できない Cast コマンドがあります。また、Cast キューは MediaSession
キューと完全に互換性があるわけではないため、一部のキュー コマンドをここで実装する必要があります。
class MyMediaCommandCallback : MediaCommandCallback() { override fun onSkipAd(requestData: RequestData?): Task<Void?> { // 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());
サポートされているメディア コマンドを指定する
Cast レシーバーと同様に、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 アプリが基本的なメディア操作のみをサポートし、Web レシーバー アプリがより高度な操作をサポートしている場合は、Android TV アプリにキャストするときに送信元アプリが正しく動作するようにする必要があります。たとえば、Android TV アプリが再生レートの変更をサポートしていないが、Web レシーバー アプリがサポートしている場合は、各プラットフォームでサポートされているアクションを正しく設定し、送信元アプリが UI を正しくレンダリングするようにする必要があります。
MediaStatus の変更
トラック、広告、ライブ、キューなどの高度な機能をサポートするには、MediaSession
では確認できない追加情報を Android TV アプリで提供する必要があります。
そのために、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 アプリを起動して参加する前に、起動チェックを指定して、送信者の認証情報が許可されているかどうかを確認できます。そうでない場合、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.3
以降が必要です。
オプションが設定された後でいつでも呼び出せます。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
でウェブ レシーバー アプリが起動します。
カスタム メッセージの送受信
Cast プロトコルを使用すると、送信者とレシーバー アプリ間でカスタム文字列メッセージを送信できます。CastReceiverContext
を初期化する前に、メッセージを送信する名前空間(チャンネル)を登録する必要があります。
Android TV - カスタム Namespace を指定する
セットアップ時に、サポートされている Namespace を 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());