На этой странице представлены фрагменты кода и описания функций, доступных для настройки приложения Android TV Receiver.
Настройка библиотек
Чтобы сделать API Cast Connect доступными для вашего приложения на Android TV:
- Откройте файл
build.gradleв каталоге модуля вашего приложения. - Убедитесь, что
google()включена в списокrepositories.repositories { google() } - В зависимости от типа целевого устройства для вашего приложения, добавьте последние версии библиотек в раздел зависимостей:
- Для приложения Android Receiver:
dependencies { implementation 'com.google.android.gms:play-services-cast-tv:21.1.1' implementation 'com.google.android.gms:play-services-cast:22.2.0' }
- Для приложения Sender на Android:
dependencies { implementation 'com.google.android.gms:play-services-cast:21.1.1' implementation 'com.google.android.gms:play-services-cast-framework:22.2.0' }
- Для приложения Android Receiver:
- Сохраните изменения и нажмите кнопку
Sync Project with Gradle Filesна панели инструментов.
- Убедитесь, что ваш
Podfileориентирован наgoogle-cast-sdkверсии 4.8.4 или выше. - Целевая платформа: iOS 15 или выше. Дополнительные сведения см. в примечаниях к выпуску .
platform: ios, '15' def target_pods pod 'google-cast-sdk', '~>4.8.4' end
- Требуется браузер Chromium версии M87 или выше.
- Добавьте библиотеку Web Sender API в свой проект.
<script src="//www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1"></script>
Требования AndroidX
Для новых версий Google Play Services требуется обновление приложения для использования пространства имен androidx . Следуйте инструкциям по миграции на AndroidX .
Приложение для Android TV — необходимые условия
Для поддержки Cast Connect в вашем приложении для Android TV необходимо создавать и поддерживать события из медиасессии. Данные, предоставляемые вашей медиасессией, содержат основную информацию — например, позицию, состояние воспроизведения и т. д. — о статусе вашего медиафайла. Ваша медиасессия также используется библиотекой Cast Connect для сигнализации о получении определенных сообщений от отправителя, например, о паузе.
Для получения дополнительной информации о медиасессиях и способах их инициализации см. руководство по работе с медиасессиями .
Жизненный цикл медиасессии
Ваше приложение должно создавать медиасессию при начале воспроизведения и освобождать её, когда управление воспроизведением становится невозможным. Например, если ваше приложение предназначено для воспроизведения видео, вы должны освобождать сессию, когда пользователь выходит из процесса воспроизведения — либо выбрав «назад» для просмотра другого контента, либо переведя приложение в фоновый режим. Если ваше приложение предназначено для воспроизведения музыки, вы должны освобождать сессию, когда воспроизведение медиафайлов прекращается.
Обновление статуса сессии
Данные в вашей медиасессии должны быть актуальными и соответствовать состоянию вашего проигрывателя. Например, когда воспроизведение приостановлено, вам следует обновить состояние воспроизведения, а также поддерживаемые действия. В следующих таблицах перечислены состояния, за поддержание которых вы несете ответственность.
MediaMetadataCompat
| Поле метаданных | Описание |
|---|---|
| METADATA_KEY_TITLE (обязательно) | Название медиа-издания. |
| METADATA_KEY_DISPLAY_SUBTITLE | Подзаголовок. |
| METADATA_KEY_DISPLAY_ICON_URI | URL значка. |
| METADATA_KEY_DURATION (обязательно) | Продолжительность воспроизведения медиафайла. |
| METADATA_KEY_MEDIA_URI | Идентификатор контента. |
| METADATA_KEY_ARTIST | Художник. |
| METADATA_KEY_ALBUM | Альбом. |
PlaybackStateCompat
| Необходимый метод | Описание |
|---|---|
| setActions() | Задает поддерживаемые команды управления мультимедиа. |
| setState() | Установите состояние воспроизведения и текущую позицию. |
MediaSessionCompat
| Необходимый метод | Описание |
|---|---|
| setRepeatMode() | Устанавливает режим повтора. |
| setShuffleMode() | Устанавливает режим перемешивания. |
| setMetadata() | Задает метаданные медиафайлов. |
| setPlaybackState() | Устанавливает состояние воспроизведения. |
private fun updateMediaSession() { val metadata = MediaMetadataCompat.Builder() .putString(MediaMetadataCompat.METADATA_KEY_TITLE, "title") .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "subtitle") .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI, mMovie.getCardImageUrl()) .build() val playbackState = PlaybackStateCompat.Builder() .setState( PlaybackStateCompat.STATE_PLAYING, player.getPosition(), player.getPlaybackSpeed(), System.currentTimeMillis() ) .build() mediaSession.setMetadata(metadata) mediaSession.setPlaybackState(playbackState) }
private void updateMediaSession() {
MediaMetadataCompat metadata =
new MediaMetadataCompat.Builder()
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, "title")
.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "subtitle")
.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI,mMovie.getCardImageUrl())
.build();
PlaybackStateCompat playbackState =
new PlaybackStateCompat.Builder()
.setState(
PlaybackStateCompat.STATE_PLAYING,
player.getPosition(),
player.getPlaybackSpeed(),
System.currentTimeMillis())
.build();
mediaSession.setMetadata(metadata);
mediaSession.setPlaybackState(playbackState);
}Обработка и контроль транспортировки
Ваше приложение должно реализовывать функцию обратного вызова для управления транспортным режимом медиасессии. В следующей таблице показано, какие действия управления транспортным режимом им необходимо обрабатывать:
MediaSessionCompat.Callback
| Действия | Описание |
|---|---|
| onPlay() | Резюме |
| onPause() | Пауза |
| onSeekTo() | Стремитесь занять должность |
| onStop() | Остановите нынешние СМИ |
class MyMediaSessionCallback : MediaSessionCompat.Callback() { override fun onPause() { // Pause the player and update the play state. ... } override fun onPlay() { // Resume the player and update the play state. ... } override fun onSeekTo (long pos) { // Seek and update the play state. ... } ... } mediaSession.setCallback( MyMediaSessionCallback() );
public MyMediaSessionCallback extends MediaSessionCompat.Callback { public void onPause() { // Pause the player and update the play state. ... } public void onPlay() { // Resume the player and update the play state. ... } public void onSeekTo (long pos) { // Seek and update the play state. ... } ... } mediaSession.setCallback(new MyMediaSessionCallback());
Настройка поддержки Cast
Когда приложение-отправитель отправляет запрос на запуск, создается интент с пространством имен приложения. Ваше приложение отвечает за его обработку и создание экземпляра объекта CastReceiverContext при запуске приложения на телевизоре. Объект CastReceiverContext необходим для взаимодействия с Cast во время работы приложения на телевизоре. Этот объект позволяет вашему приложению на телевизоре принимать медиасообщения Cast, поступающие от любых подключенных отправителей.
Настройка Android TV
Добавление фильтра намерения запуска
Добавьте новый фильтр намерений к активности, для которой вы хотите обрабатывать намерение запуска из вашего приложения-отправителя:
<activity android:name="com.example.activity">
<intent-filter>
<action android:name="com.google.android.gms.cast.tv.action.LAUNCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
Укажите поставщика параметров получателя
Для предоставления CastReceiverOptions необходимо реализовать компонент ReceiverOptionsProvider :
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 .
Контекст приемника Cast
Инициализируйте 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 для автоматического поддержания сессии и всей связанной информации, включая состояние воспроизведения, вместо того, чтобы отслеживать изменения вручную.
Для обработки событий MediaButton можно использовать MediaSessionConnector.MediaButtonEventHandler , вызвав метод setMediaButtonEventHandler(MediaButtonEventHandler) тогда как в противном случае обработка событий по умолчанию осуществляется методом MediaSessionCompat.Callback .
Чтобы интегрировать MediaSessionConnector в ваше приложение, добавьте следующее в класс активности проигрывателя или в тот раздел, где вы управляете медиасессией:
class PlayerActivity : Activity() { private var mMediaSession: MediaSessionCompat? = null private var mMediaSessionConnector: MediaSessionConnector? = null private var mMediaManager: MediaManager? = null override fun onCreate(savedInstanceState: Bundle?) { ... mMediaSession = MediaSessionCompat(this, LOG_TAG) mMediaSessionConnector = MediaSessionConnector(mMediaSession!!) ... } override fun onStart() { ... mMediaManager = receiverContext.getMediaManager() mMediaManager!!.setSessionCompatToken(currentMediaSession.getSessionToken()) mMediaSessionConnector!!.setPlayer(mExoPlayer) mMediaSessionConnector!!.setMediaMetadataProvider(mMediaMetadataProvider) mMediaSession!!.isActive = true ... } override fun onStop() { ... mMediaSessionConnector!!.setPlayer(null) mMediaSession!!.release() mMediaManager!!.setSessionCompatToken(null) ... } }
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(); } }
Требуется версия google-cast-sdk v4.4.8 или выше.
Флаг androidReceiverCompatible устанавливается в GCKLaunchOptions (который является частью GCKCastOptions ):
let options = GCKCastOptions(discoveryCriteria: GCKDiscoveryCriteria(applicationID: kReceiverAppID)) ... let launchOptions = GCKLaunchOptions() launchOptions.androidReceiverCompatible = true options.launchOptions = launchOptions GCKCastContext.setSharedInstanceWith(options)
Требуется браузер Chromium версии M87 или выше.
const context = cast.framework.CastContext.getInstance(); const castOptions = new cast.framework.CastOptions(); castOptions.receiverApplicationId = kReceiverAppID; castOptions.androidReceiverCompatible = true; context.setOptions(castOptions);
Настройка консоли разработчика Cast
Настройте приложение Android TV.
Добавьте имя пакета вашего приложения для Android TV в консоль разработчика Cast , чтобы связать его с идентификатором вашего приложения Cast.

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

Без регистрации Cast Connect будет работать только с приложениями, установленными из Google Play Store, по соображениям безопасности.
Для получения дополнительной информации о регистрации устройства Cast или Android TV для разработки приложений с использованием Cast, см. страницу регистрации .
Загрузка медиафайлов
Если вы уже реализовали поддержку глубоких ссылок в своем приложении для Android TV, то у вас должно быть аналогичное определение, настроенное в файле Manifest вашего приложения для Android TV:
<activity android:name="com.example.activity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="https"/>
<data android:host="www.example.com"/>
<data android:pathPattern=".*"/>
</intent-filter>
</activity>
Загрузка по сущности отправителя
Отправители могут передавать прямую ссылку, указав entity в информации о медиафайлах для запроса на загрузку:
val mediaToLoad = MediaInfo.Builder("some-id") .setEntity("https://example.com/watch/some-id") ... .build() val loadRequest = MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") ... .build() remoteMediaClient.load(loadRequest)
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);
let mediaInfoBuilder = GCKMediaInformationBuilder(entity: "https://example.com/watch/some-id") ... mediaInformation = mediaInfoBuilder.build() let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder() mediaLoadRequestDataBuilder.mediaInformation = mediaInformation mediaLoadRequestDataBuilder.credentials = "user-credentials" ... let mediaLoadRequestData = mediaLoadRequestDataBuilder.build() remoteMediaClient?.loadMedia(with: mediaLoadRequestData)
Требуется браузер Chromium версии M87 или выше.
let mediaInfo = new chrome.cast.media.MediaInfo('some-id"', 'video/mp4'); mediaInfo.entity = 'https://example.com/watch/some-id'; ... let request = new chrome.cast.media.LoadRequest(mediaInfo); request.credentials = 'user-credentials'; ... cast.framework.CastContext.getInstance().getCurrentSession().loadMedia(request);
Команда загрузки отправляется через Intent, содержащий вашу глубокую ссылку и имя пакета, определенное вами в консоли разработчика.
Настройка учетных данных ATV на отправителе
Возможно, ваше приложение Web Receiver и приложение для Android TV поддерживают разные прямые ссылки и credentials (например, если вы обрабатываете аутентификацию по-разному на этих двух платформах). Для решения этой проблемы вы можете предоставить альтернативные entity и credentials для Android TV:
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);
let mediaInfoBuilder = GCKMediaInformationBuilder(entity: "https://example.com/watch/some-id") mediaInfoBuilder.atvEntity = "myscheme://example.com/atv/some-id" ... mediaInformation = mediaInfoBuilder.build() let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder() mediaLoadRequestDataBuilder.mediaInformation = mediaInformation mediaLoadRequestDataBuilder.credentials = "user-credentials" mediaLoadRequestDataBuilder.atvCredentials = "atv-user-credentials" ... let mediaLoadRequestData = mediaLoadRequestDataBuilder.build() remoteMediaClient?.loadMedia(with: mediaLoadRequestData)
Требуется браузер Chromium версии M87 или выше.
let mediaInfo = new chrome.cast.media.MediaInfo('some-id"', 'video/mp4'); mediaInfo.entity = 'https://example.com/watch/some-id'; mediaInfo.atvEntity = 'myscheme://example.com/atv/some-id'; ... let request = new chrome.cast.media.LoadRequest(mediaInfo); request.credentials = 'user-credentials'; request.atvCredentials = 'atv-user-credentials'; ... cast.framework.CastContext.getInstance().getCurrentSession().loadMedia(request);
Если запущено приложение Web Receiver, оно использует entity и credentials в запросе на загрузку. Однако, если запущено ваше приложение для Android TV, SDK переопределяет entity и credentials вашими объектами atvEntity и atvCredentials (если они указаны).
Загрузка по идентификатору контента или данным очереди мультимедиа.
Если вы не используете entity или atvEntity , а используете Content ID или Content URL в информации о медиафайлах или более подробные данные запроса на загрузку медиафайлов, вам необходимо добавить следующий предопределенный фильтр намерений в ваше приложение для Android TV:
<activity android:name="com.example.activity">
<intent-filter>
<action android:name="com.google.android.gms.cast.tv.action.LOAD"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
На стороне отправителя, аналогично загрузке по сущности , вы можете создать запрос на загрузку с информацией о вашем контенте и вызвать load() .
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);
let mediaInfoBuilder = GCKMediaInformationBuilder(contentId: "some-id") ... mediaInformation = mediaInfoBuilder.build() let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder() mediaLoadRequestDataBuilder.mediaInformation = mediaInformation mediaLoadRequestDataBuilder.credentials = "user-credentials" ... let mediaLoadRequestData = mediaLoadRequestDataBuilder.build() remoteMediaClient?.loadMedia(with: mediaLoadRequestData)
Требуется браузер Chromium версии M87 или выше.
let mediaInfo = new chrome.cast.media.MediaInfo('some-id"', 'video/mp4'); ... let request = new chrome.cast.media.LoadRequest(mediaInfo); ... cast.framework.CastContext.getInstance().getCurrentSession().loadMedia(request);
Обработка запросов на загрузку
Для обработки этих запросов на загрузку в вашем Activity необходимо обрабатывать интенты в коллбэках жизненного цикла Activity:
class MyActivity : Activity() { override fun onStart() { super.onStart() val mediaManager = CastReceiverContext.getInstance().getMediaManager() // Pass the intent to the SDK. You can also do this in onCreate(). if (mediaManager.onNewIntent(intent)) { // If the SDK recognizes the intent, you should early return. return } // If the SDK doesn't recognize the intent, you can handle the intent with // your own logic. ... } // For some cases, a new load intent triggers onNewIntent() instead of // onStart(). override fun onNewIntent(intent: Intent) { val mediaManager = CastReceiverContext.getInstance().getMediaManager() // Pass the intent to the SDK. You can also do this in onCreate(). if (mediaManager.onNewIntent(intent)) { // If the SDK recognizes the intent, you should early return. return } // If the SDK doesn't recognize the intent, you can handle the intent with // your own logic. ... } }
public class MyActivity extends Activity { @Override protected void onStart() { super.onStart(); MediaManager mediaManager = CastReceiverContext.getInstance().getMediaManager(); // Pass the intent to the SDK. You can also do this in onCreate(). if (mediaManager.onNewIntent(getIntent())) { // If the SDK recognizes the intent, you should early return. return; } // If the SDK doesn't recognize the intent, you can handle the intent with // your own logic. ... } // For some cases, a new load intent triggers onNewIntent() instead of // onStart(). @Override protected void onNewIntent(Intent intent) { MediaManager mediaManager = CastReceiverContext.getInstance().getMediaManager(); // Pass the intent to the SDK. You can also do this in onCreate(). if (mediaManager.onNewIntent(intent)) { // If the SDK recognizes the intent, you should early return. return; } // If the SDK doesn't recognize the intent, you can handle the intent with // your own logic. ... } }
Если MediaManager обнаруживает, что намерение является намерением загрузки, он извлекает объект MediaLoadRequestData из намерения и вызывает метод MediaLoadCommandCallback.onLoad() . Вам необходимо переопределить этот метод для обработки запроса на загрузку. Обратный вызов должен быть зарегистрирован до вызова метода MediaManager.onNewIntent() (рекомендуется сделать это в методе onCreate() Activity или Application).
class MyActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val mediaManager = CastReceiverContext.getInstance().getMediaManager() mediaManager.setMediaLoadCommandCallback(MyMediaLoadCommandCallback()) } } class MyMediaLoadCommandCallback : MediaLoadCommandCallback() { override fun onLoad( senderId: String?, loadRequestData: MediaLoadRequestData ): Task{ return Tasks.call { // Resolve the entity into your data structure and load media. val mediaInfo = loadRequestData.getMediaInfo() if (!checkMediaInfoSupported(mediaInfo)) { // Throw MediaException to indicate load failure. throw MediaException( MediaError.Builder() .setDetailedErrorCode(DetailedErrorCode.LOAD_FAILED) .setReason(MediaError.ERROR_REASON_INVALID_REQUEST) .build() ) } myFillMediaInfo(MediaInfoWriter(mediaInfo)) myPlayerLoad(mediaInfo.getContentUrl()) // Update media metadata and state (this clears all previous status // overrides). castReceiverContext.getMediaManager() .setDataFromLoad(loadRequestData) ... castReceiverContext.getMediaManager().broadcastMediaStatus() // Return the resolved MediaLoadRequestData to indicate load success. return loadRequestData } } private fun myPlayerLoad(contentURL: String) { myPlayer.load(contentURL) // Update the MediaSession state. val playbackState: PlaybackStateCompat = Builder() .setState( player.getState(), player.getPosition(), System.currentTimeMillis() ) ... .build() mediaSession.setPlaybackState(playbackState) }
public class MyActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); MediaManager mediaManager = CastReceiverContext.getInstance().getMediaManager(); mediaManager.setMediaLoadCommandCallback(new MyMediaLoadCommandCallback()); } } public class MyMediaLoadCommandCallback extends MediaLoadCommandCallback { @Override public TaskonLoad(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 TaskonSkipAd(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 — поддерживает, необходимо правильно настроить поддерживаемые действия на каждой платформе и убедиться, что приложение-отправитель корректно отображает пользовательский интерфейс.
Изменение статуса медиафайла
Для поддержки расширенных функций, таких как отслеживание треков, показ рекламы, воспроизведение в прямом эфире и постановка в очередь, вашему приложению для 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 перед отправкой
Как и в случае с SDK Web Receiver, если вы хотите внести некоторые завершающие изменения перед отправкой, вы можете указать 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 отправитель может указать средство проверки запуска, чтобы убедиться, что учетные данные отправителя разрешены. Если нет, то SDK Cast Connect будет использовать ваш веб-приемник.
Данные учетных данных для запуска приложения отправителя
На стороне отправителя можно указать в поле CredentialsData , кто именно присоединяется к сессии.
credentials представляют собой строку, которую может определить пользователь, если ваше приложение для Apple TV может ее распознать. credentialsType определяет, с какой платформы поступают CredentialsData , или может быть задан пользовательским значением. По умолчанию он устанавливается на платформу, с которой отправляются данные.
Данные CredentialsData передаются в ваше приложение Android TV только во время запуска или подключения. Если вы установите их снова во время подключения, они не будут переданы в ваше приложение Android TV. Если отправитель меняет профиль во время подключения, вы можете либо остаться в сессии, либо вызвать SessionManager.endCurrentCastSession(boolean stopCasting) если считаете, что новый профиль несовместим с сессией.
Данные CredentialsData для каждого отправителя можно получить, используя getSenders в CastReceiverContext для получения SenderInfo , getCastLaunchRequest() для получения CastLaunchRequest , а затем getCredentialsData() .
Требуется версия 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()); Требуется версия google-cast-sdk v4.8.4 или выше.
Вызывается в любое время после установки параметров: GCKCastContext.setSharedInstanceWith(options) .
GCKCastContext.sharedInstance().setLaunch( GCKCredentialsData(credentials: "{\"userId\": \"abc\"}")
Требуется браузер Chromium версии M87 или выше.
Вызывается в любое время после установки параметров: cast.framework.CastContext.getInstance().setOptions(options); .
let credentialsData = new chrome.cast.CredentialsData("{\"userId\": \"abc\"}"); cast.framework.CastContext.getInstance().setLaunchCredentialsData(credentialsData);
Внедрение средства проверки запросов на запуск квадроциклов.
Данные CredentialsData передаются в ваше приложение для Android TV, когда отправитель пытается запустить или присоединиться. Вы можете реализовать LaunchRequestChecker , чтобы разрешить или отклонить этот запрос.
Если запрос отклонен, вместо запуска приложения Apple TV загружается веб-приемник. Следует отклонять запрос, если Apple TV не может обработать запрос пользователя на запуск или подключение. Например, это может быть связано с тем, что в приложении Apple TV подключен другой пользователь, чем тот, кто отправляет запрос, и ваше приложение не может обработать смену учетных данных, или в приложении Apple TV в данный момент нет подключенного пользователя.
Если запрос разрешен, приложение ATV запускается. Вы можете настроить это поведение в зависимости от того, поддерживает ли ваше приложение отправку запросов на загрузку, когда пользователь не авторизован в приложении ATV, или если имеет место несоответствие пользователей. Это поведение полностью настраивается в LaunchRequestChecker .
Создайте класс, реализующий интерфейс CastReceiverOptions.LaunchRequestChecker :
class MyLaunchRequestChecker : LaunchRequestChecker { override fun checkLaunchRequestSupported(launchRequest: CastLaunchRequest): Task{ return Tasks.call { myCheckLaunchRequest( launchRequest ) } } } private fun myCheckLaunchRequest(launchRequest: CastLaunchRequest): Boolean { val credentialsData = launchRequest.getCredentialsData() ?: return false // or true if you allow anonymous users to join. // The request comes from a mobile device, e.g. checking user match. return if (credentialsData.credentialsType == CredentialsData.CREDENTIALS_TYPE_ANDROID) { myCheckMobileCredentialsAllowed(credentialsData.getCredentials()) } else false // Unrecognized credentials type. }
public class MyLaunchRequestChecker implements CastReceiverOptions.LaunchRequestChecker { @Override public TaskcheckLaunchRequestSupported(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(); } }
Если в параметре LaunchRequestChecker установить значение true , запустится приложение ATV, а если false — ваше приложение Web Receiver.
Отправка и получение пользовательских сообщений
Протокол Cast позволяет отправлять пользовательские строковые сообщения между отправителями и вашим приложением-получателем. Перед инициализацией CastReceiverContext необходимо зарегистрировать пространство имен (канал) для отправки сообщений.
Android TV — Укажите пользовательское пространство имен
В процессе настройки необходимо указать поддерживаемые пространства имен в параметре CastReceiverOptions :
class MyReceiverOptionsProvider : ReceiverOptionsProvider { override fun getOptions(context: Context?): CastReceiverOptions { return CastReceiverOptions.Builder(context) .setCustomNamespaces( Arrays.asList("urn:x-cast:com.example.cast.mynamespace") ) .build() } }
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());