Эта страница содержит фрагменты кода и описания функций, доступных для настройки приложения Android TV Receiver.
Настройка библиотек
Чтобы сделать API-интерфейсы Cast Connect доступными для вашего приложения Android TV:
- Откройте файл
build.gradle
в каталоге модуля приложения. - Убедитесь, что
google()
включен в списокrepositories
.repositories { google() }
- В зависимости от типа целевого устройства для вашего приложения добавьте последние версии библиотек в свои зависимости:
- Для приложения Android-приемника:
dependencies { implementation 'com.google.android.gms:play-services-cast-tv:21.1.0' implementation 'com.google.android.gms:play-services-cast:21.5.0' }
- Для приложения Android Sender:
dependencies { implementation 'com.google.android.gms:play-services-cast:21.1.0' implementation 'com.google.android.gms:play-services-cast-framework:21.5.0' }
- Для приложения Android-приемника:
- Сохраните изменения и нажмите
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
Новые версии Сервисов Google Play требуют, чтобы приложение было обновлено для использования пространства имен androidx
. Следуйте инструкциям по переходу на AndroidX .
Приложение Android TV: необходимые условия
Чтобы поддерживать Cast Connect в приложении Android TV, вам необходимо создавать и поддерживать события из медиа-сеанса. Данные, предоставленные вашим сеансом мультимедиа, предоставляют основную информацию — например, положение, состояние воспроизведения и т. д. — для вашего статуса мультимедиа. Ваш медиа-сеанс также используется библиотекой Cast Connect для сигнализации о получении определенных сообщений от отправителя, например паузы.
Дополнительную информацию о медиа-сеансе и о том, как его инициализировать, см. в руководстве по работе с медиа-сеансом .
Жизненный цикл медиа-сессии
Ваше приложение должно создать медиа-сеанс при запуске воспроизведения и освободить его, когда им больше нельзя будет управлять. Например, если ваше приложение является видеоприложением, вам следует завершить сеанс, когда пользователь завершает действие воспроизведения — либо выбрав «Назад» для просмотра другого контента, либо переведя приложение в фоновый режим. Если ваше приложение является музыкальным, вам следует выпустить его, когда ваше приложение больше не воспроизводит медиафайлы.
Обновление статуса сеанса
Данные в вашем медиа-сеансе должны обновляться в соответствии со статусом вашего проигрывателя. Например, когда воспроизведение приостановлено, вам следует обновить состояние воспроизведения, а также поддерживаемые действия. В следующих таблицах перечислены состояния, за обновление которых вы несете ответственность.
МедиаМетаданныеКомпат
Поле метаданных | Описание |
---|---|
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
Требуемый метод | Описание |
---|---|
УстановитьДействия() | Устанавливает поддерживаемые мультимедийные команды. |
setState() | Установите состояние воспроизведения и текущую позицию. |
Медиасессионкомпат
Требуемый метод | Описание |
---|---|
установитьRepeatMode() | Устанавливает режим повтора. |
установитьShuffleMode() | Устанавливает режим случайного воспроизведения. |
установитьМетаданные() | Устанавливает метаданные мультимедиа. |
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
при запуске приложения TV. Объект CastReceiverContext
необходим для взаимодействия с Cast во время работы приложения TV. Этот объект позволяет вашему ТВ-приложению принимать сообщения Cast Media, поступающие от любых подключенных отправителей.
Настройка Android-телевизора
Добавление фильтра намерений запуска
Добавьте новый фильтр намерений к действию, которое вы хотите обрабатывать намерение запуска из приложения-отправителя:
<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, чтобы связать его с вашим идентификатором приложения 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 отправителя
Возможно, ваше приложение веб-приемника и приложение 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);
Если приложение веб-приемника запущено, оно использует 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);
Обработка запросов на загрузку
В вашей деятельности для обработки этих запросов на загрузку вам необходимо обрабатывать намерения в обратных вызовах жизненного цикла вашей активности:
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()
действия или приложения).
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 не поддерживает изменение скорости воспроизведения, в то время как ваше приложение веб-приемника поддерживает, вам следует правильно настроить поддерживаемые действия на каждой платформе и убедиться, что ваше приложение-отправитель правильно отображает пользовательский интерфейс.
Изменение медиастатуса
Для поддержки расширенных функций, таких как треки, реклама, прямая трансляция и очередь, вашему приложению Android TV необходимо предоставить дополнительную информацию, которую невозможно получить с помощью MediaSession
.
Для этого мы предоставляем класс MediaStatusModifier
. MediaStatusModifier
всегда будет работать с MediaSession
, который вы установили в CastReceiverContext
.
Чтобы создать и транслировать MediaStatus
:
val mediaManager: MediaManager = castReceiverContext.getMediaManager() val statusModifier: MediaStatusModifier = mediaManager.getMediaStatusModifier() statusModifier .setLiveSeekableRange(seekableRange) .setAdBreakStatus(adBreakStatus) .setCustomData(customData) mediaManager.broadcastMediaStatus()
MediaManager mediaManager = castReceiverContext.getMediaManager(); MediaStatusModifier statusModifier = mediaManager.getMediaStatusModifier(); statusModifier .setLiveSeekableRange(seekableRange) .setAdBreakStatus(adBreakStatus) .setCustomData(customData); mediaManager.broadcastMediaStatus();
Наша клиентская библиотека получит базовый MediaStatus
из MediaSession
, ваше приложение Android TV может указать дополнительный статус и переопределить статус с помощью модификатора MediaStatus
.
Некоторые состояния и метаданные можно задать как в MediaSession
, так и MediaStatusModifier
. Мы настоятельно рекомендуем устанавливать их только в MediaSession
. Вы по-прежнему можете использовать модификатор для переопределения состояний в MediaSession
— это не рекомендуется, поскольку статус в модификаторе всегда имеет более высокий приоритет, чем значения, предоставляемые MediaSession
.
Перехват MediaStatus перед отправкой
Как и в случае с SDK веб-приемника, если вы хотите внести некоторые последние штрихи перед отправкой, вы можете указать 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
— приложение веб-приемника.
Отправка и получение пользовательских сообщений
Протокол 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());