1. Обзор
Эта лаборатория научит вас, как изменить существующее приложение Android TV для поддержки трансляции и связи из существующих приложений Cast Sender.
Что такое Google Cast и Cast Connect?
Google Cast позволяет пользователям транслировать контент с мобильного устройства на телевизор. Типичный сеанс Google Cast состоит из двух компонентов — приложения -отправителя и приложения -получателя . Приложения-отправители, такие как мобильное приложение или веб-сайт, например Youtube.com, инициируют и управляют воспроизведением приложения-приемника Cast. Приложения Cast-приемника — это приложения HTML 5, которые работают на устройствах Chromecast и Android TV.
Почти все состояние сеанса Cast хранится в приложении-получателе. Когда состояние обновляется, например, если загружается новый элемент мультимедиа, статус мультимедиа передается всем отправителям. Эти трансляции содержат текущее состояние сеанса Cast. Приложения-отправители используют этот статус мультимедиа для отображения информации о воспроизведении в своем пользовательском интерфейсе.
Cast Connect построен на основе этой инфраструктуры, а ваше приложение Android TV выступает в качестве приемника. Библиотека Cast Connect позволяет вашему приложению Android TV получать сообщения и транслировать статус мультимедиа, как если бы это было приложение-приемник трансляции.
Что мы собираемся строить?
После завершения этой лабораторной работы вы сможете использовать приложения Cast Sender для трансляции видео в приложение Android TV. Приложение Android TV также может взаимодействовать с приложениями-отправителями через протокол Cast.
Что вы узнаете
- Как добавить библиотеку Cast Connect в образец приложения ATV.
- Как подключить отправитель Cast и запустить приложение ATV.
- Как запустить воспроизведение мультимедиа в приложении ATV из приложения Cast sender.
- Как отправить статус мультимедиа из приложения 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 . Вы можете найти серийный номер, выбрав «Настройки» > «Настройки устройства» > «Встроенный 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
из папки примера кода и нажмите «ОК». - Нажмите Файл >
Синхронизировать проект с файлами 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 будет запущено на вашем квадроцикле, и кнопка Cast на отправителе укажет, что оно подключено.
.
- Выберите видео в приложении ATV, и оно начнет воспроизводиться на вашем квадроцикле.
- На вашем мобильном телефоне в нижней части приложения-отправителя теперь виден мини-контроллер. Вы можете использовать кнопку воспроизведения/паузы для управления воспроизведением.
- Выберите видео с мобильного телефона и воспроизведите. Видео начнет воспроизводиться на вашем квадроцикле, а расширенный контроллер отобразится на вашем мобильном отправителе.
- Заблокируйте свой телефон, и когда вы его разблокируете, вы увидите уведомление на экране блокировки, позволяющее управлять воспроизведением мультимедиа или остановить трансляцию.
4. Подготовьте стартовый проект
Теперь, когда мы проверили интеграцию Cast Connect завершенного приложения, нам нужно добавить поддержку Cast Connect в загруженное вами начальное приложение. Теперь вы готовы работать над стартовым проектом с помощью Android Studio:
- Выберите «Импорт проекта» на экране приветствия или пункты меню «Файл» > «Создать» > «Импортировать проект...» .
- Выберите
app-start
из папки примера кода и нажмите «ОК». - Нажмите Файл >
Синхронизировать проект с файлами 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
}
Теперь, когда обратный вызов определен, нам нужно зарегистрировать его в 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 .