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

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

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

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

Android
  1. Откройте файл build.gradle в каталоге модулей приложения.
  2. Убедитесь, что google() включен в список repositories.
      repositories {
        google()
      }
  3. В зависимости от типа целевого устройства для вашего приложения добавьте в зависимости последние версии библиотек:
    • Для приложения-приемника Android:
        dependencies {
          implementation 'com.google.android.gms:play-services-cast-tv:21.1.1'
          implementation 'com.google.android.gms:play-services-cast:23.0.0'
        }
    • Для приложения-отправителя Android:
        dependencies {
          implementation 'com.google.android.gms:play-services-cast:21.1.1'
          implementation 'com.google.android.gms:play-services-cast-framework:23.0.0'
        }
    Не забывайте обновлять номер версии при каждом изменении сервисов.
  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>

Укажите поставщика параметров получателя

Вам нужно реализовать ReceiverOptionsProvider, чтобы предоставить CastReceiverOptions:

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(), особенно если в вашем нативном приложении есть несколько действий. Это позволяет избежать состояния гонки при вызове 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 в приложение, добавьте следующий код в класс PlayerActivity или в другое место, где вы управляете медиасеансом:

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

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

const context = cast.framework.CastContext.getInstance();
const castOptions = new cast.framework.CastOptions();
castOptions.receiverApplicationId = kReceiverAppID;
castOptions.androidReceiverCompatible = true;
context.setOptions(castOptions);

Настройка Cast Developer Console

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

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

Регистрация устройств разработчиков

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

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

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

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

Если вы уже реализовали поддержку ссылок на контент в приложении для 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 или Content URL в Media Information или более подробные данные Media Load Request Data, вам нужно добавить в приложение для 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)
Web

Требуется браузер 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:

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

  • В приложении-отправителе выполнен вход в тот же аккаунт и профиль, что и в приложении Android TV.
  • В приложении-отправителе выполнен вход в тот же аккаунт, но в другой профиль, нежели в приложении ATV.

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

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

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

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

На стороне отправителя вы можете указать 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\"}")
Web

Требуется браузер 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 не выполнен вход ни в один аккаунт.

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

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

Протокол 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());