Добавьте основные функции в свой 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

Новые версии Google Play Services требуют, чтобы приложение было обновлено для использования пространства имен androidx . Следуйте инструкциям по переходу на AndroidX .

Приложение Android TV — предварительные условия

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

Дополнительную информацию о медиа-сеансе и о том, как инициализировать медиа-сеанс, см. в руководстве по работе с медиа-сеансом .

Жизненный цикл медиа-сеанса

Ваше приложение должно создавать сеанс мультимедиа при запуске воспроизведения и завершать его, когда его больше нельзя контролировать. Например, если ваше приложение — это видеоприложение, вы должны завершать сеанс, когда пользователь выходит из процесса воспроизведения — либо выбрав «назад», чтобы просмотреть другой контент, либо переведя приложение в фоновый режим. Если ваше приложение — это музыкальное приложение, вы должны завершать сеанс, когда ваше приложение больше не воспроизводит медиа.

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

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

MediaMetadataCompat

Поле метаданных Описание
METADATA_KEY_TITLE (обязательно) Название СМИ.
METADATA_KEY_DISPLAY_SUBTITLE Подзаголовок.
METADATA_KEY_DISPLAY_ICON_URI URL-адрес значка.
METADATA_KEY_DURATION (обязательно) Продолжительность медиа.
METADATA_KEY_MEDIA_URI Идентификатор контента.
METADATA_KEY_ARTIST Художник.
МЕТАДАННЫЕ_КЛЮЧ_АЛЬБОМ Альбом.

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.Обратный вызов

Действия Описание
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 при запуске приложения TV. Объект CastReceiverContext необходим для взаимодействия с Cast во время работы приложения TV. Этот объект позволяет вашему приложению TV принимать сообщения Cast media, поступающие от любых подключенных отправителей.

Настройка 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() (рекомендуется, чтобы он был в методе Activity или Application onCreate() ).

Котлин
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 ., чтобы разрешить или отклонить этот запрос.

Если запрос отклонен, загружается Web Receiver вместо того, чтобы запускаться в приложении 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());
,

На этой странице содержатся фрагменты кода и описания функций, доступных для настройки приложения 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

Новые версии Google Play Services требуют, чтобы приложение было обновлено для использования пространства имен androidx . Следуйте инструкциям по переходу на AndroidX .

Приложение Android TV — предварительные условия

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

Дополнительную информацию о медиа-сеансе и о том, как инициализировать медиа-сеанс, см. в руководстве по работе с медиа-сеансом .

Жизненный цикл медиа-сеанса

Ваше приложение должно создавать сеанс мультимедиа при запуске воспроизведения и завершать его, когда его больше нельзя контролировать. Например, если ваше приложение — это видеоприложение, вы должны завершать сеанс, когда пользователь выходит из процесса воспроизведения — либо выбрав «назад», чтобы просмотреть другой контент, либо переведя приложение в фоновый режим. Если ваше приложение — это музыкальное приложение, вы должны завершать сеанс, когда ваше приложение больше не воспроизводит медиа.

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

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

MediaMetadataCompat

Поле метаданных Описание
METADATA_KEY_TITLE (обязательно) Название СМИ.
METADATA_KEY_DISPLAY_SUBTITLE Подзаголовок.
METADATA_KEY_DISPLAY_ICON_URI URL-адрес значка.
METADATA_KEY_DURATION (обязательно) Продолжительность медиа.
METADATA_KEY_MEDIA_URI Идентификатор контента.
METADATA_KEY_ARTIST Художник.
МЕТАДАННЫЕ_КЛЮЧ_АЛЬБОМ Альбом.

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.Обратный вызов

Действия Описание
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 при запуске приложения TV. Объект CastReceiverContext необходим для взаимодействия с Cast во время работы приложения TV. Этот объект позволяет вашему приложению TV принимать сообщения Cast media, поступающие от любых подключенных отправителей.

Настройка 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() (рекомендуется, чтобы он был в методе Activity или Application onCreate() ).

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

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

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