このページには、Android TV レシーバー アプリをカスタマイズするために利用できる機能のコード スニペットと説明が掲載されています。
ライブラリの構成
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.8.0 以降をターゲットとしていることを確認します。 -
iOS 13 以降をターゲットに設定していること。詳しくは、リリースノートをご覧ください。
platform: ios, '13' def target_pods pod 'google-cast-sdk', '~>4.8.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(必須) | メディアの再生時間。 |
METADATA_KEY_MEDIA_URI | Content ID |
METADATA_KEY_ARTIST | アーティスト。 |
METADATA_KEY_ALBUM | アルバム。 |
PlaybackStateCompat
必須のメソッド | 説明 |
---|---|
setActions() | サポートされているメディア コマンドを設定します。 |
setState() | 再生状態と現在の位置を設定します。 |
MediaSessionCompat
必須のメソッド | 説明 |
---|---|
setRepeatMode() | 繰り返しモードを設定します。 |
setShuffleMode() | シャッフル モードを設定します。 |
setMetadata() | メディア メタデータを設定します。 |
setPlaybackState() | 再生状態を設定します。 |
private fun updateMediaSession() { val metadata = MediaMetadataCompat.Builder() .putString(MediaMetadataCompat.METADATA_KEY_TITLE, "title") .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "subtitle") .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI, mMovie.getCardImageUrl()) .build() val playbackState = PlaybackStateCompat.Builder() .setState( PlaybackStateCompat.STATE_PLAYING, player.getPosition(), player.getPlaybackSpeed(), System.currentTimeMillis() ) .build() mediaSession.setMetadata(metadata) mediaSession.setPlaybackState(playbackState) }
private void updateMediaSession() { MediaMetadataCompat metadata = new MediaMetadataCompat.Builder() .putString(MediaMetadataCompat.METADATA_KEY_TITLE, "title") .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "subtitle") .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI,mMovie.getCardImageUrl()) .build(); PlaybackStateCompat playbackState = new PlaybackStateCompat.Builder() .setState( PlaybackStateCompat.STATE_PLAYING, player.getPosition(), player.getPlaybackSpeed(), System.currentTimeMillis()) .build(); mediaSession.setMetadata(metadata); mediaSession.setPlaybackState(playbackState); }
トランスポート制御の処理
アプリでは、メディア セッション トランスポート コントロール コールバックを実装する必要があります。次の表に、処理する必要があるトランスポート制御アクションを示します。
MediaSessionCompat.Callback
アクション | 説明 |
---|---|
onPlay() | 再開 |
onPause() | 一時停止 |
onSeekTo() | 特定の位置に移動 |
onStop() | 現在のメディアを停止 |
class MyMediaSessionCallback : MediaSessionCompat.Callback() { override fun onPause() { // Pause the player and update the play state. ... } override fun onPlay() { // Resume the player and update the play state. ... } override fun onSeekTo (long pos) { // Seek and update the play state. ... } ... } mediaSession.setCallback( MyMediaSessionCallback() );
public MyMediaSessionCallback extends MediaSessionCompat.Callback { public void onPause() { // Pause the player and update the play state. ... } public void onPlay() { // Resume the player and update the play state. ... } public void onSeekTo (long pos) { // Seek and update the play state. ... } ... } mediaSession.setCallback(new MyMediaSessionCallback());
キャスト サポートの構成
送信側アプリによって起動リクエストが送信されると、アプリの名前空間を持つインテントが作成されます。アプリは、TV アプリの起動時にこれを処理し、CastReceiverContext
オブジェクトのインスタンスを作成します。CastReceiverContext
オブジェクトは、TV アプリの実行中にキャストを操作するために必要です。このオブジェクトにより、TV アプリは接続済みのセンダーからのキャスト メディア メッセージを受け入れることができます。
Android TV のセットアップ
起動インテント フィルタの追加
送信側アプリからの起動インテントを処理するアクティビティに新しいインテント フィルタを追加します。
<activity android:name="com.example.activity">
<intent-filter>
<action android:name="com.google.android.gms.cast.tv.action.LAUNCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
レシーバー オプション プロバイダを指定する
CastReceiverOptions
を指定するには、ReceiverOptionsProvider
を実装する必要があります。
class MyReceiverOptionsProvider : ReceiverOptionsProvider { override fun getOptions(context: Context?): CastReceiverOptions { return CastReceiverOptions.Builder(context) .setStatusText("My App") .build() } }
public class MyReceiverOptionsProvider implements ReceiverOptionsProvider { @Override public CastReceiverOptions getOptions(Context context) { return new CastReceiverOptions.Builder(context) .setStatusText("My App") .build(); } }
次に、AndroidManifest
でオプション プロバイダを指定します。
<meta-data
android:name="com.google.android.gms.cast.tv.RECEIVER_OPTIONS_PROVIDER_CLASS_NAME"
android:value="com.example.mysimpleatvapplication.MyReceiverOptionsProvider" />
ReceiverOptionsProvider
は、CastReceiverContext
の初期化時に CastReceiverOptions
を提供するために使用されます。
キャスト レシーバーのコンテキスト
アプリの作成時に CastReceiverContext
を初期化します。
override fun onCreate() { CastReceiverContext.initInstance(this) ... }
@Override public void onCreate() { CastReceiverContext.initInstance(this); ... }
アプリがフォアグラウンドに移動したら、CastReceiverContext
を開始します。
CastReceiverContext.getInstance().start()
CastReceiverContext.getInstance().start();
動画アプリまたはバックグラウンド再生をサポートしていないアプリのアプリがバックグラウンドに移動したら、CastReceiverContext
で stop()
を呼び出します。
// Player has stopped. CastReceiverContext.getInstance().stop()
// Player has stopped. CastReceiverContext.getInstance().stop();
また、アプリがバックグラウンドでの再生をサポートしている場合は、バックグラウンドでの再生が停止したときに CastReceiverContext
で stop()
を呼び出します。
特にネイティブ アプリに複数のアクティビティがある場合は、androidx.lifecycle
ライブラリの LifecycleObserver を使用して、CastReceiverContext.start()
と CastReceiverContext.stop()
の呼び出しを管理することを強くおすすめします。これにより、start()
と stop()
を異なるアクティビティから呼び出す際の競合状態を回避できます。
// Create a LifecycleObserver class. class MyLifecycleObserver : DefaultLifecycleObserver { override fun onStart(owner: LifecycleOwner) { // App prepares to enter foreground. CastReceiverContext.getInstance().start() } override fun onStop(owner: LifecycleOwner) { // App has moved to the background or has terminated. CastReceiverContext.getInstance().stop() } } // Add the observer when your application is being created. class MyApplication : Application() { fun onCreate() { super.onCreate() // Initialize CastReceiverContext. CastReceiverContext.initInstance(this /* android.content.Context */) // Register LifecycleObserver ProcessLifecycleOwner.get().lifecycle.addObserver( MyLifecycleObserver()) } }
// Create a LifecycleObserver class. public class MyLifecycleObserver implements DefaultLifecycleObserver { @Override public void onStart(LifecycleOwner owner) { // App prepares to enter foreground. CastReceiverContext.getInstance().start(); } @Override public void onStop(LifecycleOwner owner) { // App has moved to the background or has terminated. CastReceiverContext.getInstance().stop(); } } // Add the observer when your application is being created. public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); // Initialize CastReceiverContext. CastReceiverContext.initInstance(this /* android.content.Context */); // Register LifecycleObserver ProcessLifecycleOwner.get().getLifecycle().addObserver( new MyLifecycleObserver()); } }
// In AndroidManifest.xml set MyApplication as the application class
<application
...
android:name=".MyApplication">
MediaSession を MediaManager に接続する
MediaSession
を作成するときは、現在の MediaSession
トークンを CastReceiverContext
に提供する必要もあります。これにより、コマンドの送信先を把握し、メディアの再生状態を取得できます。
val mediaManager: MediaManager = receiverContext.getMediaManager() mediaManager.setSessionCompatToken(currentMediaSession.getSessionToken())
MediaManager mediaManager = receiverContext.getMediaManager(); mediaManager.setSessionCompatToken(currentMediaSession.getSessionToken());
再生がアクティブでないときに MediaSession
を解放する場合は、MediaManager
に null トークンを設定する必要があります。
myPlayer.stop() mediaSession.release() mediaManager.setSessionCompatToken(null)
myPlayer.stop(); mediaSession.release(); mediaManager.setSessionCompatToken(null);
アプリがバックグラウンドで実行されている間のメディア再生をサポートしている場合は、アプリがバックグラウンドに送信されるときに CastReceiverContext.stop()
を呼び出すのではなく、アプリがバックグラウンドにありメディアを再生しなくなった場合にのみ呼び出す必要があります。例:
class MyLifecycleObserver : DefaultLifecycleObserver { ... // App has moved to the background. override fun onPause(owner: LifecycleOwner) { mIsBackground = true myStopCastReceiverContextIfNeeded() } } // Stop playback on the player. private fun myStopPlayback() { myPlayer.stop() myStopCastReceiverContextIfNeeded() } // Stop the CastReceiverContext when both the player has // stopped and the app has moved to the background. private fun myStopCastReceiverContextIfNeeded() { if (mIsBackground && myPlayer.isStopped()) { CastReceiverContext.getInstance().stop() } }
public class MyLifecycleObserver implements DefaultLifecycleObserver { ... // App has moved to the background. @Override public void onPause(LifecycleOwner owner) { mIsBackground = true; myStopCastReceiverContextIfNeeded(); } } // Stop playback on the player. private void myStopPlayback() { myPlayer.stop(); myStopCastReceiverContextIfNeeded(); } // Stop the CastReceiverContext when both the player has // stopped and the app has moved to the background. private void myStopCastReceiverContextIfNeeded() { if (mIsBackground && myPlayer.isStopped()) { CastReceiverContext.getInstance().stop(); } }
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
フラグは、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
以降が必要です。
GCKCastOptions
の一部である GCKLaunchOptions
で androidReceiverCompatible
フラグを設定します。
let options = GCKCastOptions(discoveryCriteria: GCKDiscoveryCriteria(applicationID: kReceiverAppID)) ... let launchOptions = GCKLaunchOptions() launchOptions.androidReceiverCompatible = true options.launchOptions = launchOptions GCKCastContext.setSharedInstanceWith(options)
Chromium ブラウザのバージョン M87
以降が必要です。
const context = cast.framework.CastContext.getInstance(); const castOptions = new cast.framework.CastOptions(); castOptions.receiverApplicationId = kReceiverAppID; castOptions.androidReceiverCompatible = true; context.setOptions(castOptions);
Cast Developer Console の設定
Android TV アプリを構成する
Cast デベロッパー コンソールに Android TV アプリのパッケージ名を追加して、キャストアプリ ID と関連付けます。
デベロッパー デバイスを登録する
Cast Developer Console で、開発に使用する Android TV デバイスのシリアル番号を登録します。
セキュリティ上の理由から、登録していない場合、Cast Connect は Google Play ストアからインストールされたアプリでしか機能しません。
キャスト開発用にキャスト デバイスまたは Android TV デバイスを登録する方法について詳しくは、登録ページをご覧ください。
メディアの読み込み
ディープリンク サポートを Android TV アプリにすでに実装している場合は、Android TV マニフェストに同様の定義を構成する必要があります。
<activity android:name="com.example.activity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="https"/>
<data android:host="www.example.com"/>
<data android:pathPattern=".*"/>
</intent-filter>
</activity>
送信者のエンティティごとに読み込む
センダー側では、読み込みリクエストのメディア情報で entity
を設定することで、ディープリンクを渡すことができます。
val mediaToLoad = MediaInfo.Builder("some-id") .setEntity("https://example.com/watch/some-id") ... .build() val loadRequest = MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") ... .build() remoteMediaClient.load(loadRequest)
MediaInfo mediaToLoad = new MediaInfo.Builder("some-id") .setEntity("https://example.com/watch/some-id") ... .build(); MediaLoadRequestData loadRequest = new MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") ... .build(); remoteMediaClient.load(loadRequest);
let mediaInfoBuilder = GCKMediaInformationBuilder(entity: "https://example.com/watch/some-id") ... mediaInformation = mediaInfoBuilder.build() let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder() mediaLoadRequestDataBuilder.mediaInformation = mediaInformation mediaLoadRequestDataBuilder.credentials = "user-credentials" ... let mediaLoadRequestData = mediaLoadRequestDataBuilder.build() remoteMediaClient?.loadMedia(with: mediaLoadRequestData)
Chromium ブラウザのバージョン M87
以降が必要です。
let mediaInfo = new chrome.cast.media.MediaInfo('some-id"', 'video/mp4'); mediaInfo.entity = 'https://example.com/watch/some-id'; ... let request = new chrome.cast.media.LoadRequest(mediaInfo); request.credentials = 'user-credentials'; ... cast.framework.CastContext.getInstance().getCurrentSession().loadMedia(request);
読み込みコマンドは、ディープリンクとデベロッパー コンソールで定義したパッケージ名を含むインテントを介して送信されます。
センダーでの ATV 認証情報の設定
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);
ウェブ レシーバー アプリが起動されると、読み込みリクエストで entity
と credentials
が使用されます。ただし、Android TV アプリが起動されると、SDK は entity
と credentials
を atvEntity
と atvCredentials
(指定されている場合)でオーバーライドします。
Content ID または MediaQueueData で読み込む
entity
や atvEntity
を使用せず、メディア情報で Content ID またはコンテンツ URL を使用している場合、またはより詳細なメディア読み込みリクエスト データを使用する場合は、Android TV アプリに次の事前定義インテント フィルタを追加する必要があります。
<activity android:name="com.example.activity">
<intent-filter>
<action android:name="com.google.android.gms.cast.tv.action.LOAD"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
送信側では、エンティティ別の読み込みと同様に、コンテンツ情報を使用して読み込みリクエストを作成し、load()
を呼び出すことができます。
val mediaToLoad = MediaInfo.Builder("some-id").build() val loadRequest = MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") ... .build() remoteMediaClient.load(loadRequest)
MediaInfo mediaToLoad = new MediaInfo.Builder("some-id").build(); MediaLoadRequestData loadRequest = new MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") ... .build(); remoteMediaClient.load(loadRequest);
let mediaInfoBuilder = GCKMediaInformationBuilder(contentId: "some-id") ... mediaInformation = mediaInfoBuilder.build() let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder() mediaLoadRequestDataBuilder.mediaInformation = mediaInformation mediaLoadRequestDataBuilder.credentials = "user-credentials" ... let mediaLoadRequestData = mediaLoadRequestDataBuilder.build() remoteMediaClient?.loadMedia(with: mediaLoadRequestData)
Chromium ブラウザのバージョン M87
以降が必要です。
let mediaInfo = new chrome.cast.media.MediaInfo('some-id"', 'video/mp4'); ... let request = new chrome.cast.media.LoadRequest(mediaInfo); ... cast.framework.CastContext.getInstance().getCurrentSession().loadMedia(request);
読み込みリクエストの処理
アクティビティでこれらの読み込みリクエストを処理するには、アクティビティのライフサイクル コールバックでインテントを処理する必要があります。
class MyActivity : Activity() { override fun onStart() { super.onStart() val mediaManager = CastReceiverContext.getInstance().getMediaManager() // Pass the intent to the SDK. You can also do this in onCreate(). if (mediaManager.onNewIntent(intent)) { // If the SDK recognizes the intent, you should early return. return } // If the SDK doesn't recognize the intent, you can handle the intent with // your own logic. ... } // For some cases, a new load intent triggers onNewIntent() instead of // onStart(). override fun onNewIntent(intent: Intent) { val mediaManager = CastReceiverContext.getInstance().getMediaManager() // Pass the intent to the SDK. You can also do this in onCreate(). if (mediaManager.onNewIntent(intent)) { // If the SDK recognizes the intent, you should early return. return } // If the SDK doesn't recognize the intent, you can handle the intent with // your own logic. ... } }
public class MyActivity extends Activity { @Override protected void onStart() { super.onStart(); MediaManager mediaManager = CastReceiverContext.getInstance().getMediaManager(); // Pass the intent to the SDK. You can also do this in onCreate(). if (mediaManager.onNewIntent(getIntent())) { // If the SDK recognizes the intent, you should early return. return; } // If the SDK doesn't recognize the intent, you can handle the intent with // your own logic. ... } // For some cases, a new load intent triggers onNewIntent() instead of // onStart(). @Override protected void onNewIntent(Intent intent) { MediaManager mediaManager = CastReceiverContext.getInstance().getMediaManager(); // Pass the intent to the SDK. You can also do this in onCreate(). if (mediaManager.onNewIntent(intent)) { // If the SDK recognizes the intent, you should early return. return; } // If the SDK doesn't recognize the intent, you can handle the intent with // your own logic. ... } }
MediaManager
は、そのインテントが読み込みインテントであることを検出すると、そのインテントから MediaLoadRequestData
オブジェクトを抽出し、MediaLoadCommandCallback.onLoad()
を呼び出します。読み込みリクエストを処理するには、このメソッドをオーバーライドする必要があります。コールバックは、MediaManager.onNewIntent()
を呼び出す前に登録する必要があります(アクティビティまたはアプリの onCreate()
メソッドで登録することをおすすめします)。
class MyActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val mediaManager = CastReceiverContext.getInstance().getMediaManager() mediaManager.setMediaLoadCommandCallback(MyMediaLoadCommandCallback()) } } class MyMediaLoadCommandCallback : MediaLoadCommandCallback() { override fun onLoad( senderId: String?, loadRequestData: MediaLoadRequestData ): Task{ return Tasks.call { // Resolve the entity into your data structure and load media. val mediaInfo = loadRequestData.getMediaInfo() if (!checkMediaInfoSupported(mediaInfo)) { // Throw MediaException to indicate load failure. throw MediaException( MediaError.Builder() .setDetailedErrorCode(DetailedErrorCode.LOAD_FAILED) .setReason(MediaError.ERROR_REASON_INVALID_REQUEST) .build() ) } myFillMediaInfo(MediaInfoWriter(mediaInfo)) myPlayerLoad(mediaInfo.getContentUrl()) // Update media metadata and state (this clears all previous status // overrides). castReceiverContext.getMediaManager() .setDataFromLoad(loadRequestData) ... castReceiverContext.getMediaManager().broadcastMediaStatus() // Return the resolved MediaLoadRequestData to indicate load success. return loadRequestData } } private fun myPlayerLoad(contentURL: String) { myPlayer.load(contentURL) // Update the MediaSession state. val playbackState: PlaybackStateCompat = Builder() .setState( player.getState(), player.getPosition(), System.currentTimeMillis() ) ... .build() mediaSession.setPlaybackState(playbackState) }
public class MyActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); MediaManager mediaManager = CastReceiverContext.getInstance().getMediaManager(); mediaManager.setMediaLoadCommandCallback(new MyMediaLoadCommandCallback()); } } public class MyMediaLoadCommandCallback extends MediaLoadCommandCallback { @Override public TaskonLoad(String senderId, MediaLoadRequestData loadRequestData) { return Tasks.call(() -> { // Resolve the entity into your data structure and load media. MediaInfo mediaInfo = loadRequestData.getMediaInfo(); if (!checkMediaInfoSupported(mediaInfo)) { // Throw MediaException to indicate load failure. throw new MediaException( new MediaError.Builder() .setDetailedErrorCode(DetailedErrorCode.LOAD_FAILED) .setReason(MediaError.ERROR_REASON_INVALID_REQUEST) .build()); } myFillMediaInfo(new MediaInfoWriter(mediaInfo)); myPlayerLoad(mediaInfo.getContentUrl()); // Update media metadata and state (this clears all previous status // overrides). castReceiverContext.getMediaManager() .setDataFromLoad(loadRequestData); ... castReceiverContext.getMediaManager().broadcastMediaStatus(); // Return the resolved MediaLoadRequestData to indicate load success. return loadRequestData; }); } private void myPlayerLoad(String contentURL) { myPlayer.load(contentURL); // Update the MediaSession state. PlaybackStateCompat playbackState = new PlaybackStateCompat.Builder() .setState( player.getState(), player.getPosition(), System.currentTimeMillis()) ... .build(); mediaSession.setPlaybackState(playbackState); }
読み込みインテントを処理するには、インテントを解析し、定義したデータ構造(読み込みリクエストの場合は MediaLoadRequestData
)に変換します。
メディア コマンドをサポートする
基本的な再生コントロールのサポート
基本的な統合コマンドには、メディア セッションと互換性のあるコマンドが含まれています。これらのコマンドは、メディア セッション コールバックを介して通知されます。これをサポートするには、メディア セッションにコールバックを登録する必要があります(すでに登録している可能性があります)。
private class MyMediaSessionCallback : MediaSessionCompat.Callback() { override fun onPause() { // Pause the player and update the play state. myPlayer.pause() } override fun onPlay() { // Resume the player and update the play state. myPlayer.play() } override fun onSeekTo(pos: Long) { // Seek and update the play state. myPlayer.seekTo(pos) } ... } mediaSession.setCallback(MyMediaSessionCallback())
private class MyMediaSessionCallback extends MediaSessionCompat.Callback { @Override public void onPause() { // Pause the player and update the play state. myPlayer.pause(); } @Override public void onPlay() { // Resume the player and update the play state. myPlayer.play(); } @Override public void onSeekTo(long pos) { // Seek and update the play state. myPlayer.seekTo(pos); } ... } mediaSession.setCallback(new MyMediaSessionCallback());
キャスト操作コマンドのサポート
ただし、skipAd()
や setActiveMediaTracks()
など、MediaSession
では使用できないキャスト コマンドもあります。また、キャストキューは MediaSession
キューと完全には互換性がないため、一部のキューコマンドをここに実装する必要があります。
class MyMediaCommandCallback : MediaCommandCallback() { override fun onSkipAd(requestData: RequestData?): Task{ // Skip your ad ... return Tasks.forResult(null) } } val mediaManager = CastReceiverContext.getInstance().getMediaManager() mediaManager.setMediaCommandCallback(MyMediaCommandCallback())
public class MyMediaCommandCallback extends MediaCommandCallback { @Override public TaskonSkipAd(RequestData requestData) { // Skip your ad ... return Tasks.forResult(null); } } MediaManager mediaManager = CastReceiverContext.getInstance().getMediaManager(); mediaManager.setMediaCommandCallback(new MyMediaCommandCallback());
サポートされているメディア コマンドを指定する
キャスト レシーバーと同様に、センダーが特定の UI コントロールを有効または無効にすることができるように、Android TV アプリでサポートされているコマンドを指定する必要があります。MediaSession
に含まれるコマンドの場合は、PlaybackStateCompat
でコマンドを指定します。追加のコマンドは MediaStatusModifier
で指定する必要があります。
// Set media session supported commands val playbackState: PlaybackStateCompat = PlaybackStateCompat.Builder() .setActions(PlaybackStateCompat.ACTION_PLAY or PlaybackStateCompat.ACTION_PAUSE) .setState(PlaybackStateCompat.STATE_PLAYING) .build() mediaSession.setPlaybackState(playbackState) // Set additional commands in MediaStatusModifier val mediaManager = CastReceiverContext.getInstance().getMediaManager() mediaManager.getMediaStatusModifier() .setMediaCommandSupported(MediaStatus.COMMAND_QUEUE_NEXT)
// Set media session supported commands PlaybackStateCompat playbackState = new PlaybackStateCompat.Builder() .setActions(PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE) .setState(PlaybackStateCompat.STATE_PLAYING) .build(); mediaSession.setPlaybackState(playbackState); // Set additional commands in MediaStatusModifier MediaManager mediaManager = CastReceiverContext.getInstance().getMediaManager(); mediaManager.getMediaStatusModifier() .setMediaCommandSupported(MediaStatus.COMMAND_QUEUE_NEXT);
サポートされていないボタンを非表示にする
Android TV アプリが基本的なメディア コントロールしかサポートしていないのに、ウェブ レシーバー アプリがより高度なコントロールをサポートしている場合は、Android TV アプリにキャストする際にセンダーアプリが正しく動作することを確認する必要があります。たとえば、Android TV アプリが再生レートの変更をサポートしていないのに対し、ウェブレシーバー アプリはサポートしている場合は、各プラットフォームでサポートされているアクションを正しく設定し、センダーアプリで UI が正しくレンダリングされることを確認する必要があります。
MediaStatus の変更
トラック、広告、ライブ、キューなどの高度な機能をサポートするには、Android TV アプリで MediaSession
では確認できない追加情報を提供する必要があります。
これを実現するために、MediaStatusModifier
クラスが用意されています。MediaStatusModifier
は常に CastReceiverContext
で設定した MediaSession
で動作します。
MediaStatus
を作成してブロードキャストするには:
val mediaManager: MediaManager = castReceiverContext.getMediaManager() val statusModifier: MediaStatusModifier = mediaManager.getMediaStatusModifier() statusModifier .setLiveSeekableRange(seekableRange) .setAdBreakStatus(adBreakStatus) .setCustomData(customData) mediaManager.broadcastMediaStatus()
MediaManager mediaManager = castReceiverContext.getMediaManager(); MediaStatusModifier statusModifier = mediaManager.getMediaStatusModifier(); statusModifier .setLiveSeekableRange(seekableRange) .setAdBreakStatus(adBreakStatus) .setCustomData(customData); mediaManager.broadcastMediaStatus();
Google のクライアント ライブラリは MediaSession
からベース MediaStatus
を取得します。Android TV アプリでは、MediaStatus
修飾子を使用して追加ステータスを指定し、ステータスをオーバーライドできます。
一部の状態とメタデータは MediaSession
と MediaStatusModifier
の両方で設定できます。MediaSession
のみで設定することを強くおすすめします。その場合でも、修飾子を使用して MediaSession
の状態をオーバーライドできます。修飾子のステータスの方が MediaSession
によって提供される値よりも常に優先度が高いため、この方法は推奨されません。
送信前に MediaStatus をインターセプトする
Web Receiver SDK と同様に、送信前に最後の仕上げを行う場合は、MediaStatusInterceptor
を指定して送信する MediaStatus
を処理します。MediaStatusWriter
を渡して、送信前に MediaStatus
を操作します。
mediaManager.setMediaStatusInterceptor(object : MediaStatusInterceptor { override fun intercept(mediaStatusWriter: MediaStatusWriter) { // Perform customization. mediaStatusWriter.setCustomData(JSONObject("{data: \"my Hello\"}")) } })
mediaManager.setMediaStatusInterceptor(new MediaStatusInterceptor() { @Override public void intercept(MediaStatusWriter mediaStatusWriter) { // Perform customization. mediaStatusWriter.setCustomData(new JSONObject("{data: \"my Hello\"}")); } });
ユーザー認証情報の処理
Android TV アプリでは、アプリ セッションの起動または参加を特定のユーザーのみに許可している場合があります。たとえば、次の場合にのみ送信者の起動や参加を許可します。
- 送信側アプリが ATV アプリと同じアカウントとプロファイルにログインしていること。
- 送信側アプリが同じアカウントにログインしているが、ATV アプリとは異なるプロファイルになっています。
アプリが複数ユーザーまたは匿名ユーザーを処理できる場合は、追加の任意のユーザーに ATV セッションへの参加を許可できます。ユーザーが認証情報を提供する場合、ATV アプリで認証情報を処理して、進行状況やその他のユーザーデータを適切に追跡できるようにする必要があります。
送信側アプリが Android TV アプリを起動したとき、または Android TV アプリに参加したときに、送信側アプリはセッションに参加するユーザーを表す認証情報を提供する必要があります。
センダーが起動して Android TV アプリに参加する前に、リリース チェッカーを指定して、送信者の認証情報が許可されているかどうかを確認できます。インストールされていない場合、Cast Connect SDK は Web Receiver の起動にフォールバックします。
センダーアプリの起動認証情報データ
センダー側では、セッションに参加しているユーザーを表す 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.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());