На этой странице содержатся фрагменты кода и описания функций, доступных для настройки приложения 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.1.0' }
- Для приложения Android Sender:
dependencies { implementation 'com.google.android.gms:play-services-cast:21.1.1' implementation 'com.google.android.gms:play-services-cast-framework:22.1.0' }
- Для приложения Android Receiver:
- Сохраните изменения и нажмите
Sync Project with Gradle Files
на панели инструментов.
- Убедитесь, что ваш
Podfile
ориентирован наgoogle-cast-sdk
4.8.3 или выше. - Целевая версия — iOS 14 или более поздняя. Подробнее см. в примечаниях к выпуску .
platform: ios, '14' def target_pods pod 'google-cast-sdk', '~>4.8.3' end
- Требуется браузер Chromium версии M87 или выше.
- Добавьте библиотеку API Web Sender в свой проект
<script src="//www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1"></script>
Требование AndroidX
Для использования пространства имён androidx
в новых версиях сервисов Google Play требуется обновить приложение. Следуйте инструкциям по переходу на AndroidX .
Приложение Android TV — предварительные условия
Для поддержки Cast Connect в приложении Android TV необходимо создавать и поддерживать события медиасеанса. Данные, предоставляемые медиасеансом, содержат основную информацию о статусе медиаконтента, например, положение, состояние воспроизведения и т. д. Библиотека Cast Connect также использует ваш медиасеанс для оповещения о получении определённых сообщений от отправителя, например, о паузе.
Дополнительную информацию о медиа-сеансе и о том, как инициализировать медиа-сеанс, см. в руководстве по работе с медиа-сеансом .
Жизненный цикл медиа-сеанса
Ваше приложение должно создавать медиасеанс при запуске воспроизведения и завершать его, когда управление им становится невозможным. Например, если ваше приложение предназначено для просмотра видео, сеанс следует завершать, когда пользователь завершает воспроизведение — либо нажимая кнопку «Назад» для просмотра другого контента, либо переводя приложение в фоновый режим. Если ваше приложение предназначено для прослушивания музыки, сеанс следует завершать, когда приложение прекращает воспроизведение медиаконтента.
Обновление статуса сеанса
Данные в вашем медиасеансе должны быть актуальными в соответствии со статусом вашего проигрывателя. Например, при приостановке воспроизведения необходимо обновлять состояние воспроизведения, а также поддерживаемые действия. В таблицах ниже перечислены состояния, за поддержание которых в актуальном состоянии вы отвечаете.
MediaMetadataCompat
Поле метаданных | Описание |
---|---|
METADATA_KEY_TITLE (обязательно) | Название СМИ. |
METADATA_KEY_DISPLAY_SUBTITLE | Подзаголовок. |
METADATA_KEY_DISPLAY_ICON_URI | URL-адрес значка. |
METADATA_KEY_DURATION (обязательно) | Продолжительность медиа. |
METADATA_KEY_MEDIA_URI | Идентификатор контента. |
METADATA_KEY_ARTIST | Художник. |
METADATA_KEY_ALBUM | Альбом. |
PlaybackStateCompat
Требуемый метод | Описание |
---|---|
setActions() | Устанавливает поддерживаемые медиа-команды. |
setState() | Установите состояние воспроизведения и текущую позицию. |
MediaSessionCompat
Требуемый метод | Описание |
---|---|
setRepeatMode() | Устанавливает режим повтора. |
setShuffleMode() | Устанавливает режим случайного воспроизведения. |
setMetadata() | Устанавливает метаданные мультимедиа. |
setPlaybackState() | Устанавливает состояние воспроизведения. |
private fun updateMediaSession() { val metadata = MediaMetadataCompat.Builder() .putString(MediaMetadataCompat.METADATA_KEY_TITLE, "title") .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "subtitle") .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI, mMovie.getCardImageUrl()) .build() val playbackState = PlaybackStateCompat.Builder() .setState( PlaybackStateCompat.STATE_PLAYING, player.getPosition(), player.getPlaybackSpeed(), System.currentTimeMillis() ) .build() mediaSession.setMetadata(metadata) mediaSession.setPlaybackState(playbackState) }
private void updateMediaSession() { MediaMetadataCompat metadata = new MediaMetadataCompat.Builder() .putString(MediaMetadataCompat.METADATA_KEY_TITLE, "title") .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "subtitle") .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI,mMovie.getCardImageUrl()) .build(); PlaybackStateCompat playbackState = new PlaybackStateCompat.Builder() .setState( PlaybackStateCompat.STATE_PLAYING, player.getPosition(), player.getPlaybackSpeed(), System.currentTimeMillis()) .build(); mediaSession.setMetadata(metadata); mediaSession.setPlaybackState(playbackState); }
Осуществление контроля за транспортом
Ваше приложение должно реализовывать обратный вызов управления транспортировкой медиа-сессии. В следующей таблице показаны действия управления транспортировкой, которые оно должно обрабатывать:
MediaSessionCompat.Callback
Действия | Описание |
---|---|
onPlay() | Резюме |
onPause() | Пауза |
onSeekTo() | Стремиться к позиции |
onStop() | Остановить текущее медиа |
class MyMediaSessionCallback : MediaSessionCompat.Callback() { override fun onPause() { // Pause the player and update the play state. ... } override fun onPlay() { // Resume the player and update the play state. ... } override fun onSeekTo (long pos) { // Seek and update the play state. ... } ... } mediaSession.setCallback( MyMediaSessionCallback() );
public MyMediaSessionCallback extends MediaSessionCompat.Callback { public void onPause() { // Pause the player and update the play state. ... } public void onPlay() { // Resume the player and update the play state. ... } public void onSeekTo (long pos) { // Seek and update the play state. ... } ... } mediaSession.setCallback(new MyMediaSessionCallback());
Настройка поддержки Cast
Когда приложение-отправитель отправляет запрос на запуск, создаётся намерение с пространством имён приложения. Ваше приложение отвечает за его обработку и создание экземпляра объекта CastReceiverContext
при запуске приложения для телевизора. Объект CastReceiverContext
необходим для взаимодействия с Cast во время работы приложения для телевизора. Этот объект позволяет вашему приложению для телевизора принимать медиасообщения Cast от любых подключённых отправителей.
Настройка Android TV
Добавление фильтра намерения запуска
Добавьте новый фильтр намерений к действию, которое должно обрабатывать намерение запуска из приложения-отправителя:
<activity android:name="com.example.activity">
<intent-filter>
<action android:name="com.google.android.gms.cast.tv.action.LAUNCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
Укажите поставщика опций приемника
Вам необходимо реализовать ReceiverOptionsProvider
для предоставления CastReceiverOptions
:
class MyReceiverOptionsProvider : ReceiverOptionsProvider { override fun getOptions(context: Context?): CastReceiverOptions { return CastReceiverOptions.Builder(context) .setStatusText("My App") .build() } }
public class MyReceiverOptionsProvider implements ReceiverOptionsProvider { @Override public CastReceiverOptions getOptions(Context context) { return new CastReceiverOptions.Builder(context) .setStatusText("My App") .build(); } }
Затем укажите поставщика опций в вашем AndroidManifest
:
<meta-data
android:name="com.google.android.gms.cast.tv.RECEIVER_OPTIONS_PROVIDER_CLASS_NAME"
android:value="com.example.mysimpleatvapplication.MyReceiverOptionsProvider" />
ReceiverOptionsProvider
используется для предоставления CastReceiverOptions
при инициализации CastReceiverContext
.
Контекст приемника трансляции
Инициализируйте CastReceiverContext
при создании вашего приложения:
override fun onCreate() { CastReceiverContext.initInstance(this) ... }
@Override public void onCreate() { CastReceiverContext.initInstance(this); ... }
Запускайте CastReceiverContext
, когда ваше приложение переходит на передний план:
CastReceiverContext.getInstance().start()
CastReceiverContext.getInstance().start();
Вызовите stop()
в CastReceiverContext
после того, как приложение перейдет в фоновый режим для видеоприложений или приложений, которые не поддерживают фоновое воспроизведение:
// Player has stopped. CastReceiverContext.getInstance().stop()
// Player has stopped. CastReceiverContext.getInstance().stop();
Кроме того, если ваше приложение поддерживает воспроизведение в фоновом режиме, вызовите stop()
для CastReceiverContext
, когда оно останавливает воспроизведение в фоновом режиме.
Мы настоятельно рекомендуем использовать LifecycleObserver из библиотеки androidx.lifecycle
для управления вызовами CastReceiverContext.start()
и CastReceiverContext.stop()
, особенно если ваше нативное приложение содержит несколько активностей. Это позволяет избежать гонок при вызовах start()
и stop()
из разных активностей.
// Create a LifecycleObserver class. class MyLifecycleObserver : DefaultLifecycleObserver { override fun onStart(owner: LifecycleOwner) { // App prepares to enter foreground. CastReceiverContext.getInstance().start() } override fun onStop(owner: LifecycleOwner) { // App has moved to the background or has terminated. CastReceiverContext.getInstance().stop() } } // Add the observer when your application is being created. class MyApplication : Application() { fun onCreate() { super.onCreate() // Initialize CastReceiverContext. CastReceiverContext.initInstance(this /* android.content.Context */) // Register LifecycleObserver ProcessLifecycleOwner.get().lifecycle.addObserver( MyLifecycleObserver()) } }
// Create a LifecycleObserver class. public class MyLifecycleObserver implements DefaultLifecycleObserver { @Override public void onStart(LifecycleOwner owner) { // App prepares to enter foreground. CastReceiverContext.getInstance().start(); } @Override public void onStop(LifecycleOwner owner) { // App has moved to the background or has terminated. CastReceiverContext.getInstance().stop(); } } // Add the observer when your application is being created. public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); // Initialize CastReceiverContext. CastReceiverContext.initInstance(this /* android.content.Context */); // Register LifecycleObserver ProcessLifecycleOwner.get().getLifecycle().addObserver( new MyLifecycleObserver()); } }
// In AndroidManifest.xml set MyApplication as the application class
<application
...
android:name=".MyApplication">
Подключение MediaSession к MediaManager
При создании MediaSession
вам также необходимо предоставить текущий токен MediaSession
для CastReceiverContext
, чтобы он знал, куда отправлять команды и получать состояние воспроизведения мультимедиа:
val mediaManager: MediaManager = receiverContext.getMediaManager() mediaManager.setSessionCompatToken(currentMediaSession.getSessionToken())
MediaManager mediaManager = receiverContext.getMediaManager(); mediaManager.setSessionCompatToken(currentMediaSession.getSessionToken());
При освобождении MediaSession
из-за неактивного воспроизведения необходимо установить нулевой токен в MediaManager
:
myPlayer.stop() mediaSession.release() mediaManager.setSessionCompatToken(null)
myPlayer.stop(); mediaSession.release(); mediaManager.setSessionCompatToken(null);
Если ваше приложение поддерживает воспроизведение медиаконтента в фоновом режиме, вместо вызова CastReceiverContext.stop()
при переходе приложения в фоновый режим следует вызывать его только тогда, когда приложение находится в фоновом режиме и больше не воспроизводит медиаконтент. Например:
class MyLifecycleObserver : DefaultLifecycleObserver { ... // App has moved to the background. override fun onPause(owner: LifecycleOwner) { mIsBackground = true myStopCastReceiverContextIfNeeded() } } // Stop playback on the player. private fun myStopPlayback() { myPlayer.stop() myStopCastReceiverContextIfNeeded() } // Stop the CastReceiverContext when both the player has // stopped and the app has moved to the background. private fun myStopCastReceiverContextIfNeeded() { if (mIsBackground && myPlayer.isStopped()) { CastReceiverContext.getInstance().stop() } }
public class MyLifecycleObserver implements DefaultLifecycleObserver { ... // App has moved to the background. @Override public void onPause(LifecycleOwner owner) { mIsBackground = true; myStopCastReceiverContextIfNeeded(); } } // Stop playback on the player. private void myStopPlayback() { myPlayer.stop(); myStopCastReceiverContextIfNeeded(); } // Stop the CastReceiverContext when both the player has // stopped and the app has moved to the background. private void myStopCastReceiverContextIfNeeded() { if (mIsBackground && myPlayer.isStopped()) { CastReceiverContext.getInstance().stop(); } }
Использование Exoplayer с Cast Connect
Если вы используете Exoplayer
, вы можете использовать MediaSessionConnector
для автоматического поддержания сеанса и всей связанной информации, включая состояние воспроизведения, вместо того, чтобы отслеживать изменения вручную.
MediaSessionConnector.MediaButtonEventHandler
можно использовать для обработки событий MediaButton путем вызова setMediaButtonEventHandler(MediaButtonEventHandler)
которые в противном случае обрабатываются MediaSessionCompat.Callback
по умолчанию.
Чтобы интегрировать MediaSessionConnector
в свое приложение, добавьте следующее в класс активности проигрывателя или в любое место, где вы управляете своим медиасеансом:
class PlayerActivity : Activity() { private var mMediaSession: MediaSessionCompat? = null private var mMediaSessionConnector: MediaSessionConnector? = null private var mMediaManager: MediaManager? = null override fun onCreate(savedInstanceState: Bundle?) { ... mMediaSession = MediaSessionCompat(this, LOG_TAG) mMediaSessionConnector = MediaSessionConnector(mMediaSession!!) ... } override fun onStart() { ... mMediaManager = receiverContext.getMediaManager() mMediaManager!!.setSessionCompatToken(currentMediaSession.getSessionToken()) mMediaSessionConnector!!.setPlayer(mExoPlayer) mMediaSessionConnector!!.setMediaMetadataProvider(mMediaMetadataProvider) mMediaSession!!.isActive = true ... } override fun onStop() { ... mMediaSessionConnector!!.setPlayer(null) mMediaSession!!.release() mMediaManager!!.setSessionCompatToken(null) ... } }
public class PlayerActivity extends Activity { private MediaSessionCompat mMediaSession; private MediaSessionConnector mMediaSessionConnector; private MediaManager mMediaManager; @Override protected void onCreate(Bundle savedInstanceState) { ... mMediaSession = new MediaSessionCompat(this, LOG_TAG); mMediaSessionConnector = new MediaSessionConnector(mMediaSession); ... } @Override protected void onStart() { ... mMediaManager = receiverContext.getMediaManager(); mMediaManager.setSessionCompatToken(currentMediaSession.getSessionToken()); mMediaSessionConnector.setPlayer(mExoPlayer); mMediaSessionConnector.setMediaMetadataProvider(mMediaMetadataProvider); mMediaSession.setActive(true); ... } @Override protected void onStop() { ... mMediaSessionConnector.setPlayer(null); mMediaSession.release(); mMediaManager.setSessionCompatToken(null); ... } }
Настройка приложения-отправителя
Включить поддержку Cast Connect
После обновления приложения-отправителя с поддержкой Cast Connect вы можете объявить о его готовности, установив флаг androidReceiverCompatible
в LaunchOptions
на значение true.
Требуется play-services-cast-framework
версии 19.0.0
или выше.
Флаг androidReceiverCompatible
устанавливается в LaunchOptions
(который является частью CastOptions
):
class CastOptionsProvider : OptionsProvider { override fun getCastOptions(context: Context?): CastOptions { val launchOptions: LaunchOptions = Builder() .setAndroidReceiverCompatible(true) .build() return CastOptions.Builder() .setLaunchOptions(launchOptions) ... .build() } }
public class CastOptionsProvider implements OptionsProvider { @Override public CastOptions getCastOptions(Context context) { LaunchOptions launchOptions = new LaunchOptions.Builder() .setAndroidReceiverCompatible(true) .build(); return new CastOptions.Builder() .setLaunchOptions(launchOptions) ... .build(); } }
Требуется google-cast-sdk
версии v4.4.8
или выше.
Флаг androidReceiverCompatible
устанавливается в GCKLaunchOptions
(который является частью GCKCastOptions
):
let options = GCKCastOptions(discoveryCriteria: GCKDiscoveryCriteria(applicationID: kReceiverAppID)) ... let launchOptions = GCKLaunchOptions() launchOptions.androidReceiverCompatible = true options.launchOptions = launchOptions GCKCastContext.setSharedInstanceWith(options)
Требуется браузер Chromium версии M87
или выше.
const context = cast.framework.CastContext.getInstance(); const castOptions = new cast.framework.CastOptions(); castOptions.receiverApplicationId = kReceiverAppID; castOptions.androidReceiverCompatible = true; context.setOptions(castOptions);
Настройка консоли разработчика Cast
Настройте приложение Android TV
Добавьте имя пакета вашего приложения Android TV в Cast Developer Console, чтобы связать его с вашим идентификатором приложения Cast.
Регистрация устройств разработчика
Зарегистрируйте серийный номер устройства Android TV, которое вы собираетесь использовать для разработки, в консоли разработчика Cast .
Без регистрации Cast Connect будет работать только с приложениями, установленными из Google Play Store из соображений безопасности.
Дополнительную информацию о регистрации устройства Cast или Android TV для разработки Cast см. на странице регистрации .
Загрузка медиа
Если вы уже реализовали поддержку глубоких ссылок в своем приложении Android TV, то вам следует настроить аналогичное определение в манифесте Android TV:
<activity android:name="com.example.activity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="https"/>
<data android:host="www.example.com"/>
<data android:pathPattern=".*"/>
</intent-filter>
</activity>
Загрузка по субъекту отправителя
На стороне отправителя вы можете передать глубокую ссылку, установив entity
в информации о медиа для запроса на загрузку:
val mediaToLoad = MediaInfo.Builder("some-id") .setEntity("https://example.com/watch/some-id") ... .build() val loadRequest = MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") ... .build() remoteMediaClient.load(loadRequest)
MediaInfo mediaToLoad = new MediaInfo.Builder("some-id") .setEntity("https://example.com/watch/some-id") ... .build(); MediaLoadRequestData loadRequest = new MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") ... .build(); remoteMediaClient.load(loadRequest);
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 в информации о мультимедиа или используете более подробные данные запроса на загрузку мультимедиа, вам необходимо добавить следующий предопределенный фильтр намерений в приложение 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);
Обработка запросов на загрузку
Чтобы обработать эти запросы на загрузку в вашей деятельности, вам необходимо обработать намерения в обратных вызовах жизненного цикла вашей деятельности:
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 поддерживает, необходимо правильно настроить поддерживаемые действия на каждой платформе и убедиться, что приложение-отправитель корректно отображает пользовательский интерфейс.
Изменение MediaStatus
Для поддержки расширенных функций, таких как треки, реклама, прямая трансляция и организация очередей, вашему приложению Android TV необходимо предоставить дополнительную информацию, которую невозможно получить через MediaSession
.
Для этого мы предоставляем класс MediaStatusModifier
. MediaStatusModifier
всегда будет работать с MediaSession
, который вы установили в CastReceiverContext
.
Чтобы создать и транслировать MediaStatus
:
val mediaManager: MediaManager = castReceiverContext.getMediaManager() val statusModifier: MediaStatusModifier = mediaManager.getMediaStatusModifier() statusModifier .setLiveSeekableRange(seekableRange) .setAdBreakStatus(adBreakStatus) .setCustomData(customData) mediaManager.broadcastMediaStatus()
MediaManager mediaManager = castReceiverContext.getMediaManager(); MediaStatusModifier statusModifier = mediaManager.getMediaStatusModifier(); statusModifier .setLiveSeekableRange(seekableRange) .setAdBreakStatus(adBreakStatus) .setCustomData(customData); mediaManager.broadcastMediaStatus();
Наша клиентская библиотека получит базовый MediaStatus
из MediaSession
, ваше приложение Android TV может указать дополнительный статус и переопределить статус с помощью модификатора MediaStatus
.
Некоторые состояния и метаданные можно задать как в MediaSession
, так и MediaStatusModifier
. Мы настоятельно рекомендуем задавать их только в MediaSession
. Вы по-прежнему можете использовать модификатор для переопределения состояний в MediaSession
— это не рекомендуется, поскольку статус в модификаторе всегда имеет более высокий приоритет, чем значения, предоставляемые MediaSession
.
Перехват MediaStatus перед отправкой
Как и в случае с Web Receiver SDK, если вам нужно внести последние штрихи перед отправкой, вы можете указать MediaStatusInterceptor
для обработки отправляемого MediaStatus
. Мы передаем MediaStatusWriter
для обработки MediaStatus
перед отправкой.
mediaManager.setMediaStatusInterceptor(object : MediaStatusInterceptor { override fun intercept(mediaStatusWriter: MediaStatusWriter) { // Perform customization. mediaStatusWriter.setCustomData(JSONObject("{data: \"my Hello\"}")) } })
mediaManager.setMediaStatusInterceptor(new MediaStatusInterceptor() { @Override public void intercept(MediaStatusWriter mediaStatusWriter) { // Perform customization. mediaStatusWriter.setCustomData(new JSONObject("{data: \"my Hello\"}")); } });
Обработка учетных данных пользователей
Ваше приложение Android TV может разрешать запуск сеанса или присоединение к нему только определённым пользователям. Например, отправителю можно разрешить запуск сеанса или присоединение только в следующих случаях:
- Приложение-отправитель авторизуется в той же учетной записи и профиле, что и приложение ATV.
- Приложение-отправитель авторизовано в той же учетной записи, что и приложение ATV, но с другим профилем.
Если ваше приложение поддерживает многопользовательскую или анонимную работу, вы можете разрешить любому пользователю присоединиться к сеансу ATV. Если пользователь предоставляет учётные данные, вашему приложению ATV необходимо обрабатывать эти данные, чтобы можно было корректно отслеживать его прогресс и другие пользовательские данные.
Когда приложение-отправитель запускает или присоединяется к приложению Android TV, оно должно предоставить учетные данные, указывающие, кто присоединяется к сеансу.
Прежде чем отправитель запустит и подключится к вашему приложению Android TV, вы можете настроить проверку запуска, чтобы проверить, разрешены ли учётные данные отправителя. Если нет, Cast Connect SDK автоматически запустит ваш веб-приёмник.
Данные учетных данных для запуска приложения-отправителя
На стороне отправителя вы можете указать CredentialsData
, чтобы указать, кто присоединяется к сеансу.
credentials
— это строка, которую можно задать пользователем, если ваше приложение ATV её распознаёт. Параметр credentialsType
определяет, с какой платформы поступают CredentialsData
, или может быть задан пользователем. По умолчанию он соответствует платформе, с которой отправляются данные.
Данные CredentialsData
передаются в приложение Android TV только во время запуска или подключения. Если вы установите их снова при подключении, они не будут переданы в приложение Android TV. Если отправитель переключит профиль во время подключения, вы можете либо остаться в сеансе, либо вызвать SessionManager.endCurrentCastSession(boolean stopCasting)
если считаете, что новый профиль несовместим с сеансом.
CredentialsData
для каждого отправителя можно получить с помощью getSenders
в CastReceiverContext
для получения SenderInfo
, getCastLaunchRequest()
для получения CastLaunchRequest
, а затем getCredentialsData()
.
Требуется play-services-cast-framework
версии 19.0.0
или выше.
CastContext.getSharedInstance().setLaunchCredentialsData( CredentialsData.Builder() .setCredentials("{\"userId\": \"abc\"}") .build() )
CastContext.getSharedInstance().setLaunchCredentialsData( new CredentialsData.Builder() .setCredentials("{\"userId\": \"abc\"}") .build());
Требуется google-cast-sdk
версии v4.8.3
или выше.
Может быть вызван в любое время после установки параметров: GCKCastContext.setSharedInstanceWith(options)
.
GCKCastContext.sharedInstance().setLaunch( GCKCredentialsData(credentials: "{\"userId\": \"abc\"}")
Требуется браузер Chromium версии M87
или выше.
Может быть вызван в любое время после установки параметров: cast.framework.CastContext.getInstance().setOptions(options);
.
let credentialsData = new chrome.cast.CredentialsData("{\"userId\": \"abc\"}"); cast.framework.CastContext.getInstance().setLaunchCredentialsData(credentialsData);
Реализация проверки запроса на запуск ATV
Данные CredentialsData
передаются в приложение Android TV при попытке запуска или подключения отправителя. Вы можете реализовать LaunchRequestChecker
, чтобы разрешить или отклонить этот запрос.
Если запрос отклонён, веб-приёмник загружается вместо того, чтобы запускаться в приложении ATV. Если ваше приложение ATV не может обработать запрос пользователя на запуск или присоединение, запрос следует отклонить. Примерами могут быть ситуация, когда в приложении ATV находится другой пользователь, а не тот, кто запрашивает, и ваше приложение не может обработать переключение учётных данных, или в приложении ATV в данный момент нет пользователя, вошедшего в приложение.
Если запрос разрешён, приложение ATV запускается. Вы можете настроить это поведение в зависимости от того, поддерживает ли ваше приложение отправку запросов на загрузку, когда пользователь не вошёл в приложение ATV, или в случае несоответствия пользователя. Это поведение полностью настраивается в LaunchRequestChecker
.
Создайте класс, реализующий интерфейс CastReceiverOptions.LaunchRequestChecker
:
class MyLaunchRequestChecker : LaunchRequestChecker { override fun checkLaunchRequestSupported(launchRequest: CastLaunchRequest): Task{ return Tasks.call { myCheckLaunchRequest( launchRequest ) } } } private fun myCheckLaunchRequest(launchRequest: CastLaunchRequest): Boolean { val credentialsData = launchRequest.getCredentialsData() ?: return false // or true if you allow anonymous users to join. // The request comes from a mobile device, e.g. checking user match. return if (credentialsData.credentialsType == CredentialsData.CREDENTIALS_TYPE_ANDROID) { myCheckMobileCredentialsAllowed(credentialsData.getCredentials()) } else false // Unrecognized credentials type. }
public class MyLaunchRequestChecker implements CastReceiverOptions.LaunchRequestChecker { @Override public 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());