Android TV レシーバーにコア機能を追加する

コレクションでコンテンツを整理 必要に応じて、コンテンツの保存と分類を行います。

このページでは、Android TV Receiver アプリのカスタマイズに使用できるコード スニペットと機能について説明します。

ライブラリの構成

Android TV アプリで Cast Connect API を使用できるようにするには:

Android
  1. アプリケーション モジュール ディレクトリ内の build.gradle ファイルを開きます。
  2. 一覧表示されている repositoriesgoogle() が含まれていることを確認します。
      repositories {
        google()
      }
  3. アプリのターゲット デバイスの種類に応じて、ライブラリの最新バージョンを依存関係に追加します。
    • Android レシーバー アプリの場合:
        dependencies {
          implementation 'com.google.android.gms:play-services-cast-tv:20.0.0'
          implementation 'com.google.android.gms:play-services-cast:21.2.0'
        }
    • Android 送信者アプリの場合:
        dependencies {
          implementation 'com.google.android.gms:play-services-cast:20.0.0'
          implementation 'com.google.android.gms:play-services-cast-framework:21.2.0'
        }
    サービスは、更新するたびに更新してください。
  4. 変更を保存し、ツールバーの Sync Project with Gradle Files をクリックします。
iOS
  1. Podfilegoogle-cast-sdk 4.7.0 以降をターゲットとしていることを確認します。
  2. iOS 12 以降が対象です。詳細については、リリースノートをご覧ください。
      platform: ios, '12'
    
      def target_pods
         pod 'google-cast-sdk', '~>4.7.0'
      end
ウェブ
  1. Chromium ブラウザ バージョン M87 以降が必要です。
  2. プロジェクトへの 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() 再生状態を設定します。
Kotlin
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)
}
Java
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() 現在のメディアを停止する
Kotlin
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() );
Java
public MyMediaSessionCallback extends MediaSessionCompat.Callback {
  public void onPause() {
    // Pause the player and update the play state.
    ...
  }

  public void onPlay() {
    // Resume the player and update the play state.
    ...
  }

  public void onSeekTo (long pos) {
    // Seek and update the play state.
    ...
  }
  ...
}

mediaSession.setCallback(new MyMediaSessionCallback());

キャスト サポートの構成

送信側のアプリケーションが起動リクエストを送信すると、アプリの名前空間を使用してインテントが作成されます。アプリは、TV アプリの起動時にこれを処理し、CastReceiverContext オブジェクトのインスタンスを作成します。CastReceiverContext オブジェクトは、TV アプリの実行中に Cast とやり取りするために必要です。このオブジェクトにより、テレビ アプリは接続された送信者からのキャスト メディア メッセージを受け入れることができます。

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 を実装する必要があります。

Kotlin
class MyReceiverOptionsProvider : ReceiverOptionsProvider {
  override fun getOptions(context: Context?): CastReceiverOptions {
    return CastReceiverOptions.Builder(context)
          .setStatusText("My App")
          .build()
    }
}
Java
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 を初期化します。

Kotlin
override fun onCreate() {
  CastReceiverContext.initInstance(this)

  ...
}
Java
@Override
public void onCreate() {
  CastReceiverContext.initInstance(this);

  ...
}

アプリがフォアグラウンドに移動したら、CastReceiverContext を開始します。

Kotlin
CastReceiverContext.getInstance().start()
Java
CastReceiverContext.getInstance().start();

動画アプリ、またはバックグラウンド再生をサポートしていないアプリの場合は、アプリがバックグラウンドになった後で CastReceiverContext に対して stop() を呼び出します。

Kotlin
// Player has stopped.
CastReceiverContext.getInstance().stop()
Java
// Player has stopped.
CastReceiverContext.getInstance().stop();

また、アプリがバックグラウンドでの再生をサポートしている場合は、バックグラウンドでの再生が停止したときに CastReceiverContextstop() を呼び出します。

特にネイティブ アプリに複数のアクティビティがある場合は、androidx.lifecycle ライブラリの LifecycleObserver を使用して CastReceiverContext.start()CastReceiverContext.stop() の呼び出しを管理することを強くおすすめします。これにより、異なるアクティビティから start()stop() を呼び出す際の競合状態を回避できます。

Kotlin
// 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())
  }
}
Java
// 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 に指定して、コマンドの送信先とメディア再生状態を取得できるようにする必要があります。

Kotlin
val mediaManager: MediaManager = receiverContext.getMediaManager()
mediaManager.setSessionCompatToken(currentMediaSession.getSessionToken())
Java
MediaManager mediaManager = receiverContext.getMediaManager();
mediaManager.setSessionCompatToken(currentMediaSession.getSessionToken());

非アクティブな再生が原因で MediaSession を解放する場合は、MediaManager に null トークンを設定する必要があります。

Kotlin
myPlayer.stop()
mediaSession.release()
mediaManager.setSessionCompatToken(null)
Java
myPlayer.stop();
mediaSession.release();
mediaManager.setSessionCompatToken(null);

アプリがバックグラウンドで実行されているときにアプリがメディアを再生できる場合は、アプリがバックグラウンドに送信されたときに CastReceiverContext.stop() を呼び出すのではなく、アプリがバックグラウンドにあり、メディアを再生しなくなったときにのみ呼び出す必要があります。例:

Kotlin
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()
  }
}
Java
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 をアプリに統合するには、プレーヤー アクティビティ クラスまたはメディア セッションを管理する場所に以下を追加します。

Kotlin
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)
    ...
  }
}
Java
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 サポートを使用して送信側アプリを更新したら、LaunchOptionsandroidReceiverCompatible フラグを true に設定することで、その準備状況を宣言できます。

Android

play-services-cast-framework バージョン 19.0.0 以降が必要です。

androidReceiverCompatible フラグは CastOptionsLaunchOptions の一部)で設定されます。

Kotlin
class CastOptionsProvider : OptionsProvider {
  override fun getCastOptions(context: Context?): CastOptions {
    val launchOptions: LaunchOptions = Builder()
          .setAndroidReceiverCompatible(true)
          .build()
    return CastOptions.Builder()
          .setLaunchOptions(launchOptions)
          ...
          .build()
    }
}
Java
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();
  }
}
iOS

google-cast-sdk バージョン v4.4.8 以降が必要です。

androidReceiverCompatible フラグは GCKLaunchOptionsGCKCastOptions の一部)で設定されます。

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 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 を設定することで、ディープリンクを渡すことができます。

Kotlin
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)
Android
Java
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);
iOS
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 用に代替の entitycredentials を指定します。

Android
Kotlin
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)
Java
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);
iOS
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 アプリが起動されると、読み込みリクエストで entitycredentials が使用されます。ただし、Android TV アプリが起動された場合、SDK は entitycredentialsatvEntityatvCredentials(指定されている場合)でオーバーライドします。

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() を呼び出します。

Android
Kotlin
val mediaToLoad = MediaInfo.Builder("some-id").build()
val loadRequest = MediaLoadRequestData.Builder()
    .setMediaInfo(mediaToLoad)
    .setCredentials("user-credentials")
    ...
    .build()
remoteMediaClient.load(loadRequest)
Java
MediaInfo mediaToLoad =
    new MediaInfo.Builder("some-id").build();
MediaLoadRequestData loadRequest =
    new MediaLoadRequestData.Builder()
        .setMediaInfo(mediaToLoad)
        .setCredentials("user-credentials")
        ...
        .build();
remoteMediaClient.load(loadRequest);
iOS
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);

読み込みリクエストの処理

アクティビティでこれらの読み込みリクエストを処理するには、アクティビティのライフサイクル コールバックでインテントを処理する必要があります。

Kotlin
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.
    ...
  }
}
Java
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() メソッドで行うことが推奨されます)。

Kotlin
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)
  }
Java
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 Task onLoad(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)を生成します。

メディア コマンドのサポート

基本的な再生コントロールのサポート

基本的な統合コマンドには、メディア セッションと互換性のあるコマンドが含まれています。これらのコマンドは、メディア セッションのコールバックを介して通知されます。これをサポートするには、メディア セッションへのコールバックを登録する必要があります(すでに作成している可能性があります)。

Kotlin
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())
Java
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 キューと完全に互換性がないため、一部のキューコマンドを実装する必要があります。

Kotlin
class MyMediaCommandCallback : MediaCommandCallback() {
    override fun onSkipAd(requestData: RequestData?): Task {
        // Skip your ad
        ...
        return Tasks.forResult(null)
    }
}

val mediaManager = CastReceiverContext.getInstance().getMediaManager()
mediaManager.setMediaCommandCallback(MyMediaCommandCallback())
Java
public class MyMediaCommandCallback extends MediaCommandCallback {
  @Override
  public Task onSkipAd(RequestData requestData) {
    // Skip your ad
    ...
    return Tasks.forResult(null);
  }
}

MediaManager mediaManager =
    CastReceiverContext.getInstance().getMediaManager();
mediaManager.setMediaCommandCallback(new MyMediaCommandCallback());

サポートされているメディア コマンドを指定する

キャスト レシーバーと同様に、Android TV アプリはサポートされているコマンドを指定する必要があります。これにより、送信者は特定の UI コントロールを有効または無効にできます。MediaSession の一部であるコマンドについては、PlaybackStateCompat でコマンドを指定します。追加のコマンドは、MediaStatusModifier で指定します。

Kotlin
// 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)
Java
// 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 アプリにキャストする際に送信側のアプリが正しく動作することを確認する必要があります。たとえば、Web Receiver アプリが Android TV アプリに再生速度の変更をサポートしていない場合は、各プラットフォームでサポートされているアクションを正しく設定し、送信側のアプリで UI が適切にレンダリングされるようにする必要があります。

MediaStatus の変更

トラック、広告、ライブ、キューなどの高度な機能をサポートするには、Android TV アプリが、MediaSession を介して確認できない追加情報を提供する必要があります。

これを実現するための MediaStatusModifier クラスが用意されています。MediaStatusModifier は常に、CastReceiverContext で設定した MediaSession で動作します。

MediaStatus を作成してブロードキャストするには:

Kotlin
val mediaManager: MediaManager = castReceiverContext.getMediaManager()
val statusModifier: MediaStatusModifier = mediaManager.getMediaStatusModifier()

statusModifier
    .setLiveSeekableRange(seekableRange)
    .setAdBreakStatus(adBreakStatus)
    .setCustomData(customData)

mediaManager.broadcastMediaStatus()
Java
MediaManager mediaManager = castReceiverContext.getMediaManager();
MediaStatusModifier statusModifier = mediaManager.getMediaStatusModifier();

statusModifier
    .setLiveSeekableRange(seekableRange)
    .setAdBreakStatus(adBreakStatus)
    .setCustomData(customData);

mediaManager.broadcastMediaStatus();

クライアント ライブラリは MediaSession からベース MediaStatus を取得します。Android TV アプリは、MediaStatus 修飾子を介して追加のステータスを指定し、ステータスをオーバーライドできます。

一部の状態とメタデータは MediaSessionMediaStatusModifier の両方で設定できます。MediaSession のみに設定することを強くおすすめします。この場合でも、修飾子を使用して MediaSession の状態をオーバーライドできますが、修飾子のステータスは常に MediaSession で指定された値よりも優先されるため、推奨されません。

MediaStatus をインターセプトして送信

Web Receiver SDK と同様に、送信前に最後の仕上げを行う場合は、MediaStatusInterceptor を指定して、送信される MediaStatus を処理できます。MediaStatus を送信する前に、MediaStatusWriter を渡して操作します。

Kotlin
mediaManager.setMediaStatusInterceptor(object : MediaStatusInterceptor {
    override fun intercept(mediaStatusWriter: MediaStatusWriter) {
      // Perform customization.
        mediaStatusWriter.setCustomData(JSONObject("{data: \"my Hello\"}"))
    }
})
Java
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 は、CastReceiverContextgetSenders を使用して SenderInfogetCastLaunchRequest()CastLaunchRequestgetCredentialsData() で取得できます。

Android

play-services-cast-framework バージョン 19.0.0 以降が必要です。

Kotlin
CastContext.getSharedInstance().setLaunchCredentialsData(
    CredentialsData.Builder()
        .setCredentials("{\"userId\": \"abc\"}")
        .build()
)
Java
CastContext.getSharedInstance().setLaunchCredentialsData(
    new CredentialsData.Builder()
        .setCredentials("{\"userId\": \"abc\"}")
        .build());
iOS

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 インターフェースを実装するクラスを作成します。

Kotlin
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.
}
Java
public class MyLaunchRequestChecker
    implements CastReceiverOptions.LaunchRequestChecker {
  @Override
  public Task checkLaunchRequestSupported(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 で設定します。

Kotlin
class MyReceiverOptionsProvider : ReceiverOptionsProvider {
  override fun getOptions(context: Context?): CastReceiverOptions {
    return CastReceiverOptions.Builder(context)
        ...
        .setLaunchRequestChecker(MyLaunchRequestChecker())
        .build()
  }
}
Java
public class MyReceiverOptionsProvider implements ReceiverOptionsProvider {
  @Override
  public CastReceiverOptions getOptions(Context context) {
    return new CastReceiverOptions.Builder(context)
        ...
        .setLaunchRequestChecker(new MyLaunchRequestChecker())
        .build();
  }
}

LaunchRequestCheckertrue を解決すると、ATV アプリが起動され、false により Web Receiver アプリが起動します。

送信 & カスタム メッセージの受信

キャスト プロトコルを使用すると、送信者と受信者のアプリケーション間でカスタム文字列メッセージを送信できます。CastReceiverContext を初期化する前に、メッセージを送信する名前空間(チャネル)を登録する必要があります。

Android TV - カスタム名前空間を指定する

設定時に CastReceiverOptions でサポートされている名前空間を指定する必要があります。

Kotlin
class MyReceiverOptionsProvider : ReceiverOptionsProvider {
  override fun getOptions(context: Context?): CastReceiverOptions {
    return CastReceiverOptions.Builder(context)
        .setCustomNamespaces(
            Arrays.asList("urn:x-cast:com.example.cast.mynamespace")
        )
        .build()
  }
}
Java
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 - メッセージの送信

Kotlin
// If senderId is null, then the message is broadcasted to all senders.
CastReceiverContext.getInstance().sendMessage(
    "urn:x-cast:com.example.cast.mynamespace", senderId, customString)
Java
// 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 - カスタムの名前空間メッセージを受信する

Kotlin
class MyCustomMessageListener : MessageReceivedListener {
    override fun onMessageReceived(
        namespace: String, senderId: String?, message: String ) {
        ...
    }
}

CastReceiverContext.getInstance().setMessageReceivedListener(
    "urn:x-cast:com.example.cast.mynamespace", new MyCustomMessageListener());
Java
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());