Как добавить основные функции в ресивер Android TV

На этой странице приведены фрагменты кода и описания функций, доступных для настройки приложения Android TV Receiver.

Настройка библиотек

Чтобы API Cast Connect стали доступны в приложении для Android TV:

Android
  1. Откройте файл build.gradle в каталоге модулей приложения.
  2. Убедитесь, что google() включен в список repositories.
      repositories {
        google()
      }
  3. В зависимости от типа целевого устройства добавьте в зависимости последние версии библиотек:
    • Для приложения Android Receiver:
        dependencies {
          implementation 'com.google.android.gms:play-services-cast-tv:21.1.1'
          implementation 'com.google.android.gms:play-services-cast:22.3.1'
        }
    • Для приложения-отправителя Android:
        dependencies {
          implementation 'com.google.android.gms:play-services-cast:21.1.1'
          implementation 'com.google.android.gms:play-services-cast-framework:22.3.1'
        }
    Обязательно обновляйте номер версии при каждом обновлении сервисов.
  4. Сохраните изменения и нажмите Sync Project with Gradle Files на панели инструментов.
iOS
  1. Убедитесь, что в Podfile используется google-cast-sdk 4.8.6 или более поздней версии.
  2. Ориентируйтесь на iOS 16 или более поздней версии. Подробную информацию можно найти в примечаниях к выпуску.
      platform: ios, '16'
    
      def target_pods
         pod 'google-cast-sdk', '~>4.8.6'
      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

Чтобы поддерживать Cast Connect в приложении для Android TV, необходимо создавать и поддерживать события из медиасеанса. Данные, предоставляемые вашим сеансом мультимедиа, содержат основную информацию, например позицию, состояние воспроизведения и т. д., для вашего статуса мультимедиа. Медиасеанс также используется библиотекой Cast Connect, чтобы сообщать, когда она получила от отправителя определенные сообщения, например о приостановке.

Подробнее о сеансе мультимедиа и о том, как его инициализировать, рассказывается в руководстве по работе с сеансом мультимедиа.

Жизненный цикл сеанса воспроизведения медиаконтента

Приложение должно создавать сеанс воспроизведения, когда оно начинается, и завершать его, когда управление становится невозможным. Например, если ваше приложение предназначено для просмотра видео, сеанс нужно завершать, когда пользователь выходит из режима воспроизведения, например нажимая кнопку "Назад" или переключаясь на другое приложение. Если ваше приложение предназначено для прослушивания музыки, сеанс нужно завершать, когда приложение перестает воспроизводить медиаконтент.

Обновление статуса сеанса

Данные в сеансе мультимедиа должны быть актуальными и соответствовать статусу проигрывателя. Например, когда воспроизведение приостановлено, необходимо обновить состояние воспроизведения, а также поддерживаемые действия. В таблицах ниже перечислены статусы, которые вы должны поддерживать в актуальном состоянии.

MediaMetadataCompat

Поле метаданных Описание
METADATA_KEY_TITLE (обязательный) Название медиафайла.
METADATA_KEY_DISPLAY_SUBTITLE Подзаголовок.
METADATA_KEY_DISPLAY_ICON_URI URL значка.
METADATA_KEY_DURATION (обязательный) Продолжительность медиаконтента.
METADATA_KEY_MEDIA_URI Идентификатор контента.
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

Действия Описание
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());

Как настроить поддержку Cast

Когда приложение-отправитель отправляет запрос на запуск, создается намерение с пространством имен приложения. Ваше приложение должно обрабатывать его и создавать экземпляр объекта CastReceiverContext при запуске ТВ-приложения. Объект CastReceiverContext необходим для взаимодействия с Cast во время работы ТВ-приложения. Этот объект позволяет приложению для телевизора принимать медиасообщения Cast от любых подключенных отправителей.

Настройка Android TV

Добавление фильтра интентов запуска

Добавьте новый фильтр интентов в Activity, который должен обрабатывать интент запуска из приложения-отправителя:

<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 используется для передачи значения CastReceiverOptions при инициализации CastReceiverContext.

Контекст устройства-получателя трансляции

Инициализируйте 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();

Вызов stop() на CastReceiverContext после того, как приложение переходит в фоновый режим для видеоприложений или приложений, которые не поддерживают фоновое воспроизведение:

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

Если приложение поддерживает воспроизведение в фоновом режиме, вызовите stop() для CastReceiverContext, когда воспроизведение в фоновом режиме прекратится.

Мы настоятельно рекомендуем использовать LifecycleObserver из библиотеки androidx.lifecycle для управления вызовами CastReceiverContext.start() и CastReceiverContext.stop(), особенно если в вашем нативном приложении несколько Activity. Это позволяет избежать конфликтов при вызове 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:

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();
  }
}

Как использовать Exoplayer с Cast Connect

Если вы используете Exoplayer, то можете автоматически поддерживать сеанс и всю связанную с ним информацию, включая состояние воспроизведения, с помощью MediaSessionConnector, а не отслеживать изменения вручную.

MediaSessionConnector.MediaButtonEventHandler можно использовать для обработки событий MediaButton, вызывая setMediaButtonEventHandler(MediaButtonEventHandler), которые по умолчанию обрабатываются 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, вы можете заявить о его готовности, установив флаг androidReceiverCompatible в LaunchOptions в значение true.

Android

Требуется play-services-cast-framework версии 19.0.0 или более поздней.

Флаг androidReceiverCompatible задается в LaunchOptions (который является частью CastOptions):

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 задается в 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

Как настроить приложение для Android TV

Добавьте название пакета приложения для Android TV в Cast Developer Console, чтобы связать его с идентификатором приложения Cast.

Как зарегистрировать устройства разработчика

Зарегистрируйте серийный номер устройства Android TV, которое вы будете использовать для разработки, в Cast Developer Console.

Без регистрации Cast Connect будет работать только с приложениями, установленными из Google Play, в целях безопасности.

Дополнительную информацию о регистрации устройств Cast или устройств Android TV для разработки приложений Cast можно найти на странице регистрации.

Загрузка медиаконтента

Если вы уже добавили в приложение для 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)
Web

Требуется браузер 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 (например, если вы по-разному обрабатываете аутентификацию на этих платформах). Чтобы решить эту проблему, вы можете предоставить альтернативные entity и credentials для Android TV:

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)
Web

Требуется браузер Chromium версии M87 или более поздней.

let mediaInfo = new chrome.cast.media.MediaInfo('some-id"', 'video/mp4');
mediaInfo.entity = 'https://example.com/watch/some-id';
mediaInfo.atvEntity = 'myscheme://example.com/atv/some-id';
...

let request = new chrome.cast.media.LoadRequest(mediaInfo);
request.credentials = 'user-credentials';
request.atvCredentials = 'atv-user-credentials';
...

cast.framework.CastContext.getInstance().getCurrentSession().loadMedia(request);

Если приложение Web Receiver запущено, оно использует entity и credentials в запросе на загрузку. Однако если приложение для Android TV запущено, SDK переопределяет значения entity и credentials, используя atvEntity и atvCredentials (если они указаны).

Загрузка с помощью Content ID или MediaQueueData

Если вы не используете entity или atvEntity, а применяете Content ID или 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);

Обработка запросов на загрузку

Чтобы обрабатывать запросы на загрузку в объекте activity, вам нужно обрабатывать намерения в обратных вызовах жизненного цикла объекта activity:

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() класса Activity или Application).

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());

Поддержка команд управления трансляцией

Некоторые команды Cast недоступны в MediaSession, например skipAd() или setActiveMediaTracks(). Кроме того, здесь нужно реализовать некоторые команды очереди, поскольку очередь Cast не полностью совместима с очередью MediaSession.

Kotlin
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())
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());

Как указать поддерживаемые команды для управления медиаконтентом

Как и в случае с приемником Cast, в приложении для Android TV необходимо указать, какие команды поддерживаются, чтобы отправители могли включать или отключать определенные элементы управления пользовательского интерфейса. Для команд, входящих в 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. Например, если ваше приложение для Android TV не поддерживает изменение скорости воспроизведения, а веб-приемник поддерживает, вам следует правильно настроить поддерживаемые действия на каждой платформе и убедиться, что приложение-отправитель правильно отображает пользовательский интерфейс.

Изменение MediaStatus

Чтобы поддерживать такие продвинутые функции, как дорожки, реклама, трансляции и очереди, приложению для Android TV требуется дополнительная информация, которую нельзя получить с помощью MediaSession.

Для этого предназначен класс MediaStatusModifier. MediaStatusModifier всегда будет работать с MediaSession, которую вы задали в CastReceiverContext.

Чтобы создать и транслировать 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();

Наша клиентская библиотека получит базовый MediaStatus из MediaSession, а ваше приложение для Android TV сможет указать дополнительный статус и переопределить статус с помощью модификатора MediaStatus.

Некоторые состояния и метаданные можно задать как в MediaSession, так и в MediaStatusModifier. Мы настоятельно рекомендуем задавать их только в файле MediaSession. Вы по-прежнему можете использовать модификатор, чтобы переопределять статусы в MediaSession, но делать это не рекомендуется, поскольку статус в модификаторе всегда имеет более высокий приоритет, чем значения, предоставленные MediaSession.

Перехват MediaStatus перед отправкой

Как и в случае с Web Receiver SDK, если вы хотите внести последние изменения перед отправкой, вы можете указать MediaStatusInterceptor для обработки MediaStatus, который будет отправлен. Мы передаем в MediaStatusWriter для обработки MediaStatus перед отправкой.

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. Если пользователь предоставит учетные данные, ваше приложение для Android TV должно обработать их, чтобы можно было отслеживать прогресс и другие данные пользователя.

При запуске приложения-отправителя или его подключении к приложению для Android TV приложение-отправитель должно предоставить учетные данные, которые представляют пользователя, присоединяющегося к сеансу.

Перед тем как отправитель запустит ваше приложение для Android TV и подключится к нему, вы можете указать проверку запуска, чтобы узнать, разрешены ли учетные данные отправителя. В противном случае SDK Cast Connect запустит веб-приемник.

Данные учетных данных для запуска приложения-отправителя

На стороне отправителя можно указать CredentialsData, чтобы обозначить, кто присоединяется к сеансу.

credentials – это строка, которую может задать пользователь, если она понятна приложению Android TV. credentialsType определяет, с какой платформы поступает CredentialsData, или может быть пользовательским значением. По умолчанию это платформа, с которой отправляется запрос.

Параметр CredentialsData передается приложению для Android TV только при запуске или подключении. Если вы зададите его снова, когда устройство подключено, он не будет передан в приложение для Android TV. Если отправитель сменит профиль во время подключения, вы можете остаться в сеансе или вызвать SessionManager.endCurrentCastSession(boolean stopCasting), если считаете, что новый профиль несовместим с сеансом.

CredentialsData для каждого отправителя можно получить с помощью getSenders на CastReceiverContext для получения SenderInfo, getCastLaunchRequest() для получения CastLaunchRequest, а затем getCredentialsData().

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.8.6 или более поздней.

Можно вызвать в любое время после установки параметров: 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

Когда отправитель пытается запустить приложение или присоединиться к нему, в приложение для Android TV передается CredentialsData. Вы можете реализовать LaunchRequestChecker. чтобы одобрить или отклонить запрос.

Если запрос отклонен, вместо запуска приложения ATV загружается веб-приемник. Запрос следует отклонять, если ATV не может обработать запрос пользователя на запуск или присоединение. Например, в приложении на устройстве Android TV вошел не тот пользователь, который отправил запрос, и ваше приложение не может переключить учетные данные, или в приложении на устройстве Android TV не выполнен вход ни одного пользователя.

Если запрос разрешен, приложение 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();
  }
}

При нажатии на кнопку true в LaunchRequestChecker запускается приложение ATV, а при нажатии на кнопку false – приложение Web Receiver.

Отправка и получение специальных сообщений

Протокол Cast позволяет отправлять пользовательские строковые сообщения между отправителями и приложением-получателем. Чтобы отправлять сообщения через 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());