1. Обзор

В этом практическом занятии вы узнаете, как модифицировать существующее приложение для Android TV, чтобы оно поддерживало трансляцию и обмен данными с вашими существующими приложениями-отправителями Cast.
Что такое Google Cast и Cast Connect?
Google Cast позволяет пользователям транслировать контент с мобильного устройства на телевизор. Типичная сессия Google Cast состоит из двух компонентов — приложения -отправителя и приложения- получателя . Приложения-отправители, такие как мобильные приложения или веб-сайты, например Youtube.com, инициируют и управляют воспроизведением приложения-получателя Cast. Приложения-получатели Cast — это приложения HTML5, работающие на устройствах Chromecast и Android TV.
Практически все состояние сессии Cast хранится в принимающем приложении. Когда состояние обновляется, например, при загрузке нового медиафайла, статус медиафайла передается всем отправителям. Эти сообщения содержат текущее состояние сессии Cast. Приложения-отправители используют этот статус медиафайла для отображения информации о воспроизведении в своем пользовательском интерфейсе.
Cast Connect использует эту инфраструктуру, при этом ваше приложение для Android TV выступает в роли приемника. Библиотека Cast Connect позволяет вашему приложению для Android TV получать сообщения и передавать информацию о состоянии медиафайлов, как если бы это было приложение-приемник Cast.
Что мы будем строить?
После завершения этого практического занятия вы сможете использовать приложения-отправители Cast для трансляции видео на приложение Android TV. Приложение Android TV также может взаимодействовать с приложениями-отправителями через протокол Cast.
Что вы узнаете
- Как добавить библиотеку Cast Connect в пример приложения для Apple TV.
- Как подключить передатчик Cast и запустить приложение ATV.
- Как начать воспроизведение мультимедиа в приложении ATV из приложения-передатчика Cast.
- Как передавать информацию о состоянии медиафайлов из приложения Apple TV в приложения-отправители Cast.
Что вам понадобится
- Последняя версия Android SDK .
- Последняя версия Android Studio . В частности,
Chipmunk | 2021.2.1или более поздние версии. - Устройство Android TV с включенными параметрами разработчика и отладкой по USB .
- Телефон на Android с включенными параметрами разработчика и отладкой по USB .
- USB-кабель для передачи данных, позволяющий подключить ваш Android-телефон и устройства Android TV к компьютеру для разработки.
- Базовые знания разработки Android-приложений с использованием Kotlin.
2. Получите пример кода.
Вы можете загрузить весь примерный код на свой компьютер...
и распакуйте загруженный zip-файл.
3. Запустите демонстрационное приложение.
Для начала давайте посмотрим, как выглядит готовый пример приложения. Приложение для Android TV использует пользовательский интерфейс Leanback и простой видеоплеер. Пользователь может выбрать видео из списка, которое затем воспроизводится на телевизоре после выбора. С помощью сопутствующего мобильного приложения для отправки пользователь также может транслировать видео в приложение для Android TV.

Зарегистрируйте устройства разработчика
Для включения возможностей Cast Connect в разработке приложений необходимо зарегистрировать серийный номер Google Cast на вашем устройстве Android TV в консоли разработчика Cast . Серийный номер можно найти в разделе «Настройки» > «Настройки устройства» > «Google Cast» > «Серийный номер» на вашем Android TV. Обратите внимание, что это отличается от серийного номера вашего физического устройства и должен быть получен описанным выше способом.

Без регистрации Cast Connect будет работать только с приложениями, установленными из Google Play Store, по соображениям безопасности. Через 15 минут после начала процесса регистрации перезагрузите устройство.
Установите приложение для отправки сообщений на Android.
Для тестирования отправки запросов с мобильного устройства мы предоставили простое приложение для отправки видео под названием Cast Videos в виде файла mobile-sender-0629.apk который находится в архиве с исходным кодом. Для установки APK-файла мы будем использовать ADB. Если у вас уже установлена другая версия Cast Videos, пожалуйста, удалите её из всех профилей на устройстве, прежде чем продолжить.
- Включите параметры разработчика и отладку по USB на своем телефоне Android.
- Подключите USB-кабель для передачи данных, чтобы соединить ваш Android-телефон с компьютером для разработки.
- Установите файл
mobile-sender-0629.apkна свой Android-телефон.

- Приложение для отправки видео Cast Videos можно найти на вашем телефоне Android.


Установите приложение Android TV.
В следующих инструкциях описано, как открыть и запустить готовое демонстрационное приложение в Android Studio:
- Выберите пункт «Импорт проекта» на экране приветствия или выберите пункт меню «Файл» > «Создать» > «Импорт проекта...» .
- Выберите
Перейдите в папку с примерами кода и запустите команду app-done, затем нажмите `OK`. - Нажмите Файл >
Синхронизация проекта с файлами Gradle . - Включите параметры разработчика и отладку по USB на вашем устройстве Android TV.
- Подключитесь к своему устройству Android TV через ADB, и устройство должно отобразиться в Android Studio.

- Нажмите
После нажатия кнопки «Запуск» через несколько секунд должно появиться приложение для Apple TV под названием Cast Connect Codelab .
Давайте поиграем в Cast Connect с помощью приложения для Apple TV.
- Перейдите на главный экран Android TV.
- Откройте приложение Cast Videos на своем телефоне Android. Нажмите на кнопку Cast.
и выберите ваше устройство ATV. - Приложение Cast Connect Codelab ATV запустится на вашем квадрокоптере, и кнопка Cast на вашем устройстве покажет, что оно подключено.
. - Выберите видео в приложении ATV, и оно начнет воспроизводиться на вашем квадроцикле.
- На вашем мобильном телефоне в нижней части приложения отправителя теперь отображается мини-контроллер. Вы можете использовать кнопку воспроизведения/паузы для управления воспроизведением.
- Выберите видео на мобильном телефоне и воспроизведите его. Видео начнет воспроизводиться на вашем Apple TV, а на экране вашего мобильного устройства отобразится расширенное меню управления.
- Заблокируйте телефон, и после разблокировки на экране блокировки должно появиться уведомление с возможностью управления воспроизведением мультимедиа или остановки трансляции.

4. Подготовьте стартовый проект.
Теперь, когда мы проверили интеграцию Cast Connect в готовое приложение, нам нужно добавить поддержку Cast Connect в загруженное вами стартовое приложение. Теперь вы готовы развивать стартовый проект, используя Android Studio:
- Выберите пункт «Импорт проекта» на экране приветствия или выберите пункт меню «Файл» > «Создать» > «Импорт проекта...» .
- Выберите
Перейдите в каталог app-startиз папки с примерами кода и нажмите OK. - Нажмите Файл >
Синхронизация проекта с файлами Gradle . - Выберите устройство ATV и нажмите
Нажмите кнопку «Запустить» , чтобы запустить приложение и изучить пользовательский интерфейс. 

дизайн приложения
Приложение предоставляет пользователю список видеороликов для просмотра. Пользователи могут выбрать видео для воспроизведения на Android TV. Приложение состоит из двух основных разделов: MainActivity и PlaybackActivity .
Главная активность
Эта активность содержит фрагмент ( MainFragment ). Список видеороликов и связанные с ними метаданные настраиваются в классе MovieList , а для создания списка объектов Movie вызывается метод setupMovies() .
Объект Movie представляет собой видеообъект, содержащий заголовок, описание, миниатюры изображений и URL-адрес видео. Каждый объект Movie связан с CardPresenter для отображения миниатюры видео с заголовком и студией и передается в ArrayObjectAdapter .
При выборе элемента соответствующий объект Movie передается в PlaybackActivity .
PlaybackActivity
Эта активность содержит фрагмент ( PlaybackVideoFragment ), в котором размещен VideoView с ExoPlayer , элементы управления воспроизведением и текстовое поле для отображения описания выбранного видео, а также возможность воспроизведения видео на Android TV. Пользователь может использовать пульт дистанционного управления для воспроизведения/паузы или перемотки видео.
Предварительные условия для Cast Connect
Cast Connect использует новые версии Google Play Services, для работы которых требуется обновление вашего приложения для Apple TV до использования пространства имен AndroidX .
Для поддержки Cast Connect в вашем приложении для Android TV необходимо создавать и поддерживать события из медиасессии . Библиотека Cast Connect генерирует статус медиафайла на основе статуса медиасессии. Ваша медиасессия также используется библиотекой Cast Connect для сигнализации о получении определенных сообщений от отправителя, например, о паузе.
5. Настройка поддержки Cast
Зависимости
Обновите файл build.gradle приложения, добавив необходимые зависимости библиотек:
dependencies {
....
// Cast Connect libraries
implementation 'com.google.android.gms:play-services-cast-tv:20.0.0'
implementation 'com.google.android.gms:play-services-cast:21.1.0'
}
Синхронизируйте проект, чтобы убедиться, что сборка проекта проходит без ошибок.
Инициализация
CastReceiverContext — это объект-одиночка, координирующий все взаимодействия Cast. Для предоставления параметров CastReceiverOptions при инициализации CastReceiverContext необходимо реализовать интерфейс ReceiverOptionsProvider .
Создайте файл CastReceiverOptionsProvider.kt и добавьте в проект следующий класс:
package com.google.sample.cast.castconnect
import android.content.Context
import com.google.android.gms.cast.tv.ReceiverOptionsProvider
import com.google.android.gms.cast.tv.CastReceiverOptions
class CastReceiverOptionsProvider : ReceiverOptionsProvider {
override fun getOptions(context: Context): CastReceiverOptions {
return CastReceiverOptions.Builder(context)
.setStatusText("Cast Connect Codelab")
.build()
}
}
Затем укажите поставщика параметров получателя в теге <application> файла AndroidManifest.xml приложения:
<application>
...
<meta-data
android:name="com.google.android.gms.cast.tv.RECEIVER_OPTIONS_PROVIDER_CLASS_NAME"
android:value="com.google.sample.cast.castconnect.CastReceiverOptionsProvider" />
</application>
Чтобы подключиться к приложению Apple TV с помощью Cast, выберите активность, которую хотите запустить. В этом практическом занятии мы будем запускать MainActivity приложения при запуске сессии Cast. В файле AndroidManifest.xml добавьте фильтр намерения запуска в MainActivity .
<activity android:name=".MainActivity">
...
<intent-filter>
<action android:name="com.google.android.gms.cast.tv.action.LAUNCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
Жизненный цикл контекста получателя трансляции
CastReceiverContext следует запускать при запуске приложения и CastReceiverContext при переводе приложения в фоновый режим. Мы рекомендуем использовать LifecycleObserver из библиотеки androidx.lifecycle для управления вызовами CastReceiverContext.start() и CastReceiverContext.stop()
Откройте файл MyApplication.kt , инициализируйте контекст приведения типов, вызвав initInstance() в методе onCreate приложения. В классе AppLifeCycleObserver вызовите метод start() CastReceiverContext при возобновлении работы приложения и метод stop() при приостановке работы приложения:
package com.google.sample.cast.castconnect
import com.google.android.gms.cast.tv.CastReceiverContext
...
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
CastReceiverContext.initInstance(this)
ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifecycleObserver())
}
class AppLifecycleObserver : DefaultLifecycleObserver {
override fun onResume(owner: LifecycleOwner) {
Log.d(LOG_TAG, "onResume")
CastReceiverContext.getInstance().start()
}
override fun onPause(owner: LifecycleOwner) {
Log.d(LOG_TAG, "onPause")
CastReceiverContext.getInstance().stop()
}
}
}
Подключение MediaSession к MediaManager
MediaManager — это свойство синглтона CastReceiverContext , оно управляет состоянием медиафайлов, обрабатывает намерение загрузки, преобразует сообщения пространства имен медиафайлов от отправителей в команды для работы с медиафайлами и отправляет состояние медиафайлов обратно отправителям.
При создании MediaSession также необходимо передать текущий токен MediaSession в MediaManager , чтобы он знал, куда отправлять команды и получать состояние воспроизведения мультимедиа. В файле PlaybackVideoFragment.kt убедитесь, что MediaSession инициализирован, прежде чем устанавливать токен для MediaManager .
import com.google.android.gms.cast.tv.CastReceiverContext
import com.google.android.gms.cast.tv.media.MediaManager
...
class PlaybackVideoFragment : VideoSupportFragment() {
private var castReceiverContext: CastReceiverContext? = null
...
private fun initializePlayer() {
if (mPlayer == null) {
...
mMediaSession = MediaSessionCompat(getContext(), LOG_TAG)
...
castReceiverContext = CastReceiverContext.getInstance()
if (castReceiverContext != null) {
val mediaManager: MediaManager = castReceiverContext!!.getMediaManager()
mediaManager.setSessionCompatToken(mMediaSession!!.getSessionToken())
}
}
}
}
При освобождении MediaSession из-за неактивного воспроизведения следует установить нулевой токен в MediaManager :
private fun releasePlayer() {
mMediaSession?.release()
castReceiverContext?.mediaManager?.setSessionCompatToken(null)
...
}
Давайте запустим тестовое приложение.
Нажмите
Нажмите кнопку «Запустить», чтобы развернуть приложение на вашем устройстве ATV, закройте приложение и вернитесь на главный экран ATV. На устройстве-отправителе нажмите кнопку «Трансляция».
и выберите ваше устройство ATV. Вы увидите, что приложение ATV запущено на устройстве ATV, и кнопка Cast находится в состоянии «подключено».
6. Загрузка медиафайлов
Команда загрузки отправляется через Intent с именем пакета, определенным вами в консоли разработчика. Вам необходимо добавить следующий предопределенный фильтр Intent в ваше приложение для Android TV, чтобы указать целевую активность, которая получит этот Intent. В файле AndroidManifest.xml добавьте фильтр Intent загрузки для PlayerActivity :
<activity android:name="com.google.sample.cast.castconnect.PlaybackActivity"
android:launchMode="singleTask"
android:exported="true">
<intent-filter>
<action android:name="com.google.android.gms.cast.tv.action.LOAD"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
Обработка запросов на загрузку на Android TV
Теперь, когда активность настроена на получение этого намерения, содержащего запрос на загрузку, нам нужно будет его обработать.
Приложение вызывает приватный метод processIntent при запуске активности. Этот метод содержит логику обработки входящих интентов. Для обработки запроса на загрузку мы изменим этот метод и отправим интент на дальнейшую обработку, вызвав метод onNewIntent экземпляра MediaManager . Если MediaManager обнаружит, что интент является запросом на загрузку, он извлечет объект MediaLoadRequestData из интента и вызовет MediaLoadCommandCallback.onLoad() . Измените метод processIntent в файле PlaybackVideoFragment.kt , чтобы он обрабатывал интент, содержащий запрос на загрузку:
fun processIntent(intent: Intent?) {
val mediaManager: MediaManager = CastReceiverContext.getInstance().getMediaManager()
// Pass intent to Cast SDK
if (mediaManager.onNewIntent(intent)) {
return
}
// Clears all overrides in the modifier.
mediaManager.getMediaStatusModifier().clear()
// If the SDK doesn't recognize the intent, handle the intent with your own logic.
...
}
Далее мы расширим абстрактный класс MediaLoadCommandCallback , который переопределит метод onLoad() , вызываемый классом MediaManager . Этот метод получает данные запроса на загрузку и преобразует их в объект Movie . После преобразования фильм воспроизводится локальным проигрывателем. Затем MediaManager обновляется с помощью объекта MediaLoadRequest и передает MediaStatus подключенным отправителям. Создайте вложенный приватный класс с именем MyMediaLoadCommandCallback в файле PlaybackVideoFragment.kt :
import com.google.android.gms.cast.MediaLoadRequestData
import com.google.android.gms.cast.MediaInfo
import com.google.android.gms.cast.MediaMetadata
import com.google.android.gms.cast.MediaError
import com.google.android.gms.cast.tv.media.MediaException
import com.google.android.gms.cast.tv.media.MediaCommandCallback
import com.google.android.gms.cast.tv.media.QueueUpdateRequestData
import com.google.android.gms.cast.tv.media.MediaLoadCommandCallback
import com.google.android.gms.tasks.Task
import com.google.android.gms.tasks.Tasks
import android.widget.Toast
...
private inner class MyMediaLoadCommandCallback : MediaLoadCommandCallback() {
override fun onLoad(
senderId: String?, mediaLoadRequestData: MediaLoadRequestData): Task<MediaLoadRequestData> {
Toast.makeText(activity, "onLoad()", Toast.LENGTH_SHORT).show()
return if (mediaLoadRequestData == null) {
// Throw MediaException to indicate load failure.
Tasks.forException(MediaException(
MediaError.Builder()
.setDetailedErrorCode(MediaError.DetailedErrorCode.LOAD_FAILED)
.setReason(MediaError.ERROR_REASON_INVALID_REQUEST)
.build()))
} else Tasks.call {
play(convertLoadRequestToMovie(mediaLoadRequestData)!!)
// Update media metadata and state
val mediaManager = castReceiverContext!!.mediaManager
mediaManager.setDataFromLoad(mediaLoadRequestData)
mediaLoadRequestData
}
}
}
private fun convertLoadRequestToMovie(mediaLoadRequestData: MediaLoadRequestData?): Movie? {
if (mediaLoadRequestData == null) {
return null
}
val mediaInfo: MediaInfo = mediaLoadRequestData.getMediaInfo() ?: return null
var videoUrl: String = mediaInfo.getContentId()
if (mediaInfo.getContentUrl() != null) {
videoUrl = mediaInfo.getContentUrl()
}
val metadata: MediaMetadata = mediaInfo.getMetadata()
val movie = Movie()
movie.videoUrl = videoUrl
movie.title = metadata?.getString(MediaMetadata.KEY_TITLE)
movie.description = metadata?.getString(MediaMetadata.KEY_SUBTITLE)
if(metadata?.hasImages() == true) {
movie.cardImageUrl = metadata.images[0].url.toString()
}
return movie
}
Теперь, когда функция обратного вызова определена, нам нужно зарегистрировать её в MediaManager . Обратный вызов должен быть зарегистрирован до вызова MediaManager.onNewIntent() . Добавьте setMediaLoadCommandCallback при инициализации плеера:
private fun initializePlayer() {
if (mPlayer == null) {
...
mMediaSession = MediaSessionCompat(getContext(), LOG_TAG)
...
castReceiverContext = CastReceiverContext.getInstance()
if (castReceiverContext != null) {
val mediaManager: MediaManager = castReceiverContext.getMediaManager()
mediaManager.setSessionCompatToken(mMediaSession.getSessionToken())
mediaManager.setMediaLoadCommandCallback(MyMediaLoadCommandCallback())
}
}
}
Давайте запустим тестовое приложение.
Нажмите
Нажмите кнопку «Запустить », чтобы развернуть приложение на вашем устройстве Apple TV. На устройстве отправителя нажмите кнопку «Трансляция».
Выберите ваше устройство ATV. Приложение ATV запустится на устройстве ATV. Выберите видео на мобильном устройстве, и видео начнет воспроизводиться на ATV. Проверьте, получаете ли вы уведомление на телефоне с элементами управления воспроизведением. Попробуйте использовать такие элементы управления, как пауза; воспроизведение видео на устройстве ATV должно быть приостановлено.
7. Вспомогательные команды управления литьем
В текущей версии приложения поддерживаются основные команды, совместимые с медиасессией, такие как воспроизведение, пауза и перемотка. Однако некоторые команды управления Cast недоступны в медиасессии. Для поддержки этих команд управления Cast необходимо зарегистрировать объект MediaCommandCallback .
Добавьте метод setMediaCommandCallback MyMediaCommandCallback к экземпляру MediaManager при инициализации проигрывателя:
private fun initializePlayer() {
...
castReceiverContext = CastReceiverContext.getInstance()
if (castReceiverContext != null) {
val mediaManager = castReceiverContext!!.mediaManager
...
mediaManager.setMediaCommandCallback(MyMediaCommandCallback())
}
}
Создайте класс MyMediaCommandCallback для переопределения методов, таких как onQueueUpdate() чтобы поддерживать команды управления Cast:
private inner class MyMediaCommandCallback : MediaCommandCallback() {
override fun onQueueUpdate(
senderId: String?,
queueUpdateRequestData: QueueUpdateRequestData
): Task<Void> {
Toast.makeText(getActivity(), "onQueueUpdate()", Toast.LENGTH_SHORT).show()
// Queue Prev / Next
if (queueUpdateRequestData.getJump() != null) {
Toast.makeText(
getActivity(),
"onQueueUpdate(): Jump = " + queueUpdateRequestData.getJump(),
Toast.LENGTH_SHORT
).show()
}
return super.onQueueUpdate(senderId, queueUpdateRequestData)
}
}
8. Работа со статусом медиафайлов
Изменение статуса медиафайла
Cast Connect получает базовый статус мультимедиа из медиасессии. Для поддержки расширенных функций ваше приложение для Android TV может указывать и переопределять дополнительные свойства статуса с помощью MediaStatusModifier . MediaStatusModifier всегда будет работать с той MediaSession , которую вы установили в CastReceiverContext .
Например, чтобы указать setMediaCommandSupported при срабатывании функции обратного вызова onLoad :
import com.google.android.gms.cast.MediaStatus
...
private class MyMediaLoadCommandCallback : MediaLoadCommandCallback() {
fun onLoad(
senderId: String?,
mediaLoadRequestData: MediaLoadRequestData
): Task<MediaLoadRequestData> {
Toast.makeText(getActivity(), "onLoad()", Toast.LENGTH_SHORT).show()
...
return Tasks.call({
play(convertLoadRequestToMovie(mediaLoadRequestData)!!)
...
// Use MediaStatusModifier to provide additional information for Cast senders.
mediaManager.getMediaStatusModifier()
.setMediaCommandSupported(MediaStatus.COMMAND_QUEUE_NEXT, true)
.setIsPlayingAd(false)
mediaManager.broadcastMediaStatus()
// Return the resolved MediaLoadRequestData to indicate load success.
mediaLoadRequestData
})
}
}
Перехват MediaStatus перед отправкой
Подобно компоненту MessageInterceptor из SDK веб-приемника, вы можете указать MediaStatusWriter в своем MediaManager для внесения дополнительных изменений в MediaStatus перед его передачей подключенным отправителям.
Например, перед отправкой на мобильные устройства можно задать пользовательские данные в параметре MediaStatus :
import com.google.android.gms.cast.tv.media.MediaManager.MediaStatusInterceptor
import com.google.android.gms.cast.tv.media.MediaStatusWriter
import org.json.JSONObject
import org.json.JSONException
...
private fun initializePlayer() {
if (mPlayer == null) {
...
if (castReceiverContext != null) {
...
val mediaManager: MediaManager = castReceiverContext.getMediaManager()
...
// Use MediaStatusInterceptor to process the MediaStatus before sending out.
mediaManager.setMediaStatusInterceptor(
MediaStatusInterceptor { mediaStatusWriter: MediaStatusWriter ->
try {
mediaStatusWriter.setCustomData(JSONObject("{myData: 'CustomData'}"))
} catch (e: JSONException) {
Log.e(LOG_TAG,e.message,e);
}
})
}
}
}
9. Поздравляем!
Теперь вы знаете, как включить функцию трансляции экрана в приложении Android TV с помощью библиотеки Cast Connect.
Для получения более подробной информации ознакомьтесь с руководством для разработчиков: /cast/docs/android_tv_receiver .