Добавьте основные функции в свой Android TV-ресивер

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

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

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

Андроид
  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.1.0'
        }
    • Для приложения Android Sender:
        dependencies {
          implementation 'com.google.android.gms:play-services-cast:21.1.1'
          implementation 'com.google.android.gms:play-services-cast-framework:22.1.0'
        }
    Обязательно обновляйте этот номер версии каждый раз при обновлении сервисов.
  4. Сохраните изменения и нажмите Sync Project with Gradle Files на панели инструментов.
iOS
  1. Убедитесь, что ваш Podfile ориентирован на google-cast-sdk 4.8.3 или выше.
  2. Целевая версия — iOS 14 или более поздняя. Подробнее см. в примечаниях к выпуску .
      platform: ios, '14'
    
      def target_pods
         pod 'google-cast-sdk', '~>4.8.3'
      end
Интернет
  1. Требуется браузер Chromium версии M87 или выше.
  2. Добавьте библиотеку API Web Sender в свой проект
      <script src="//www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1"></script>

Требование AndroidX

Для использования пространства имён androidx в новых версиях сервисов Google Play требуется обновить приложение. Следуйте инструкциям по переходу на 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)
}
Ява
private void updateMediaSession() {
  MediaMetadataCompat metadata =
      new MediaMetadataCompat.Builder()
          .putString(MediaMetadataCompat.METADATA_KEY_TITLE, "title")
          .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "subtitle")
          .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI,mMovie.getCardImageUrl())
          .build();

  PlaybackStateCompat playbackState =
      new PlaybackStateCompat.Builder()
          .setState(
               PlaybackStateCompat.STATE_PLAYING,
               player.getPosition(),
               player.getPlaybackSpeed(),
               System.currentTimeMillis())
          .build();

  mediaSession.setMetadata(metadata);
  mediaSession.setPlaybackState(playbackState);
}

Осуществление контроля за транспортом

Ваше приложение должно реализовывать обратный вызов управления транспортировкой медиа-сессии. В следующей таблице показаны действия управления транспортировкой, которые оно должно обрабатывать:

MediaSessionCompat.Callback

Действия Описание
onPlay() Резюме
onPause() Пауза
onSeekTo() Стремиться к позиции
onStop() Остановить текущее медиа
Котлин
class MyMediaSessionCallback : MediaSessionCompat.Callback() {
  override fun onPause() {
    // Pause the player and update the play state.
    ...
  }

  override fun onPlay() {
    // Resume the player and update the play state.
    ...
  }

  override fun onSeekTo (long pos) {
    // Seek and update the play state.
    ...
  }
  ...
}

mediaSession.setCallback( MyMediaSessionCallback() );
Ява
public MyMediaSessionCallback extends MediaSessionCompat.Callback {
  public void onPause() {
    // Pause the player and update the play state.
    ...
  }

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

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

mediaSession.setCallback(new MyMediaSessionCallback());

Настройка поддержки 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>

Укажите поставщика опций приемника

Вам необходимо реализовать ReceiverOptionsProvider для предоставления CastReceiverOptions :

Котлин
class MyReceiverOptionsProvider : ReceiverOptionsProvider {
  override fun getOptions(context: Context?): CastReceiverOptions {
    return CastReceiverOptions.Builder(context)
          .setStatusText("My App")
          .build()
    }
}
Ява
public class MyReceiverOptionsProvider implements ReceiverOptionsProvider {
  @Override
  public CastReceiverOptions getOptions(Context context) {
    return new CastReceiverOptions.Builder(context)
        .setStatusText("My App")
        .build();
  }
}

Затем укажите поставщика опций в вашем AndroidManifest :

 <meta-data
    android:name="com.google.android.gms.cast.tv.RECEIVER_OPTIONS_PROVIDER_CLASS_NAME"
    android:value="com.example.mysimpleatvapplication.MyReceiverOptionsProvider" />

ReceiverOptionsProvider используется для предоставления CastReceiverOptions при инициализации CastReceiverContext .

Контекст приемника трансляции

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

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

  ...
}
Ява
@Override
public void onCreate() {
  CastReceiverContext.initInstance(this);

  ...
}

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

Котлин
CastReceiverContext.getInstance().start()
Ява
CastReceiverContext.getInstance().start();

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

Котлин
// Player has stopped.
CastReceiverContext.getInstance().stop()
Ява
// 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())
  }
}
Ява
// Create a LifecycleObserver class.
public class MyLifecycleObserver implements DefaultLifecycleObserver {
  @Override
  public void onStart(LifecycleOwner owner) {
    // App prepares to enter foreground.
    CastReceiverContext.getInstance().start();
  }

  @Override
  public void onStop(LifecycleOwner owner) {
    // App has moved to the background or has terminated.
    CastReceiverContext.getInstance().stop();
  }
}

// Add the observer when your application is being created.
public class MyApplication extends Application {
  @Override
  public void onCreate() {
    super.onCreate();

    // Initialize CastReceiverContext.
    CastReceiverContext.initInstance(this /* android.content.Context */);

    // Register LifecycleObserver
    ProcessLifecycleOwner.get().getLifecycle().addObserver(
        new MyLifecycleObserver());
  }
}
// In AndroidManifest.xml set MyApplication as the application class
<application
    ...
    android:name=".MyApplication">

Подключение MediaSession к MediaManager

При создании MediaSession вам также необходимо предоставить текущий токен MediaSession для CastReceiverContext , чтобы он знал, куда отправлять команды и получать состояние воспроизведения мультимедиа:

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

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

Котлин
myPlayer.stop()
mediaSession.release()
mediaManager.setSessionCompatToken(null)
Ява
myPlayer.stop();
mediaSession.release();
mediaManager.setSessionCompatToken(null);

Если ваше приложение поддерживает воспроизведение медиаконтента в фоновом режиме, вместо вызова CastReceiverContext.stop() при переходе приложения в фоновый режим следует вызывать его только тогда, когда приложение находится в фоновом режиме и больше не воспроизводит медиаконтент. Например:

Котлин
class MyLifecycleObserver : DefaultLifecycleObserver {
  ...
  // App has moved to the background.
  override fun onPause(owner: LifecycleOwner) {
    mIsBackground = true
    myStopCastReceiverContextIfNeeded()
  }
}

// Stop playback on the player.
private fun myStopPlayback() {
  myPlayer.stop()

  myStopCastReceiverContextIfNeeded()
}

// Stop the CastReceiverContext when both the player has
// stopped and the app has moved to the background.
private fun myStopCastReceiverContextIfNeeded() {
  if (mIsBackground && myPlayer.isStopped()) {
    CastReceiverContext.getInstance().stop()
  }
}
Ява
public class MyLifecycleObserver implements DefaultLifecycleObserver {
  ...
  // App has moved to the background.
  @Override
  public void onPause(LifecycleOwner owner) {
    mIsBackground = true;

    myStopCastReceiverContextIfNeeded();
  }
}

// Stop playback on the player.
private void myStopPlayback() {
  myPlayer.stop();

  myStopCastReceiverContextIfNeeded();
}

// Stop the CastReceiverContext when both the player has
// stopped and the app has moved to the background.
private void myStopCastReceiverContextIfNeeded() {
  if (mIsBackground && myPlayer.isStopped()) {
    CastReceiverContext.getInstance().stop();
  }
}

Использование Exoplayer с Cast Connect

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

MediaSessionConnector.MediaButtonEventHandler можно использовать для обработки событий MediaButton путем вызова 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)
    ...
  }
}
Ява
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.

Андроид

Требуется 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()
    }
}
Ява
public class CastOptionsProvider implements OptionsProvider {
  @Override
  public CastOptions getCastOptions(Context context) {
    LaunchOptions launchOptions = new LaunchOptions.Builder()
              .setAndroidReceiverCompatible(true)
              .build();
    return new CastOptions.Builder()
        .setLaunchOptions(launchOptions)
        ...
        .build();
  }
}
iOS

Требуется google-cast-sdk версии v4.4.8 или выше.

Флаг androidReceiverCompatible устанавливается в GCKLaunchOptions (который является частью GCKCastOptions ):

let options = GCKCastOptions(discoveryCriteria: GCKDiscoveryCriteria(applicationID: kReceiverAppID))
...
let launchOptions = GCKLaunchOptions()
launchOptions.androidReceiverCompatible = true
options.launchOptions = launchOptions
GCKCastContext.setSharedInstanceWith(options)
Интернет

Требуется браузер Chromium версии M87 или выше.

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

Настройка консоли разработчика Cast

Настройте приложение Android TV

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

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

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

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

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

Загрузка медиа

Если вы уже реализовали поддержку глубоких ссылок в своем приложении Android TV, то вам следует настроить аналогичное определение в манифесте Android TV:

<activity android:name="com.example.activity">
  <intent-filter>
     <action android:name="android.intent.action.VIEW" />
     <category android:name="android.intent.category.DEFAULT" />
     <data android:scheme="https"/>
     <data android:host="www.example.com"/>
     <data android:pathPattern=".*"/>
  </intent-filter>
</activity>

Загрузка по субъекту отправителя

На стороне отправителя вы можете передать глубокую ссылку, установив entity в информации о медиа для запроса на загрузку:

Котлин
val mediaToLoad = MediaInfo.Builder("some-id")
    .setEntity("https://example.com/watch/some-id")
    ...
    .build()
val loadRequest = MediaLoadRequestData.Builder()
    .setMediaInfo(mediaToLoad)
    .setCredentials("user-credentials")
    ...
    .build()
remoteMediaClient.load(loadRequest)
Андроид
Ява
MediaInfo mediaToLoad =
    new MediaInfo.Builder("some-id")
        .setEntity("https://example.com/watch/some-id")
        ...
        .build();
MediaLoadRequestData loadRequest =
    new MediaLoadRequestData.Builder()
        .setMediaInfo(mediaToLoad)
        .setCredentials("user-credentials")
        ...
        .build();
remoteMediaClient.load(loadRequest);
iOS
let mediaInfoBuilder = GCKMediaInformationBuilder(entity: "https://example.com/watch/some-id")
...
mediaInformation = mediaInfoBuilder.build()

let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder()
mediaLoadRequestDataBuilder.mediaInformation = mediaInformation
mediaLoadRequestDataBuilder.credentials = "user-credentials"
...
let mediaLoadRequestData = mediaLoadRequestDataBuilder.build()

remoteMediaClient?.loadMedia(with: mediaLoadRequestData)
Интернет

Требуется браузер Chromium версии M87 или выше.

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

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

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

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

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

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

Андроид
Котлин
val mediaToLoad = MediaInfo.Builder("some-id")
        .setEntity("https://example.com/watch/some-id")
        .setAtvEntity("myscheme://example.com/atv/some-id")
        ...
        .build()
val loadRequest = MediaLoadRequestData.Builder()
        .setMediaInfo(mediaToLoad)
        .setCredentials("user-credentials")
        .setAtvCredentials("atv-user-credentials")
        ...
        .build()
remoteMediaClient.load(loadRequest)
Ява
MediaInfo mediaToLoad =
    new MediaInfo.Builder("some-id")
        .setEntity("https://example.com/watch/some-id")
        .setAtvEntity("myscheme://example.com/atv/some-id")
        ...
        .build();
MediaLoadRequestData loadRequest =
    new MediaLoadRequestData.Builder()
        .setMediaInfo(mediaToLoad)
        .setCredentials("user-credentials")
        .setAtvCredentials("atv-user-credentials")
        ...
        .build();
remoteMediaClient.load(loadRequest);
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 (если указаны).

Загрузка по Content ID или MediaQueueData

Если вы не используете 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() .

Андроид
Котлин
val mediaToLoad = MediaInfo.Builder("some-id").build()
val loadRequest = MediaLoadRequestData.Builder()
    .setMediaInfo(mediaToLoad)
    .setCredentials("user-credentials")
    ...
    .build()
remoteMediaClient.load(loadRequest)
Ява
MediaInfo mediaToLoad =
    new MediaInfo.Builder("some-id").build();
MediaLoadRequestData loadRequest =
    new MediaLoadRequestData.Builder()
        .setMediaInfo(mediaToLoad)
        .setCredentials("user-credentials")
        ...
        .build();
remoteMediaClient.load(loadRequest);
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);

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

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

Котлин
class MyActivity : Activity() {
  override fun onStart() {
    super.onStart()
    val mediaManager = CastReceiverContext.getInstance().getMediaManager()
    // Pass the intent to the SDK. You can also do this in onCreate().
    if (mediaManager.onNewIntent(intent)) {
        // If the SDK recognizes the intent, you should early return.
        return
    }
    // If the SDK doesn't recognize the intent, you can handle the intent with
    // your own logic.
    ...
  }

  // For some cases, a new load intent triggers onNewIntent() instead of
  // onStart().
  override fun onNewIntent(intent: Intent) {
    val mediaManager = CastReceiverContext.getInstance().getMediaManager()
    // Pass the intent to the SDK. You can also do this in onCreate().
    if (mediaManager.onNewIntent(intent)) {
        // If the SDK recognizes the intent, you should early return.
        return
    }
    // If the SDK doesn't recognize the intent, you can handle the intent with
    // your own logic.
    ...
  }
}
Ява
public class MyActivity extends Activity {
  @Override
  protected void onStart() {
    super.onStart();
    MediaManager mediaManager =
        CastReceiverContext.getInstance().getMediaManager();
    // Pass the intent to the SDK. You can also do this in onCreate().
    if (mediaManager.onNewIntent(getIntent())) {
      // If the SDK recognizes the intent, you should early return.
      return;
    }
    // If the SDK doesn't recognize the intent, you can handle the intent with
    // your own logic.
    ...
  }

  // For some cases, a new load intent triggers onNewIntent() instead of
  // onStart().
  @Override
  protected void onNewIntent(Intent intent) {
    MediaManager mediaManager =
        CastReceiverContext.getInstance().getMediaManager();
    // Pass the intent to the SDK. You can also do this in onCreate().
    if (mediaManager.onNewIntent(intent)) {
      // If the SDK recognizes the intent, you should early return.
      return;
    }
    // If the SDK doesn't recognize the intent, you can handle the intent with
    // your own logic.
    ...
  }
}

Если MediaManager обнаруживает, что намерение — загрузка, он извлекает из намерения объект MediaLoadRequestData и вызывает MediaLoadCommandCallback.onLoad() . Для обработки запроса на загрузку необходимо переопределить этот метод. Обратный вызов должен быть зарегистрирован до вызова метода MediaManager.onNewIntent() (рекомендуется зарегистрировать его в методе onCreate() 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)
  }
Ява
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())
Ява
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())
Ява
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)
Ява
// 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 поддерживает, необходимо правильно настроить поддерживаемые действия на каждой платформе и убедиться, что приложение-отправитель корректно отображает пользовательский интерфейс.

Изменение MediaStatus

Для поддержки расширенных функций, таких как треки, реклама, прямая трансляция и организация очередей, вашему приложению 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()
Ява
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 перед отправкой.

Котлин
mediaManager.setMediaStatusInterceptor(object : MediaStatusInterceptor {
    override fun intercept(mediaStatusWriter: MediaStatusWriter) {
      // Perform customization.
        mediaStatusWriter.setCustomData(JSONObject("{data: \"my Hello\"}"))
    }
})
Ява
mediaManager.setMediaStatusInterceptor(new MediaStatusInterceptor() {
    @Override
    public void intercept(MediaStatusWriter mediaStatusWriter) {
        // Perform customization.
        mediaStatusWriter.setCustomData(new JSONObject("{data: \"my Hello\"}"));
    }
});

Обработка учетных данных пользователей

Ваше приложение Android TV может разрешать запуск сеанса или присоединение к нему только определённым пользователям. Например, отправителю можно разрешить запуск сеанса или присоединение только в следующих случаях:

  • Приложение-отправитель авторизуется в той же учетной записи и профиле, что и приложение ATV.
  • Приложение-отправитель авторизовано в той же учетной записи, что и приложение ATV, но с другим профилем.

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

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

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

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

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

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

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

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

Андроид

Требуется play-services-cast-framework версии 19.0.0 или выше.

Котлин
CastContext.getSharedInstance().setLaunchCredentialsData(
    CredentialsData.Builder()
        .setCredentials("{\"userId\": \"abc\"}")
        .build()
)
Ява
CastContext.getSharedInstance().setLaunchCredentialsData(
    new CredentialsData.Builder()
        .setCredentials("{\"userId\": \"abc\"}")
        .build());
iOS

Требуется google-cast-sdk версии v4.8.3 или выше.

Может быть вызван в любое время после установки параметров: GCKCastContext.setSharedInstanceWith(options) .

GCKCastContext.sharedInstance().setLaunch(
    GCKCredentialsData(credentials: "{\"userId\": \"abc\"}")
Интернет

Требуется браузер Chromium версии M87 или выше.

Может быть вызван в любое время после установки параметров: cast.framework.CastContext.getInstance().setOptions(options); .

let credentialsData =
    new chrome.cast.CredentialsData("{\"userId\": \"abc\"}");
cast.framework.CastContext.getInstance().setLaunchCredentialsData(credentialsData);

Реализация проверки запроса на запуск ATV

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

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

Если запрос разрешён, приложение ATV запускается. Вы можете настроить это поведение в зависимости от того, поддерживает ли ваше приложение отправку запросов на загрузку, когда пользователь не вошёл в приложение ATV, или в случае несоответствия пользователя. Это поведение полностью настраивается в LaunchRequestChecker .

Создайте класс, реализующий интерфейс CastReceiverOptions.LaunchRequestChecker :

Котлин
class MyLaunchRequestChecker : LaunchRequestChecker {
  override fun checkLaunchRequestSupported(launchRequest: CastLaunchRequest): Task {
    return Tasks.call {
      myCheckLaunchRequest(
           launchRequest
      )
    }
  }
}

private fun myCheckLaunchRequest(launchRequest: CastLaunchRequest): Boolean {
  val credentialsData = launchRequest.getCredentialsData()
     ?: return false // or true if you allow anonymous users to join.

  // The request comes from a mobile device, e.g. checking user match.
  return if (credentialsData.credentialsType == CredentialsData.CREDENTIALS_TYPE_ANDROID) {
     myCheckMobileCredentialsAllowed(credentialsData.getCredentials())
  } else false // Unrecognized credentials type.
}
Ява
public class MyLaunchRequestChecker
    implements CastReceiverOptions.LaunchRequestChecker {
  @Override
  public 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()
  }
}
Ява
public class MyReceiverOptionsProvider implements ReceiverOptionsProvider {
  @Override
  public CastReceiverOptions getOptions(Context context) {
    return new CastReceiverOptions.Builder(context)
        ...
        .setLaunchRequestChecker(new MyLaunchRequestChecker())
        .build();
  }
}

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

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

Протокол Cast позволяет отправлять пользовательские строковые сообщения между отправителями и приложением-получателем. Для отправки сообщений необходимо зарегистрировать пространство имён (канал) до инициализации CastReceiverContext .

Android TV — укажите пользовательское пространство имен

Во время настройки вам необходимо указать поддерживаемые пространства имен в CastReceiverOptions :

Котлин
class MyReceiverOptionsProvider : ReceiverOptionsProvider {
  override fun getOptions(context: Context?): CastReceiverOptions {
    return CastReceiverOptions.Builder(context)
        .setCustomNamespaces(
            Arrays.asList("urn:x-cast:com.example.cast.mynamespace")
        )
        .build()
  }
}
Ява
public class MyReceiverOptionsProvider implements ReceiverOptionsProvider {
  @Override
  public CastReceiverOptions getOptions(Context context) {
    return new CastReceiverOptions.Builder(context)
        .setCustomNamespaces(
              Arrays.asList("urn:x-cast:com.example.cast.mynamespace"))
        .build();
  }
}

Android TV — Отправка сообщений

Котлин
// If senderId is null, then the message is broadcasted to all senders.
CastReceiverContext.getInstance().sendMessage(
    "urn:x-cast:com.example.cast.mynamespace", senderId, customString)
Ява
// If senderId is null, then the message is broadcasted to all senders.
CastReceiverContext.getInstance().sendMessage(
    "urn:x-cast:com.example.cast.mynamespace", senderId, customString);

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

Котлин
class MyCustomMessageListener : MessageReceivedListener {
    override fun onMessageReceived(
        namespace: String, senderId: String?, message: String ) {
        ...
    }
}

CastReceiverContext.getInstance().setMessageReceivedListener(
    "urn:x-cast:com.example.cast.mynamespace", new MyCustomMessageListener());
Ява
class MyCustomMessageListener implements CastReceiverContext.MessageReceivedListener {
  @Override
  public void onMessageReceived(
      String namespace, String senderId, String message) {
    ...
  }
}

CastReceiverContext.getInstance().setMessageReceivedListener(
    "urn:x-cast:com.example.cast.mynamespace", new MyCustomMessageListener());