На этой странице приведены фрагменты кода и описания функций, доступных для настройки приложения 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.3.1' }
-
Для приложения-отправителя Android:
dependencies { implementation 'com.google.android.gms:play-services-cast:21.1.1' implementation 'com.google.android.gms:play-services-cast-framework:22.3.1' }
-
Для приложения Android Receiver:
-
Сохраните изменения и нажмите
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>
Укажите поставщика параметров получателя
Чтобы предоставить 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.
Контекст устройства-получателя трансляции
Инициализируйте 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(), особенно если в вашем нативном приложении несколько Activity. Это позволяет избежать конфликтов при вызове 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(); } }
Требуется 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 Developer Console.

Без регистрации Cast Connect будет работать только с приложениями, установленными из Google Play, в целях безопасности.
Дополнительную информацию о регистрации устройств 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);
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 или 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 недоступны в 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 может быть разрешено запускать сеанс или присоединяться к нему только определенным пользователям. Например, разрешить отправителю запустить или присоединиться к встрече, только если:
- В приложении отправителя выполнен вход в тот же аккаунт и профиль, что и в приложении ATV.
- В приложении-отправителе выполнен вход в тот же аккаунт, но в другой профиль, нежели в приложении ATV.
Если ваше приложение может работать с несколькими или анонимными пользователями, вы можете разрешить другим пользователям присоединяться к сеансу ATV. Если пользователь предоставит учетные данные, ваше приложение для Android TV должно обработать их, чтобы можно было отслеживать прогресс и другие данные пользователя.
При запуске приложения-отправителя или его подключении к приложению для Android TV приложение-отправитель должно предоставить учетные данные, которые представляют пользователя, присоединяющегося к сеансу.
Перед тем как отправитель запустит ваше приложение для Android TV и подключится к нему, вы можете указать проверку запуска, чтобы узнать, разрешены ли учетные данные отправителя. В противном случае SDK Cast Connect запустит веб-приемник.
Данные учетных данных для запуска приложения-отправителя
На стороне отправителя можно указать 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 не выполнен вход ни одного пользователя.
Если запрос разрешен, приложение 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(); } }
При нажатии на кнопку 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());