На этой странице содержатся фрагменты кода и описания функций, доступных для настройки приложения 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
Новые версии Google Play Services требуют, чтобы приложение было обновлено для использования пространства имен androidx
. Следуйте инструкциям по переходу на AndroidX .
Приложение Android TV — предварительные условия
Для поддержки Cast Connect в вашем приложении Android TV необходимо создавать и поддерживать события из сеанса мультимедиа. Данные, предоставляемые вашим сеансом мультимедиа, содержат основную информацию — например, положение, состояние воспроизведения и т. д. — о состоянии вашего мультимедиа. Ваш сеанс мультимедиа также используется библиотекой Cast Connect для подачи сигнала о получении определенных сообщений от отправителя, например паузы.
Дополнительную информацию о медиа-сеансе и о том, как инициализировать медиа-сеанс, см. в руководстве по работе с медиа-сеансом .
Жизненный цикл медиа-сеанса
Ваше приложение должно создавать сеанс мультимедиа при запуске воспроизведения и завершать его, когда его больше нельзя контролировать. Например, если ваше приложение — это видеоприложение, вы должны завершать сеанс, когда пользователь выходит из процесса воспроизведения — либо выбрав «назад», чтобы просмотреть другой контент, либо переведя приложение в фоновый режим. Если ваше приложение — это музыкальное приложение, вы должны завершать сеанс, когда ваше приложение больше не воспроизводит медиа.
Обновление статуса сеанса
Данные в вашем сеансе мультимедиа должны обновляться в соответствии со статусом вашего проигрывателя. Например, когда воспроизведение приостановлено, вы должны обновить состояние воспроизведения, а также поддерживаемые действия. В следующих таблицах перечислены состояния, за поддержание которых в актуальном состоянии вы несете ответственность.
MediaMetadataCompat
Поле метаданных | Описание |
---|---|
METADATA_KEY_TITLE (обязательно) | Название СМИ. |
METADATA_KEY_DISPLAY_SUBTITLE | Подзаголовок. |
METADATA_KEY_DISPLAY_ICON_URI | URL-адрес значка. |
METADATA_KEY_DURATION (обязательно) | Продолжительность медиа. |
METADATA_KEY_MEDIA_URI | Идентификатор контента. |
METADATA_KEY_ARTIST | Художник. |
МЕТАДАННЫЕ_КЛЮЧ_АЛЬБОМ | Альбом. |
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.Обратный вызов
Действия | Описание |
---|---|
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. Этот объект позволяет вашему приложению TV принимать сообщения Cast media, поступающие от любых подключенных отправителей.
Настройка 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()
(рекомендуется, чтобы он был в методе Activity или Application 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
Некоторые команды 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
., чтобы разрешить или отклонить этот запрос.
Если запрос отклонен, загружается Web Receiver вместо того, чтобы запускаться в приложении 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());
На этой странице содержатся фрагменты кода и описания функций, доступных для настройки приложения 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
Новые версии Google Play Services требуют, чтобы приложение было обновлено для использования пространства имен androidx
. Следуйте инструкциям по переходу на AndroidX .
Приложение Android TV — предварительные условия
Для поддержки Cast Connect в вашем приложении Android TV необходимо создавать и поддерживать события из сеанса мультимедиа. Данные, предоставляемые вашим сеансом мультимедиа, содержат основную информацию — например, положение, состояние воспроизведения и т. д. — о состоянии вашего мультимедиа. Ваш сеанс мультимедиа также используется библиотекой Cast Connect для подачи сигнала о получении определенных сообщений от отправителя, например паузы.
Дополнительную информацию о медиа-сеансе и о том, как инициализировать медиа-сеанс, см. в руководстве по работе с медиа-сеансом .
Жизненный цикл медиа-сеанса
Ваше приложение должно создавать сеанс мультимедиа при запуске воспроизведения и завершать его, когда его больше нельзя контролировать. Например, если ваше приложение — это видеоприложение, вы должны завершать сеанс, когда пользователь выходит из процесса воспроизведения — либо выбрав «назад», чтобы просмотреть другой контент, либо переведя приложение в фоновый режим. Если ваше приложение — это музыкальное приложение, вы должны завершать сеанс, когда ваше приложение больше не воспроизводит медиа.
Обновление статуса сеанса
Данные в вашем сеансе мультимедиа должны обновляться в соответствии со статусом вашего проигрывателя. Например, когда воспроизведение приостановлено, вы должны обновить состояние воспроизведения, а также поддерживаемые действия. В следующих таблицах перечислены состояния, за поддержание которых в актуальном состоянии вы несете ответственность.
MediaMetadataCompat
Поле метаданных | Описание |
---|---|
METADATA_KEY_TITLE (обязательно) | Название СМИ. |
METADATA_KEY_DISPLAY_SUBTITLE | Подзаголовок. |
METADATA_KEY_DISPLAY_ICON_URI | URL-адрес значка. |
METADATA_KEY_DURATION (обязательно) | Продолжительность медиа. |
METADATA_KEY_MEDIA_URI | Идентификатор контента. |
METADATA_KEY_ARTIST | Художник. |
МЕТАДАННЫЕ_КЛЮЧ_АЛЬБОМ | Альбом. |
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.Обратный вызов
Действия | Описание |
---|---|
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. Этот объект позволяет вашему приложению TV принимать сообщения Cast media, поступающие от любых подключенных отправителей.
Настройка 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()
(рекомендуется, чтобы он был в методе Activity или Application 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
Некоторые команды 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. Если ваш отправитель переключает профиль во время подключения, вы можете либо остаться в сеансе, либо Call 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
запускает приложение для веб -приемника.
Отправка и получение пользовательских сообщений
Протокол актеров позволяет отправлять индивидуальные строковые сообщения между отправителями и приложением вашего приемника. Вы должны зарегистрировать пространство имен (канал) для отправки сообщений, прежде чем инициализации вашего 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());