1. Обзор
Эта лаборатория кода научит вас, как изменить существующее приложение Android TV для поддержки трансляции и связи из ваших существующих приложений отправителя трансляции.
Что такое Google Cast и Cast Connect?
Google Cast позволяет пользователям транслировать контент с мобильного устройства на телевизор. Типичный сеанс Google Cast состоит из двух компонентов — приложения-отправителя и приложения- получателя . Приложения-отправители, такие как мобильное приложение или веб-сайт, такой как Youtube.com, инициируют и контролируют воспроизведение приложения-приемника Cast. Приложения-приемники трансляции — это приложения HTML 5, которые работают на устройствах Chromecast и Android TV.
Почти все состояние сеанса Cast хранится в приложении-получателе. Когда состояние обновляется, например, если загружается новый элемент мультимедиа, статус мультимедиа передается всем отправителям. Эти трансляции содержат текущее состояние сеанса Cast. Приложения-отправители используют этот статус мультимедиа для отображения информации о воспроизведении в своем пользовательском интерфейсе.
Cast Connect строится на основе этой инфраструктуры, а ваше приложение Android TV выступает в роли приемника. Библиотека Cast Connect позволяет вашему приложению Android TV получать сообщения и транслировать статус мультимедиа, как если бы это было приложение-приемник трансляции.
Что мы будем строить?
Когда вы закончите эту лабораторную работу, вы сможете использовать приложения отправителя Cast для трансляции видео в приложение Android TV. Приложение Android TV также может взаимодействовать с приложениями-отправителями по протоколу Cast.
Что вы узнаете
- Как добавить библиотеку Cast Connect в пример приложения ATV.
- Как подключить отправителя Cast и запустить приложение ATV.
- Как инициировать воспроизведение мультимедиа в приложении ATV из приложения отправителя трансляции.
- Как отправить статус мультимедиа из приложения ATV в приложения отправителя 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 для разработки приложений, вы должны зарегистрировать серийный номер встроенного Chromecast устройства Android TV, который вы собираетесь использовать в Cast Developer Console . Серийный номер можно найти, выбрав «Настройки» > «Настройки устройства» > «Встроенный Chromecast» > «Серийный номер» на Android TV. Обратите внимание, что он отличается от серийного номера вашего физического устройства и должен быть получен описанным выше способом.
Без регистрации Cast Connect будет работать только с приложениями, установленными из Google Play Store, из соображений безопасности. Через 15 минут после начала процесса регистрации перезагрузите устройство.
Установите приложение для отправки Android
Чтобы протестировать отправку запросов с мобильного устройства, мы предоставили простое приложение-отправитель под названием Cast Videos в виде файла mobile-sender-0629.apk
в исходном коде zip для загрузки. Мы будем использовать ADB для установки APK. Если вы уже установили другую версию 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.
- ADB подключается к вашему устройству Android TV, устройство должно отображаться в Android Studio.
- Нажмите на
Кнопка «Выполнить» , через несколько секунд должно появиться приложение ATV с именем Cast Connect Codelab .
Давайте поиграем в Cast Connect с приложением ATV
- Перейдите на главный экран Android TV.
- Откройте приложение отправителя Cast Videos на своем телефоне Android. Нажмите на кнопку трансляции
и выберите свое устройство ATV.
- Приложение Cast Connect Codelab ATV будет запущено на вашем ATV, а кнопка Cast в вашем отправителе будет указывать на то, что он подключен.
.
- Выберите видео в приложении ATV, и оно начнет воспроизводиться на вашем ATV.
- На вашем мобильном телефоне мини-контроллер теперь виден в нижней части приложения отправителя. Вы можете использовать кнопку воспроизведения/паузы для управления воспроизведением.
- Выберите видео с мобильного телефона и воспроизведите. Видео начнет воспроизводиться на вашем квадроцикле, а расширенный контроллер отобразится на вашем мобильном отправителе.
- Заблокируйте свой телефон, и когда вы разблокируете его, вы должны увидеть уведомление на экране блокировки, чтобы управлять воспроизведением мультимедиа или остановить трансляцию.
4. Подготовить стартовый проект
Теперь, когда мы проверили интеграцию Cast Connect завершенного приложения, нам нужно добавить поддержку Cast Connect в загруженное вами начальное приложение. Теперь вы готовы к сборке поверх начального проекта с помощью Android Studio:
- Выберите « Импорт проекта» на экране приветствия или пункты меню «Файл» > «Создать» > «Импорт проекта...» .
- Выберите
каталог
app-start
из папки примера кода и нажмите OK. - Щелкните Файл >
Синхронизируйте проект с файлами Gradle .
- Выберите устройство ATV и нажмите кнопку
Кнопка «Выполнить» , чтобы запустить приложение и изучить пользовательский интерфейс.
Дизайн приложения
Приложение предоставляет список видео для просмотра пользователем. Пользователи могут выбрать видео для воспроизведения на Android TV. Приложение состоит из двух основных действий: MainActivity
и PlaybackActivity
.
Основная деятельность
Это действие содержит фрагмент ( MainFragment
). Список видео и связанные с ними метаданные настраиваются в классе MovieList
, а метод setupMovies()
вызывается для создания списка объектов Movie
.
Объект Movie
представляет объект видео с заголовком, описанием, эскизами изображений и URL-адресом видео. Каждый объект Movie
привязывается к CardPresenter
для представления миниатюры видео с заголовком и студией и передается в ArrayObjectAdapter
.
Когда элемент выбран, соответствующий объект Movie
передается в PlaybackActivity
.
Воспроизведение
Это действие содержит фрагмент ( PlaybackVideoFragment
), который содержит VideoView
с ExoPlayer
, некоторые элементы управления мультимедиа и текстовую область для отображения описания выбранного видео и позволяет пользователю воспроизводить видео на Android TV. Пользователь может использовать пульт дистанционного управления для воспроизведения/паузы или поиска воспроизведения видео.
Требования к Cast Connect
Cast Connect использует новые версии сервисов Google Play, которые требуют, чтобы ваше приложение ATV было обновлено для использования пространства имен AndroidX .
Чтобы поддерживать Cast Connect в приложении для Android TV, вы должны создавать и поддерживать события из мультимедийного сеанса . Библиотека Cast Connect генерирует статус мультимедиа на основе статуса сеанса мультимедиа. Медиасессия также используется библиотекой Cast Connect для оповещения о получении определенных сообщений от отправителя, например о паузе.
5. Настройка поддержки трансляции
Зависимости
Обновите файл приложения 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. Вы должны реализовать интерфейс ReceiverOptionsProvider
для предоставления CastReceiverOptions
при инициализации CastReceiverContext
.
Создайте файл 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>
Чтобы подключиться к приложению ATV из отправителя 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. От вашего отправителя нажмите кнопку Cast
и выберите свое устройство ATV. Вы увидите, что приложение ATV запущено на устройстве ATV, а состояние кнопки Cast установлено.
6. Загрузка носителя
Команда загрузки отправляется через намерение с именем пакета, которое вы определили в консоли разработчика. Вам нужно добавить следующий предопределенный фильтр намерений в ваше приложение Android TV, чтобы указать целевое действие, которое получит это намерение. В файле AndroidManifest.xml
добавьте фильтр намерения загрузки в 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
}
Теперь, когда Callback определен, нам нужно зарегистрировать его в 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())
}
}
}
Запустим пример приложения
Нажмите на Кнопка «Выполнить» , чтобы развернуть приложение на вашем устройстве ATV. От вашего отправителя нажмите кнопку Cast
и выберите свое устройство ATV. Приложение ATV будет запущено на устройстве ATV. Выберите видео на мобильном устройстве, оно начнет воспроизводиться на квадроцикле. Проверьте, получаете ли вы уведомление на свой телефон, где у вас есть элементы управления воспроизведением. Попробуйте использовать элементы управления, такие как пауза, видео на устройстве ATV должно быть приостановлено.
7. Поддержка команд управления приведением
Текущее приложение теперь поддерживает основные команды, совместимые с мультимедийным сеансом, такие как воспроизведение, пауза и поиск. Однако некоторые команды управления трансляцией недоступны в мультимедийном сеансе. Вам необходимо зарегистрировать MediaCommandCallback
для поддержки этих команд управления трансляцией.
Добавьте MyMediaCommandCallback
в экземпляр MediaManager
с помощью setMediaCommandCallback
при инициализации проигрывателя:
private fun initializePlayer() {
...
castReceiverContext = CastReceiverContext.getInstance()
if (castReceiverContext != null) {
val mediaManager = castReceiverContext!!.mediaManager
...
mediaManager.setMediaCommandCallback(MyMediaCommandCallback())
}
}
Создайте класс MyMediaCommandCallback
, чтобы переопределить методы, такие как onQueueUpdate()
, для поддержки этих команд управления трансляцией:
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. Поздравления
Теперь вы знаете, как включить Cast в приложении Android TV с помощью библиотеки Cast Connect.
Подробнее см. в руководстве для разработчиков: /cast/docs/android_tv_receiver .