На этой странице приведены фрагменты кода и описания функций, доступных для настройки приложения Android TV Receiver.
Настройка библиотек
Чтобы сделать Cast Connect API доступными для приложения Android TV, выполните следующие действия:
-
Откройте файл
build.gradleв каталоге модулей приложения. -
Убедитесь, что
google()включен в списокrepositories.repositories { google() } -
В зависимости от типа целевого устройства для вашего приложения добавьте в зависимости последние версии библиотек:
-
Для приложения-приемника Android:
dependencies { implementation 'com.google.android.gms:play-services-cast-tv:21.1.1' implementation 'com.google.android.gms:play-services-cast:23.0.0' }
-
Для приложения-отправителя Android:
dependencies { implementation 'com.google.android.gms:play-services-cast:21.1.1' implementation 'com.google.android.gms:play-services-cast-framework:23.0.0' }
-
Для приложения-приемника Android:
-
Сохраните изменения и нажмите
Sync Project with Gradle Filesна панели инструментов.
-
Убедитесь, что
Podfileподдерживаетgoogle-cast-sdk4.8.6 или более поздней версии. -
Целевая версия iOS – 16 или более поздняя. Подробную информацию можно найти в примечаниях к выпуску.
platform: ios, '16' def target_pods pod 'google-cast-sdk', '~>4.8.6' end
- Требуется браузер Chromium версии M87 или более поздней.
-
Добавьте библиотеку Web Sender API в проект
<script src="//www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1"></script>
Требование AndroidX
Для работы с новыми версиями сервисов Google Play приложение должно быть обновлено для использования пространства имен androidx. Следуйте инструкциям по переходу на AndroidX.
Приложение для Android TV – предварительные требования
Чтобы обеспечить поддержку Cast Connect в приложении для Android TV, необходимо создать события из сеанса воспроизведения медиаконтента и обеспечить их поддержку. Данные, предоставляемые сеансом мультимедиа, содержат основную информацию, например позицию, состояние воспроизведения и т. д., для статуса мультимедиа. Библиотека Cast Connect также использует медиасеанс, чтобы сигнализировать о получении определенных сообщений от отправителя, например о паузе.
Подробнее о том, как работать с медиасеансами…
Жизненный цикл сеанса воспроизведения медиаконтента
Приложение должно создавать сеанс воспроизведения, когда оно начинается, и завершать его, когда управление становится недоступно. Например, если у вас видеоприложение, сеанс нужно завершать, когда пользователь выходит из режима воспроизведения, нажимая кнопку "Назад" для просмотра другого контента или сворачивая приложение. Если у вас музыкальное приложение, сеанс нужно завершать, когда воспроизведение медиаконтента прекращается.
Обновление статуса сеанса
Данные в сеансе воспроизведения должны соответствовать статусу проигрывателя. Например, когда воспроизведение приостановлено, вы должны обновить состояние воспроизведения, а также поддерживаемые действия. В таблицах ниже указано, какие состояния вы должны поддерживать в актуальном виде.
MediaMetadataCompat
| Поле метаданных | Описание |
|---|---|
| METADATA_KEY_TITLE (обязательно) | Название медиафайла. |
| METADATA_KEY_DISPLAY_SUBTITLE | Подзаголовок. |
| METADATA_KEY_DISPLAY_ICON_URI | URL значка. |
| METADATA_KEY_DURATION (обязательный) | длительность медиафайла; |
| METADATA_KEY_MEDIA_URI | Идентификатор контента. |
| METADATA_KEY_ARTIST | Исполнитель. |
| METADATA_KEY_ALBUM | Альбом. |
PlaybackStateCompat
| Обязательный метод | Описание |
|---|---|
| setActions() | Задает поддерживаемые команды для медиаконтента. |
| setState() | Задайте состояние воспроизведения и текущую позицию. |
MediaSessionCompat
| Обязательный метод | Описание |
|---|---|
| setRepeatMode() | Устанавливает режим повтора. |
| setShuffleMode() | Включает воспроизведение в случайном порядке. |
| setMetadata() | Задает метаданные медиаконтента. |
| setPlaybackState() | Задает состояние воспроизведения. |
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, который должен обрабатывать интент запуска из приложения-отправителя:
<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 в приложение, добавьте следующий код в класс PlayerActivity или в другое место, где вы управляете медиасеансом:
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 Developer Console
Как настроить приложение для Android TV
Добавьте название пакета приложения для Android TV в Cast Developer Console, чтобы связать его с идентификатором приложения Cast.

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

В целях безопасности без регистрации Cast Connect будет работать только для приложений, установленных из Google Play.
Дополнительную информацию о регистрации устройств Cast или устройств Android TV для разработки приложений Cast можно найти на странице регистрации.
Загрузка медиафайлов
Если вы уже реализовали поддержку ссылок на контент в приложении для Android TV, то в манифесте приложения должно быть похожее определение:
<activity android:name="com.example.activity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="https"/>
<data android:host="www.example.com"/>
<data android:pathPattern=".*"/>
</intent-filter>
</activity>
Загрузка по объекту на отправителе
На отправителях вы можете передать ссылку на контент, задав entity в информации о медиаконтенте для запроса на загрузку:
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);
Команда загрузки отправляется через намерение со ссылкой на контент и названием пакета, которое вы указали в консоли разработчика.
Настройка учетных данных 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 (если они указаны).
Загрузка по Content ID или MediaQueueData
Если вы не используете entity или atvEntity, а используете Content ID или Content URL в Media Information или более подробные данные Media Load Request Data, вам нужно добавить в приложение для Android TV следующий предопределенный фильтр интентов:
<activity android:name="com.example.activity">
<intent-filter>
<action android:name="com.google.android.gms.cast.tv.action.LOAD"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
На стороне отправителя, как и в случае с загрузкой по объекту, вы можете создать запрос на загрузку с информацией о контенте и вызвать load().
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:
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 недоступны в 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 поддерживает только базовое управление медиаконтентом, а веб-приемник – более продвинутое, убедитесь, что приложение-отправитель работает корректно при трансляции в приложение для Android TV. Например, если ваше приложение для Android TV не поддерживает изменение скорости воспроизведения, а веб-приемник поддерживает, вам следует правильно настроить поддерживаемые действия на каждой платформе и убедиться, что приложение-отправитель правильно отображает пользовательский интерфейс.
Изменение 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 могут быть ограничения на то, кто может запускать сеансы или присоединяться к ним. Например, разрешить отправителю запускать встречу или присоединяться к ней, только если:
- В приложении-отправителе выполнен вход в тот же аккаунт и профиль, что и в приложении Android TV.
- В приложении-отправителе выполнен вход в тот же аккаунт, но в другой профиль, нежели в приложении ATV.
Если ваше приложение поддерживает нескольких или анонимных пользователей, вы можете разрешить любому пользователю присоединиться к сеансу Android TV. Если пользователь вводит учетные данные, ваше приложение для Android TV должно обработать их, чтобы правильно отслеживать прогресс и другие пользовательские данные.
Когда приложение-отправитель запускает приложение для Android TV или подключается к нему, оно должно предоставить учетные данные пользователя, который присоединяется к сеансу.
Прежде чем отправитель запустит ваше приложение для Android TV и подключится к нему, вы можете указать средство проверки запуска, чтобы узнать, разрешены ли учетные данные отправителя. Если нет, Cast Connect SDK запускает веб-приемник.
Данные учетных данных для запуска приложения отправителя
На стороне отправителя вы можете указать CredentialsData, чтобы показать, кто присоединяется к сеансу.
credentials – это строка, которую может задать пользователь, если она понятна приложению Android TV. credentialsType определяет, с какой платформы поступает CredentialsData, или может быть пользовательским значением. По умолчанию это платформа, с которой отправляется запрос.
CredentialsData передается в приложение для Android TV только во время запуска или подключения. Если вы зададите его снова во время подключения, он не будет передан в приложение для Android TV. Если отправитель переключит профиль во время подключения, вы можете либо остаться в сеансе, либо вызвать SessionManager.endCurrentCastSession(boolean stopCasting), если считаете, что новый профиль несовместим с сеансом.
CredentialsData для каждого отправителя можно получить с помощью getSenders в CastReceiverContext, чтобы получить SenderInfo, getCastLaunchRequest(), чтобы получить CastLaunchRequest, а затем getCredentialsData().
Требуется 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.6 или более поздней.
Можно вызвать в любое время после установки параметров:
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
Когда отправитель пытается запустить приложение или присоединиться к нему, в приложение для Android TV передается CredentialsData. Вы можете реализовать LaunchRequestChecker.
чтобы одобрить или отклонить запрос.
Если запрос отклонен, вместо запуска приложения ATV загружается веб-приемник. Запрос следует отклонять, если ATV не может обработать запрос пользователя на запуск или присоединение. Например, в приложении для Android TV выполнен вход в аккаунт, отличный от того, который отправляет запрос, и ваше приложение не может переключить учетные данные, или в приложении для Android TV не выполнен вход ни в один аккаунт.
Если запрос одобрен, приложение Android TV запускается. Вы можете настроить это поведение в зависимости от того, поддерживает ли ваше приложение отправку запросов на загрузку, когда пользователь не вошел в приложение 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(); } }
При разрешении true в LaunchRequestChecker запускается приложение ATV, а при разрешении false – веб-приложение получателя.
Отправка и получение собственных сообщений
Протокол 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());