1. Descripción general
En este codelab, aprenderás a modificar una app de video para Android existente a fin de transmitir contenido en un dispositivo compatible con Google Cast.
¿Qué es Google Cast?
Google Cast permite a los usuarios transmitir contenido desde un dispositivo móvil a una TV. De esa manera, los usuarios pueden usar su dispositivo móvil como control remoto de modo que se reproduzca contenido multimedia en la TV.
El SDK de Google Cast posibilita que extiendas tu app para controlar una TV o un sistema de sonido. Este SDK te permite agregar los componentes de IU necesarios según la lista de tareas de diseño de Google Cast.
Te proporcionamos la lista de tareas de diseño de Google Cast con el fin de que la experiencia del usuario de Cast resulte sencilla y predecible en todas las plataformas compatibles.
¿Qué compilaremos?
Cuando completes este codelab, tendrás una app de video para Android que podrá transmitir videos a un dispositivo compatible con Google Cast.
Qué aprenderás
- Cómo agregar el SDK de Google Cast a una app de video de muestra
- Cómo agregar el botón para transmitir a fin de seleccionar un dispositivo Google Cast
- Cómo conectarse a un dispositivo de transmisión e iniciar un receptor de contenido multimedia
- Cómo transmitir un video
- Cómo agregar un minicontrolador de transmisión a tu app
- Cómo admitir las notificaciones de contenido multimedia y los controles de pantalla de bloqueo
- Cómo agregar un control expandido
- Cómo proporcionar una superposición introductoria
- Cómo personalizar los widgets de Cast
- Cómo realizar la integración con Cast Connect
Requisitos
- El SDK de Android más reciente
- Android Studio versión 3.2 o superior
- Un dispositivo móvil con Android 4.1 Jelly Bean (nivel de API 16) o una versión posterior
- Un cable de datos USB para conectar tu dispositivo móvil a la computadora de desarrollo
- Un dispositivo Google Cast, como Chromecast o Android TV, que esté configurado con acceso a Internet
- Una TV o un monitor con entrada HDMI
- Se requiere un Chromecast con Google TV para probar la integración de Cast Connect, pero es opcional para el resto del codelab. Si no tienes una, puedes omitir el paso Agregar compatibilidad con Cast Connect al final de este instructivo.
Experiencia
- Necesitarás tener conocimientos previos sobre desarrollo en Kotlin y Android.
- También deberás tener experiencia como usuario de TV :)
¿Cómo usarás este instructivo?
¿Cómo calificarías tu experiencia cuando compilas apps para Android?
¿Cómo calificarías tu experiencia cuando miras TV?
2. Obtén el código de muestra
Puedes descargar el código de muestra completo a tu computadora…
y descomprimir el archivo ZIP que se descargó.
3. Ejecuta la app de muestra
Primero, veamos el aspecto de la app de muestra completa. La app es un reproductor de video básico. El usuario podrá seleccionar un video de una lista y, a continuación, reproducirlo en un dispositivo local o transmitirlo a uno compatible con Google Cast.
Con el código descargado, en las siguientes instrucciones, se describe cómo abrir y ejecutar la app de ejemplo completa en Android Studio:
Selecciona Import Project en la pantalla de bienvenida o haz clic en File > Nuevo > Opciones del menú Import Project...
Selecciona el directorio app-done
de la carpeta del código de muestra y haz clic en OK.
Haz clic en Archivo > Sync Project with Gradle Files.
Habilita la depuración por USB en tu dispositivo Android. En Android 4.2 y versiones posteriores, la pantalla de Opciones para desarrolladores está oculta de forma predeterminada. Para poder visualizarla, dirígete a Configuración > Acerca del dispositivo y presiona Número de compilación siete veces. Regresa a la pantalla anterior, ve a Sistema > Avanzado y presiona Opciones para desarrolladores cerca de la parte inferior. Luego, presiona Depuración por USB para activarla.
Conecta tu dispositivo Android y haz clic en el botón Run en Android Studio. Después de unos segundos, debería aparecer la app de video llamada Cast Videos.
Haz clic en el botón para transmitir de la app de video y selecciona tu dispositivo Google Cast.
Selecciona un video y haz clic en el botón de reproducción.
El video comenzará a reproducirse en tu dispositivo Google Cast.
Se mostrará el control expandido. Puedes usar el botón de reproducción/pausa para controlar la reproducción.
Regresa a la lista de videos.
Ahora, verás un minicontrolador en la parte inferior de la pantalla.
Haz clic en el botón de pausa del minicontrolador a fin de pausar el video en el receptor. Haz clic en el botón de reproducción del minicontrolador para reanudar la reproducción del video.
Haz clic en el botón de inicio del dispositivo móvil. Desliza hacia abajo las notificaciones de modo que veas una notificación para la sesión de transmisión.
Bloquea el teléfono. Cuando lo desbloquees, deberías ver una notificación en la pantalla de bloqueo que permita controlar la reproducción de contenido multimedia o detener la transmisión.
Regresa a la app de video y haz clic en el botón para transmitir a fin de dejar de transmitir en el dispositivo Google Cast.
Preguntas frecuentes
4. Prepara el proyecto inicial
Debemos agregar compatibilidad con Google Cast a la app inicial que descargaste. A continuación, se detalla la terminología de Google Cast que usaremos en este codelab:
- una app emisora se ejecuta en un dispositivo móvil o una laptop.
- una app receptora se ejecuta en el dispositivo Google Cast.
Ahora está todo listo para compilar sobre el proyecto inicial usando Android Studio:
- Selecciona el directorio
app-start
de la descarga de código de muestra (selecciona Import Project en la pantalla de bienvenida o la opción de menú File > New > Import Project...). - Haz clic en el botón
Sync Project with Gradle Files.
- Haz clic en el botón
Run para ejecutar la app y explorar la IU.
Diseño de apps
La app recuperará una lista de videos de un servidor web remoto y proporcionará una lista para que el usuario explore. Los usuarios podrán seleccionar un video de forma que vean los detalles o reproducirlo localmente en el dispositivo móvil.
La app consta de dos actividades principales: VideoBrowserActivity
y LocalPlayerActivity
. Para integrar la funcionalidad de Google Cast, las Actividades deben heredar de AppCompatActivity
o de su elemento superior FragmentActivity
. Esta limitación existe ya que tendríamos que agregar el elemento MediaRouteButton
(proporcionado en la biblioteca de compatibilidad de MediaRouter) como MediaRouteActionProvider
, y esto solo funcionará si la actividad se hereda de las clases mencionadas anteriormente. La biblioteca de compatibilidad de MediaRouter depende de la biblioteca de compatibilidad de AppCompat, que proporciona las clases requeridas.
VideoBrowserActivity
Esta actividad contiene un Fragment
(VideoBrowserFragment
). Esta lista está respaldada por un ArrayAdapter
(VideoListAdapter
). La lista de videos y sus metadatos asociados se alojan en un servidor remoto como un archivo JSON. Un AsyncTaskLoader
(VideoItemLoader
) recupera este JSON y lo procesa para compilar una lista de objetos MediaItem
.
Un objeto MediaItem
modelará un video y los metadatos asociados, como el título, la descripción, la URL de la transmisión, la URL de las imágenes complementarias y las pistas de texto asociadas (para los subtítulos) si las hubiera. El objeto MediaItem
se pasa entre las actividades, por lo que MediaItem
tiene métodos de utilidad para convertirlo en Bundle
y viceversa.
Cuando el cargador compila la lista de MediaItems
y la pasará al VideoListAdapter
. Luego, presentará la lista de MediaItems
en el VideoBrowserFragment
. Se le mostrará al usuario una lista de miniaturas de video con una descripción breve de cada uno. Cuando se seleccione un elemento, el MediaItem
correspondiente se convertirá en Bundle
y se pasará a LocalPlayerActivity
.
LocalPlayerActivity
Esta actividad muestra los metadatos de un video específico y permite que el usuario reproduzca el video de forma local en el dispositivo móvil.
La actividad aloja una VideoView
, algunos controles multimedia y un área de texto para mostrar la descripción del video seleccionado. El reproductor cubre la parte superior de la pantalla y deja espacio para la descripción detallada del video que se encuentra a continuación. El usuario puede reproducir, pausar o saltar la reproducción local de los videos.
Dependencias
Como usamos AppCompatActivity
, necesitamos la biblioteca de compatibilidad de AppCompat. Para administrar la lista de videos y obtener de forma asíncrona las imágenes de la lista, usamos la biblioteca de Volley.
Preguntas frecuentes
5. Agrega el botón para transmitir
Una app compatible con Cast muestra el botón para transmitir en cada una de sus actividades. Al hacer clic en ese botón, se mostrará la lista de dispositivos de transmisión que un usuario puede seleccionar. Si el usuario estaba reproduciendo contenido de forma local en el dispositivo emisor, al seleccionar un dispositivo de transmisión podrá iniciar o reanudar la reproducción en ese dispositivo. En cualquier momento de una sesión de transmisión, el usuario podrá hacer clic en el botón para transmitir y dejar de transmitir tu aplicación al dispositivo de transmisión. El usuario debe poder conectarse al dispositivo de transmisión o desconectarse de él mientras realiza cualquier actividad de tu aplicación, como se describe en la lista de tareas de diseño de Google Cast.
Dependencias
Actualiza el archivo build.gradle de la app para incluir las dependencias de biblioteca necesarias:
dependencies {
implementation 'androidx.appcompat:appcompat:1.5.0'
implementation 'androidx.mediarouter:mediarouter:1.3.1'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'com.google.android.gms:play-services-cast-framework:21.1.0'
implementation 'com.android.volley:volley:1.2.1'
implementation "androidx.core:core-ktx:1.8.0"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
}
Sincroniza el proyecto a fin de confirmar que se compile sin errores.
Inicialización
El framework de Cast tiene un objeto singleton global, el CastContext
, que coordina todas las interacciones de Cast.
Debes implementar la interfaz OptionsProvider
para proporcionar los CastOptions
necesarios para inicializar el singleton CastContext
. La opción más importante es el ID de aplicación receptora, que se utiliza para filtrar los resultados de detección de dispositivos de transmisión y activar la aplicación receptora cuando se inicia una sesión de transmisión.
Cuando desarrolles tu propia app compatible con Cast, tendrás que registrarte como desarrollador de Cast y, luego, obtener el ID de aplicación correspondiente a tu app. Para este codelab, usaremos un ID de app de muestra.
Agrega el siguiente archivo CastOptionsProvider.kt
nuevo al paquete com.google.sample.cast.refplayer
del proyecto:
package com.google.sample.cast.refplayer
import android.content.Context
import com.google.android.gms.cast.framework.OptionsProvider
import com.google.android.gms.cast.framework.CastOptions
import com.google.android.gms.cast.framework.SessionProvider
class CastOptionsProvider : OptionsProvider {
override fun getCastOptions(context: Context): CastOptions {
return CastOptions.Builder()
.setReceiverApplicationId(context.getString(R.string.app_id))
.build()
}
override fun getAdditionalSessionProviders(context: Context): List<SessionProvider>? {
return null
}
}
Ahora, declara el OptionsProvider
en la etiqueta "application
" del archivo AndroidManifest.xml
de la app:
<meta-data
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="com.google.sample.cast.refplayer.CastOptionsProvider" />
Inicia el CastContext
de manera diferida en el método onCreate VideoBrowserActivity
:
import com.google.android.gms.cast.framework.CastContext
private var mCastContext: CastContext? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.video_browser)
setupActionBar()
mCastContext = CastContext.getSharedInstance(this)
}
Agrega la misma lógica de inicialización a la LocalPlayerActivity
.
Botón para transmitir
Ahora que se inicializó CastContext
, debemos agregar el botón para transmitir a fin de permitir que el usuario seleccione un dispositivo de transmisión. El MediaRouteButton
implementa el botón para transmitir desde la biblioteca de compatibilidad de MediaRouter. Al igual que cualquier ícono de acción que puedes agregar a tu actividad (mediante un ActionBar
o un Toolbar
), primero debes agregar el elemento de menú correspondiente a tu menú.
Edita el archivo res/menu/browse.xml
y agrega el elemento MediaRouteActionProvider
en el menú antes del elemento de configuración:
<item
android:id="@+id/media_route_menu_item"
android:title="@string/media_route_menu_title"
app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
app:showAsAction="always"/>
Anula el método onCreateOptionsMenu()
de VideoBrowserActivity
usando CastButtonFactory
para conectar MediaRouteButton
al framework de Cast:
import com.google.android.gms.cast.framework.CastButtonFactory
private var mediaRouteMenuItem: MenuItem? = null
override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
menuInflater.inflate(R.menu.browse, menu)
mediaRouteMenuItem = CastButtonFactory.setUpMediaRouteButton(getApplicationContext(), menu,
R.id.media_route_menu_item)
return true
}
Anula onCreateOptionsMenu
de LocalPlayerActivity
de una manera similar.
Haz clic en el botón Run para ejecutar la app en tu dispositivo móvil. Deberías ver un botón para transmitir en la barra de acciones de la app. Cuando hagas clic en él, se mostrará una lista de los dispositivos de transmisión en tu red local.
CastContext
administra la detección de dispositivos automáticamente. Selecciona tu dispositivo de transmisión. La app receptora de muestra se cargará en él. Puedes navegar entre la actividad de navegación y la actividad del reproductor local. El estado del botón para transmitir se mantendrá sincronizado.
Aún no se incluyó ningún tipo de compatibilidad para reproducir contenido multimedia, por lo que aún no podrás reproducir videos en el dispositivo de transmisión. Haz clic en el botón para transmitir a fin de desconectarte.
6. Transmite contenido de video
Extenderemos la app de muestra de modo que también reproduzca videos de forma remota en un dispositivo de transmisión. Para ello, tenemos que escuchar los diferentes eventos generados por el framework de Cast.
Transmisión de contenido multimedia
A grandes rasgos, si deseas reproducir contenido multimedia en un dispositivo de transmisión, deberás realizar las siguientes acciones:
- Crea un objeto
MediaInfo
que modele un elemento multimedia. - Conectarte al dispositivo de transmisión e iniciar la aplicación receptora
- Cargar el objeto
MediaInfo
en tu receptor y reproducir el contenido - Realizar un seguimiento del estado del contenido multimedia
- Enviar comandos de reproducción al receptor según las interacciones del usuario
Ya realizamos el paso 2 en la sección anterior. Con el framework de Cast, el paso 3 resulta sencillo. El paso 1 equivale a mapear un objeto con otro. MediaInfo
es algo que el framework de Cast interpreta, y MediaItem
es el encapsulamiento de nuestra app para un elemento multimedia. Podemos mapear fácilmente un MediaItem
con una MediaInfo
.
La app de muestra LocalPlayerActivity
ya distingue entre la reproducción local y la remota a través de esta enumeración:
private var mLocation: PlaybackLocation? = null
enum class PlaybackLocation {
LOCAL, REMOTE
}
enum class PlaybackState {
PLAYING, PAUSED, BUFFERING, IDLE
}
En este codelab, no es importante que comprendas exactamente cómo funciona toda la lógica del reproductor de muestra. Sí resulta importante que comprendas que el reproductor multimedia de tu app deberá modificarse para que tenga en cuenta las dos ubicaciones de reproducción de manera similar.
Por el momento, el reproductor local siempre estará en el estado de reproducción local porque aún no conoce los estados de transmisión. Deberemos actualizar la IU en función de las transiciones de estado que ocurran en el framework de Cast. Por ejemplo, si comenzamos a transmitir, deberemos detener la reproducción local e inhabilitar algunos controles. Análogamente, si dejamos de transmitir contenido cuando estemos en esta actividad, deberemos hacer la transición a la reproducción local. Para ello, tenemos que escuchar los diferentes eventos generados por el framework de Cast.
Administración de sesiones de transmisión
En el framework de Cast, una sesión de transmisión combina los pasos para conectarse a un dispositivo, iniciar (o unirse a) una aplicación receptora y conectarse a ella e inicializar un canal de control de contenido multimedia, si corresponde. Este canal es la forma en la que el framework de Cast envía y recibe mensajes del reproductor multimedia del receptor.
La sesión de transmisión se iniciará automáticamente cuando el usuario seleccione un dispositivo desde el botón para transmitir y se detendrá automáticamente cuando el usuario se desconecte. El SDK de Cast también administra automáticamente la reconexión a una sesión del receptor a causa de problemas de red.
Agreguemos un SessionManagerListener
a la LocalPlayerActivity
:
import com.google.android.gms.cast.framework.CastSession
import com.google.android.gms.cast.framework.SessionManagerListener
...
private var mSessionManagerListener: SessionManagerListener<CastSession>? = null
private var mCastSession: CastSession? = null
...
private fun setupCastListener() {
mSessionManagerListener = object : SessionManagerListener<CastSession> {
override fun onSessionEnded(session: CastSession, error: Int) {
onApplicationDisconnected()
}
override fun onSessionResumed(session: CastSession, wasSuspended: Boolean) {
onApplicationConnected(session)
}
override fun onSessionResumeFailed(session: CastSession, error: Int) {
onApplicationDisconnected()
}
override fun onSessionStarted(session: CastSession, sessionId: String) {
onApplicationConnected(session)
}
override fun onSessionStartFailed(session: CastSession, error: Int) {
onApplicationDisconnected()
}
override fun onSessionStarting(session: CastSession) {}
override fun onSessionEnding(session: CastSession) {}
override fun onSessionResuming(session: CastSession, sessionId: String) {}
override fun onSessionSuspended(session: CastSession, reason: Int) {}
private fun onApplicationConnected(castSession: CastSession) {
mCastSession = castSession
if (null != mSelectedMedia) {
if (mPlaybackState == PlaybackState.PLAYING) {
mVideoView!!.pause()
loadRemoteMedia(mSeekbar!!.progress, true)
return
} else {
mPlaybackState = PlaybackState.IDLE
updatePlaybackLocation(PlaybackLocation.REMOTE)
}
}
updatePlayButton(mPlaybackState)
invalidateOptionsMenu()
}
private fun onApplicationDisconnected() {
updatePlaybackLocation(PlaybackLocation.LOCAL)
mPlaybackState = PlaybackState.IDLE
mLocation = PlaybackLocation.LOCAL
updatePlayButton(mPlaybackState)
invalidateOptionsMenu()
}
}
}
En la actividad LocalPlayerActivity
, nos interesa recibir un aviso cuando nos conectemos al dispositivo de transmisión o nos desconectemos de él, de manera que podamos alternar entre el reproductor local y otro. Ten en cuenta que no solo la instancia de tu aplicación que se ejecute en tu dispositivo móvil interrumpirá la conectividad, sino que también puede hacerlo otra instancia de tu aplicación (o bien otra) que se ejecute en un dispositivo móvil diferente.
Se puede acceder a la sesión activa en ese momento como SessionManager.getCurrentSession()
. Las sesiones se crean y destruyen automáticamente en respuesta a las interacciones del usuario con los diálogos de Cast.
Deberemos registrar el objeto de escucha de nuestra sesión e inicializar algunas variables que usaremos en la actividad. Cambia el método onCreate
de LocalPlayerActivity
a:
import com.google.android.gms.cast.framework.CastContext
...
private var mCastContext: CastContext? = null
...
override fun onCreate(savedInstanceState: Bundle?) {
...
mCastContext = CastContext.getSharedInstance(this)
mCastSession = mCastContext!!.sessionManager.currentCastSession
setupCastListener()
...
loadViews()
...
val bundle = intent.extras
if (bundle != null) {
....
if (shouldStartPlayback) {
....
} else {
if (mCastSession != null && mCastSession!!.isConnected()) {
updatePlaybackLocation(PlaybackLocation.REMOTE)
} else {
updatePlaybackLocation(PlaybackLocation.LOCAL)
}
mPlaybackState = PlaybackState.IDLE
updatePlayButton(mPlaybackState)
}
}
...
}
Carga de contenido multimedia
En el SDK de Cast, el RemoteMediaClient
proporciona un conjunto de APIs convenientes para administrar la reproducción de contenido multimedia remoto en el receptor. Para una CastSession
compatible con la reproducción de contenido multimedia, el SDK creará automáticamente una instancia de RemoteMediaClient
. Se puede acceder a ella llamando al método getRemoteMediaClient()
en la instancia CastSession
. Agrega los siguientes métodos a LocalPlayerActivity
para cargar el video que esté seleccionado en el receptor:
import com.google.android.gms.cast.framework.media.RemoteMediaClient
import com.google.android.gms.cast.MediaInfo
import com.google.android.gms.cast.MediaLoadOptions
import com.google.android.gms.cast.MediaMetadata
import com.google.android.gms.common.images.WebImage
import com.google.android.gms.cast.MediaLoadRequestData
private fun loadRemoteMedia(position: Int, autoPlay: Boolean) {
if (mCastSession == null) {
return
}
val remoteMediaClient = mCastSession!!.remoteMediaClient ?: return
remoteMediaClient.load( MediaLoadRequestData.Builder()
.setMediaInfo(buildMediaInfo())
.setAutoplay(autoPlay)
.setCurrentTime(position.toLong()).build())
}
private fun buildMediaInfo(): MediaInfo? {
val movieMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE)
mSelectedMedia?.studio?.let { movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, it) }
mSelectedMedia?.title?.let { movieMetadata.putString(MediaMetadata.KEY_TITLE, it) }
movieMetadata.addImage(WebImage(Uri.parse(mSelectedMedia!!.getImage(0))))
movieMetadata.addImage(WebImage(Uri.parse(mSelectedMedia!!.getImage(1))))
return mSelectedMedia!!.url?.let {
MediaInfo.Builder(it)
.setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
.setContentType("videos/mp4")
.setMetadata(movieMetadata)
.setStreamDuration((mSelectedMedia!!.duration * 1000).toLong())
.build()
}
}
A continuación, actualiza varios métodos existentes a fin de usar la lógica de la sesión de transmisión y admitir la reproducción remota:
private fun play(position: Int) {
startControllersTimer()
when (mLocation) {
PlaybackLocation.LOCAL -> {
mVideoView!!.seekTo(position)
mVideoView!!.start()
}
PlaybackLocation.REMOTE -> {
mPlaybackState = PlaybackState.BUFFERING
updatePlayButton(mPlaybackState)
//seek to a new position within the current media item's new position
//which is in milliseconds from the beginning of the stream
mCastSession!!.remoteMediaClient?.seek(position.toLong())
}
else -> {}
}
restartTrickplayTimer()
}
private fun togglePlayback() {
...
PlaybackState.IDLE -> when (mLocation) {
...
PlaybackLocation.REMOTE -> {
if (mCastSession != null && mCastSession!!.isConnected) {
loadRemoteMedia(mSeekbar!!.progress, true)
}
}
else -> {}
}
...
}
override fun onPause() {
...
mCastContext!!.sessionManager.removeSessionManagerListener(
mSessionManagerListener!!, CastSession::class.java)
}
override fun onResume() {
Log.d(TAG, "onResume() was called")
mCastContext!!.sessionManager.addSessionManagerListener(
mSessionManagerListener!!, CastSession::class.java)
if (mCastSession != null && mCastSession!!.isConnected) {
updatePlaybackLocation(PlaybackLocation.REMOTE)
} else {
updatePlaybackLocation(PlaybackLocation.LOCAL)
}
super.onResume()
}
Para el método updatePlayButton
, cambia el valor de la variable isConnected
:
private fun updatePlayButton(state: PlaybackState?) {
...
val isConnected = (mCastSession != null
&& (mCastSession!!.isConnected || mCastSession!!.isConnecting))
...
}
Ahora, haz clic en el botón Run para ejecutar la app en tu dispositivo móvil. Conéctate a tu dispositivo de transmisión y comienza a reproducir un video. Deberías ver que el video se reproduce en el receptor.
7. Minicontrolador
La lista de tareas de diseño de Cast requiere que todas las apps de Cast proporcionen un minicontrol que aparezca cuando el usuario salga de la página de contenido actual. El minicontrol proporciona acceso instantáneo y un recordatorio visible de la sesión de transmisión en curso.
El SDK de Cast proporciona una vista personalizada, MiniControllerFragment
, que se puede agregar al archivo de diseño de la app de las actividades en las que quieres mostrar el minicontrol.
Agrega el siguiente fragmento de definición a la parte inferior de res/layout/player_activity.xml
y res/layout/video_browser.xml
:
<fragment
android:id="@+id/castMiniController"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:visibility="gone"
class="com.google.android.gms.cast.framework.media.widget.MiniControllerFragment"/>
Haz clic en el botón Run para ejecutar la app y transmitir un video. Cuando se inicie la reproducción en el receptor, deberá aparecer el minicontrolador en la parte inferior de cada actividad. Podrás controlar la reproducción remota con el minicontrolador. Si navegas entre la actividad de navegación y la actividad del reproductor local, el estado del minicontrolador debería mantenerse sincronizado con el estado de reproducción de contenido multimedia del receptor.
8. Notificaciones y pantalla de bloqueo
La lista de tareas de diseño de Google Cast requiere que una app emisora implemente controles multimedia desde una notificación y la pantalla de bloqueo.
El SDK de Cast proporciona un MediaNotificationService
para ayudar a la app emisora a compilar controles multimedia para la notificación y la pantalla de bloqueo. El servicio se combina automáticamente en el manifiesto de tu app mediante Gradle.
El MediaNotificationService
se ejecutará en segundo plano cuando la emisora esté transmitiendo y mostrará una notificación con una miniatura de imagen y metadatos sobre el elemento que se esté transmitiendo, un botón de reproducción/pausa y un botón para detener.
Los controles de notificación y pantalla de bloqueo se pueden habilitar con las CastOptions
cuando inicialices el CastContext
. Estos controles estarán activados de forma predeterminada. La función de pantalla de bloqueo se activará siempre que la notificación esté activada.
Edita el CastOptionsProvider
y cambia la implementación de getCastOptions
de modo que coincida con este código:
import com.google.android.gms.cast.framework.media.CastMediaOptions
import com.google.android.gms.cast.framework.media.NotificationOptions
override fun getCastOptions(context: Context): CastOptions {
val notificationOptions = NotificationOptions.Builder()
.setTargetActivityClassName(VideoBrowserActivity::class.java.name)
.build()
val mediaOptions = CastMediaOptions.Builder()
.setNotificationOptions(notificationOptions)
.build()
return CastOptions.Builder()
.setReceiverApplicationId(context.getString(R.string.app_id))
.setCastMediaOptions(mediaOptions)
.build()
}
Haz clic en el botón Run para ejecutar la app en tu dispositivo móvil. Transmite un video y sal de la app de muestra. Deberías ver una notificación sobre el video que se está reproduciendo en el receptor. Bloquea el dispositivo móvil. La pantalla de bloqueo debería mostrar los controles de la reproducción de contenido multimedia en el dispositivo de transmisión.
9. Superposición introductoria
La lista de tareas de diseño de Google Cast requiere que la app emisora presente el botón para transmitir a los usuarios existentes a fin de informarles que la app ahora admite la transmisión y también ayuda a los usuarios nuevos de Google Cast.
El SDK de Cast proporciona una vista personalizada, IntroductoryOverlay
, que se puede usar a fin de destacar el botón para transmitir cuando se muestra por primera vez a los usuarios. Agrega el siguiente código a VideoBrowserActivity
:
import com.google.android.gms.cast.framework.IntroductoryOverlay
import android.os.Looper
private var mIntroductoryOverlay: IntroductoryOverlay? = null
private fun showIntroductoryOverlay() {
mIntroductoryOverlay?.remove()
if (mediaRouteMenuItem?.isVisible == true) {
Looper.myLooper().run {
mIntroductoryOverlay = com.google.android.gms.cast.framework.IntroductoryOverlay.Builder(
this@VideoBrowserActivity, mediaRouteMenuItem!!)
.setTitleText("Introducing Cast")
.setSingleTime()
.setOnOverlayDismissedListener(
object : IntroductoryOverlay.OnOverlayDismissedListener {
override fun onOverlayDismissed() {
mIntroductoryOverlay = null
}
})
.build()
mIntroductoryOverlay!!.show()
}
}
}
Ahora, agrega un CastStateListener
y llama al método showIntroductoryOverlay
cuando haya un dispositivo de transmisión disponible. Para ello, modifica el método onCreate
y anula los métodos onResume
y onPause
para que coincidan con lo siguiente:
import com.google.android.gms.cast.framework.CastState
import com.google.android.gms.cast.framework.CastStateListener
private var mCastStateListener: CastStateListener? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.video_browser)
setupActionBar()
mCastStateListener = object : CastStateListener {
override fun onCastStateChanged(newState: Int) {
if (newState != CastState.NO_DEVICES_AVAILABLE) {
showIntroductoryOverlay()
}
}
}
mCastContext = CastContext.getSharedInstance(this)
}
override fun onResume() {
super.onResume()
mCastContext?.addCastStateListener(mCastStateListener!!)
}
override fun onPause() {
super.onPause()
mCastContext?.removeCastStateListener(mCastStateListener!!)
}
Borra los datos de la app o quítala del dispositivo. Luego, haz clic en el botón Run para ejecutar la app en tu dispositivo móvil. Deberías ver la superposición introductoria (borra los datos de la app si no se muestra la superposición).
10. Control expandido
La lista de tareas de diseño de Google Cast requiere que la app emisora proporcione un control expandido para el contenido multimedia que se transmite. Este control es una versión de pantalla completa del minicontrolador.
El SDK de Cast proporciona un widget para el control expandido llamado ExpandedControllerActivity
. Esta es una clase abstracta para la que debes crear una subclase a fin de agregar un botón para transmitir.
Primero, crea un nuevo archivo de recursos del menú, llamado expanded_controller.xml
, de modo que el control expandido proporcione el botón para transmitir:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/media_route_menu_item"
android:title="@string/media_route_menu_title"
app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
app:showAsAction="always"/>
</menu>
Crea un paquete nuevo expandedcontrols
en el paquete com.google.sample.cast.refplayer
. A continuación, crea un archivo nuevo llamado ExpandedControlsActivity.kt
en el paquete com.google.sample.cast.refplayer.expandedcontrols
.
package com.google.sample.cast.refplayer.expandedcontrols
import android.view.Menu
import com.google.android.gms.cast.framework.media.widget.ExpandedControllerActivity
import com.google.sample.cast.refplayer.R
import com.google.android.gms.cast.framework.CastButtonFactory
class ExpandedControlsActivity : ExpandedControllerActivity() {
override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
menuInflater.inflate(R.menu.expanded_controller, menu)
CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.media_route_menu_item)
return true
}
}
Ahora declara el ExpandedControlsActivity
en el AndroidManifest.xml
dentro de la etiqueta application
sobre el OPTIONS_PROVIDER_CLASS_NAME
:
<application>
...
<activity
android:name="com.google.sample.cast.refplayer.expandedcontrols.ExpandedControlsActivity"
android:label="@string/app_name"
android:launchMode="singleTask"
android:theme="@style/Theme.CastVideosDark"
android:screenOrientation="portrait"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
</intent-filter>
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.google.sample.cast.refplayer.VideoBrowserActivity"/>
</activity>
...
</application>
Edita el CastOptionsProvider
y cambia NotificationOptions
y CastMediaOptions
a fin de establecer la actividad objetivo en ExpandedControlsActivity
:
import com.google.sample.cast.refplayer.expandedcontrols.ExpandedControlsActivity
override fun getCastOptions(context: Context): CastOptions {
val notificationOptions = NotificationOptions.Builder()
.setTargetActivityClassName(ExpandedControlsActivity::class.java.name)
.build()
val mediaOptions = CastMediaOptions.Builder()
.setNotificationOptions(notificationOptions)
.setExpandedControllerActivityClassName(ExpandedControlsActivity::class.java.name)
.build()
return CastOptions.Builder()
.setReceiverApplicationId(context.getString(R.string.app_id))
.setCastMediaOptions(mediaOptions)
.build()
}
Actualiza el método LocalPlayerActivity
de loadRemoteMedia
para mostrar la ExpandedControlsActivity
cuando se cargue el contenido multimedia remoto:
import com.google.sample.cast.refplayer.expandedcontrols.ExpandedControlsActivity
private fun loadRemoteMedia(position: Int, autoPlay: Boolean) {
if (mCastSession == null) {
return
}
val remoteMediaClient = mCastSession!!.remoteMediaClient ?: return
remoteMediaClient.registerCallback(object : RemoteMediaClient.Callback() {
override fun onStatusUpdated() {
val intent = Intent(this@LocalPlayerActivity, ExpandedControlsActivity::class.java)
startActivity(intent)
remoteMediaClient.unregisterCallback(this)
}
})
remoteMediaClient.load(MediaLoadRequestData.Builder()
.setMediaInfo(buildMediaInfo())
.setAutoplay(autoPlay)
.setCurrentTime(position.toLong()).build())
}
Haz clic en el botón Run para ejecutar la app en tu dispositivo móvil y transmitir un video. Deberías ver el control expandido. Vuelve a la lista de videos. Cuando hagas clic en el minicontrolador, se volverá a cargar el control expandido. Navega fuera de la app para ver la notificación. Haz clic en la imagen de la notificación a fin de cargar el control expandido.
11. Cómo agregar compatibilidad con Cast Connect
La biblioteca de Cast Connect permite que las aplicaciones emisoras existentes se comuniquen con las aplicaciones de Android TV a través del protocolo de transmisión. Cast Connect se basa en la infraestructura de transmisión y tu app para Android TV actúa como receptora.
Dependencias
Nota: Para implementar Cast Connect, el play-services-cast-framework
debe ser 19.0.0
o superior.
LaunchOptions
Para iniciar la aplicación para Android TV, también conocida como Android Receiver, debemos establecer la marca setAndroidReceiverCompatible
como verdadera en el objeto LaunchOptions
. Este objeto LaunchOptions
determina cómo se inicia el receptor y se pasa al CastOptions
que muestra la clase CastOptionsProvider
. Si estableces la marca mencionada anteriormente en false
, se iniciará el receptor web para el ID de app definido en la consola para desarrolladores de Cast.
En el archivo CastOptionsProvider.kt
, agrega lo siguiente al método getCastOptions
:
import com.google.android.gms.cast.LaunchOptions
...
val launchOptions = LaunchOptions.Builder()
.setAndroidReceiverCompatible(true)
.build()
return new CastOptions.Builder()
.setLaunchOptions(launchOptions)
...
.build()
Configurar credenciales de lanzamiento
En el lado del remitente, puedes especificar CredentialsData
para representar quién se une a la sesión. credentials
es una cadena que el usuario puede definir, siempre que la app de ATV pueda comprenderla. El CredentialsData
solo se pasa a la app para Android TV durante el inicio o el momento de unión. Si la vuelves a configurar mientras estás conectado, no se transmitirá a la app de Android TV.
Para configurar las credenciales de inicio, se debe definir CredentialsData
y pasar al objeto LaunchOptions
. Agrega el siguiente código al método getCastOptions
en tu archivo CastOptionsProvider.kt
:
import com.google.android.gms.cast.CredentialsData
...
val credentialsData = CredentialsData.Builder()
.setCredentials("{\"userId\": \"abc\"}")
.build()
val launchOptions = LaunchOptions.Builder()
...
.setCredentialsData(credentialsData)
.build()
Configurar credenciales en LoadRequest
En caso de que tu app de receptor web y tu app de Android TV controlen credentials
de manera diferente, es posible que debas definir credentials
independientes para cada uno. Para solucionarlo, agrega el siguiente código en el archivo LocalPlayerActivity.kt
en la función loadRemoteMedia
:
remoteMediaClient.load(MediaLoadRequestData.Builder()
...
.setCredentials("user-credentials")
.setAtvCredentials("atv-user-credentials")
.build())
Según la app receptora a la que la emisora transmita, el SDK ahora controlará automáticamente las credenciales que se deben usar para la sesión actual.
Prueba Cast Connect
Pasos para instalar el APK de Android TV en Chromecast con Google TV
- Busca la dirección IP de tu dispositivo Android TV. Generalmente, está disponible en Configuración > Redes y Internet > (Nombre de la red a la que está conectado tu dispositivo). A la derecha, se mostrarán los detalles y la IP del dispositivo en la red.
- Usa la dirección IP para que tu dispositivo se conecte a través de ADB desde la terminal:
$ adb connect <device_ip_address>:5555
- Desde la ventana de tu terminal, navega a la carpeta de nivel superior de las muestras de codelab que descargaste al comienzo de este codelab. Por ejemplo:
$ cd Desktop/android_codelab_src
- Instala el archivo .apk en esta carpeta en tu Android TV ejecutando lo siguiente:
$ adb -s <device_ip_address>:5555 install android-tv-app.apk
- Ahora deberías poder ver una app con el nombre Transmitir videos en el menú Tus apps de tu dispositivo Android TV.
- Regresa a tu proyecto de Android Studio y haz clic en el botón Ejecutar para instalar y ejecuta la app emisora en tu dispositivo móvil físico. En la esquina superior derecha, haz clic en el ícono de transmisión y selecciona tu dispositivo Android TV en las opciones disponibles. Ahora deberías ver la app de Android TV iniciada en tu dispositivo Android TV y reproducir un video debería permitirte controlar la reproducción con el control remoto de Android TV.
12. Personaliza los widgets de Cast
Puedes personalizar los widgets de Cast mediante la configuración de los colores, el estilo de los botones, el texto y la apariencia de la miniatura, y la elección de los tipos de botones que se mostrarán.
Actualizar res/values/styles_castvideo.xml
<style name="Theme.CastVideosTheme" parent="Theme.AppCompat.Light.NoActionBar">
...
<item name="mediaRouteTheme">@style/CustomMediaRouterTheme</item>
<item name="castIntroOverlayStyle">@style/CustomCastIntroOverlay</item>
<item name="castMiniControllerStyle">@style/CustomCastMiniController</item>
<item name="castExpandedControllerStyle">@style/CustomCastExpandedController</item>
<item name="castExpandedControllerToolbarStyle">
@style/ThemeOverlay.AppCompat.ActionBar
</item>
...
</style>
Declara los siguientes temas personalizados:
<!-- Customize Cast Button -->
<style name="CustomMediaRouterTheme" parent="Theme.MediaRouter">
<item name="mediaRouteButtonStyle">@style/CustomMediaRouteButtonStyle</item>
</style>
<style name="CustomMediaRouteButtonStyle" parent="Widget.MediaRouter.Light.MediaRouteButton">
<item name="mediaRouteButtonTint">#EEFF41</item>
</style>
<!-- Customize Introductory Overlay -->
<style name="CustomCastIntroOverlay" parent="CastIntroOverlay">
<item name="castButtonTextAppearance">@style/TextAppearance.CustomCastIntroOverlay.Button</item>
<item name="castTitleTextAppearance">@style/TextAppearance.CustomCastIntroOverlay.Title</item>
</style>
<style name="TextAppearance.CustomCastIntroOverlay.Button" parent="android:style/TextAppearance">
<item name="android:textColor">#FFFFFF</item>
</style>
<style name="TextAppearance.CustomCastIntroOverlay.Title" parent="android:style/TextAppearance.Large">
<item name="android:textColor">#FFFFFF</item>
</style>
<!-- Customize Mini Controller -->
<style name="CustomCastMiniController" parent="CastMiniController">
<item name="castShowImageThumbnail">true</item>
<item name="castTitleTextAppearance">@style/TextAppearance.AppCompat.Subhead</item>
<item name="castSubtitleTextAppearance">@style/TextAppearance.AppCompat.Caption</item>
<item name="castBackground">@color/accent</item>
<item name="castProgressBarColor">@color/orange</item>
</style>
<!-- Customize Expanded Controller -->
<style name="CustomCastExpandedController" parent="CastExpandedController">
<item name="castButtonColor">#FFFFFF</item>
<item name="castPlayButtonDrawable">@drawable/cast_ic_expanded_controller_play</item>
<item name="castPauseButtonDrawable">@drawable/cast_ic_expanded_controller_pause</item>
<item name="castStopButtonDrawable">@drawable/cast_ic_expanded_controller_stop</item>
</style>
13. Felicitaciones
Ahora sabes cómo habilitar la transmisión de contenido en una app de video con los widgets del SDK de Cast en Android.
Para obtener más información, consulta la guía para desarrolladores de Android Sender.