Добавьте основные функции в свой 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.2.0'
        }
    • Для приложения Sender на Android:
        dependencies {
          implementation 'com.google.android.gms:play-services-cast:21.1.1'
          implementation 'com.google.android.gms:play-services-cast-framework:22.2.0'
        }
    Обязательно обновляйте этот номер версии каждый раз при обновлении сервисов.
  4. Сохраните изменения и нажмите кнопку Sync Project with Gradle Files на панели инструментов.
iOS
  1. Убедитесь, что ваш Podfile ориентирован на google-cast-sdk версии 4.8.4 или выше.
  2. Целевая платформа: iOS 15 или выше. Дополнительные сведения см. в примечаниях к выпуску .
      platform: ios, '15'
    
      def target_pods
         pod 'google-cast-sdk', '~>4.8.4'
      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 Services требуется обновление приложения для использования пространства имен 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() Устанавливает состояние воспроизведения.
Котлин
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() Остановите нынешние СМИ
Котлин
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 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()
    }
}
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 .

Контекст приемника Cast

Инициализируйте CastReceiverContext при создании приложения:

Котлин
override fun onCreate() {
  CastReceiverContext.initInstance(this)

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

  ...
}

Запускайте CastReceiverContext , когда ваше приложение переходит на передний план:

Котлин
CastReceiverContext.getInstance().start()
Java
CastReceiverContext.getInstance().start();

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

Котлин
// Player has stopped.
CastReceiverContext.getInstance().stop()
Java
// Player has stopped.
CastReceiverContext.getInstance().stop();

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

Мы настоятельно рекомендуем использовать LifecycleObserver из библиотеки androidx.lifecycle для управления вызовами 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())
  }
}
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 , чтобы он знал, куда отправлять команды и получать состояние воспроизведения мультимедиа:

Котлин
val mediaManager: MediaManager = receiverContext.getMediaManager()
mediaManager.setSessionCompatToken(currentMediaSession.getSessionToken())
Java
MediaManager mediaManager = receiverContext.getMediaManager();
mediaManager.setSessionCompatToken(currentMediaSession.getSessionToken());

При освобождении MediaSession из-за неактивного воспроизведения следует установить нулевой токен в MediaManager :

Котлин
myPlayer.stop()
mediaSession.release()
mediaManager.setSessionCompatToken(null)
Java
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()
  }
}
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 для автоматического поддержания сессии и всей связанной информации, включая состояние воспроизведения, вместо того, чтобы отслеживать изменения вручную.

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

Котлин
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 , чтобы связать его с идентификатором вашего приложения Cast.

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

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

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

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

Загрузка медиафайлов

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

Команда загрузки отправляется через Intent, содержащий вашу глубокую ссылку и имя пакета, определенное вами в консоли разработчика.

Настройка учетных данных ATV на отправителе

Возможно, ваше приложение Web Receiver и приложение для Android TV поддерживают разные прямые ссылки и credentials (например, если вы обрабатываете аутентификацию по-разному на этих двух платформах). Для решения этой проблемы вы можете предоставить альтернативные entity и credentials для Android TV:

Android
Котлин
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, оно использует entity и credentials в запросе на загрузку. Однако, если запущено ваше приложение для Android TV, SDK переопределяет entity и credentials вашими объектами atvEntity и atvCredentials (если они указаны).

Загрузка по идентификатору контента или данным очереди мультимедиа.

Если вы не используете entity или atvEntity , а используете Content ID или Content URL в информации о медиафайлах или более подробные данные запроса на загрузку медиафайлов, вам необходимо добавить следующий предопределенный фильтр намерений в ваше приложение для Android TV:

<activity android:name="com.example.activity">
  <intent-filter>
     <action android:name="com.google.android.gms.cast.tv.action.LOAD"/>
     <category android:name="android.intent.category.DEFAULT" />
  </intent-filter>
</activity>

На стороне отправителя, аналогично загрузке по сущности , вы можете создать запрос на загрузку с информацией о вашем контенте и вызвать load() .

Android
Котлин
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:

Котлин
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).

Котлин
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 для запросов на загрузку).

Поддержка команд мультимедиа

Базовая поддержка управления воспроизведением

Основные команды интеграции включают команды, совместимые с медиасессией. Эти команды передаются через обратные вызовы медиасессии. Для поддержки этой функции необходимо зарегистрировать обратный вызов в медиасессии (возможно, вы уже это делаете).

Котлин
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

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

Котлин
class MyMediaCommandCallback : MediaCommandCallback() {
    override fun onSkipAd(requestData: RequestData?): Task<Void?> {
        // Skip your ad
        ...
        return Tasks.forResult(null)
    }
}

val mediaManager = CastReceiverContext.getInstance().getMediaManager()
mediaManager.setMediaCommandCallback(MyMediaCommandCallback())
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 .

Котлин
// 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 поддерживает только базовое управление воспроизведением, а приложение Web Receiver — более расширенные возможности, убедитесь, что ваше приложение-отправитель корректно работает при трансляции на приложение Android TV. Например, если ваше приложение для Android TV не поддерживает изменение скорости воспроизведения, а приложение Web Receiver — поддерживает, необходимо правильно настроить поддерживаемые действия на каждой платформе и убедиться, что приложение-отправитель корректно отображает пользовательский интерфейс.

Изменение статуса медиафайла

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

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

Для создания и трансляции MediaStatus :

Котлин
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 перед отправкой

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

Котлин
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 отправитель может указать средство проверки запуска, чтобы убедиться, что учетные данные отправителя разрешены. Если нет, то SDK Cast Connect будет использовать ваш веб-приемник.

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

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

credentials представляют собой строку, которую может определить пользователь, если ваше приложение для Apple 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 или выше.

Котлин
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.4 или выше.

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

Внедрение средства проверки запросов на запуск квадроциклов.

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

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

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

Котлин
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();
  }
}

Если в параметре LaunchRequestChecker установить значение true , запустится приложение ATV, а если false — ваше приложение Web Receiver.

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

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

Котлин
// 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 — получение сообщений от пользовательских пространств имен

Котлин
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());