En esta guía para desarrolladores, se describe cómo agregar compatibilidad con Google Cast a tu dispositivo Android app emisora con el SDK de Android Sender.
El dispositivo móvil o laptop es el remitente que controla la reproducción. El dispositivo Google Cast es el receptor que muestra el contenido en la TV.
El framework de remitente hace referencia al objeto binario de la biblioteca de la clase Cast y al asociado recursos presentes en el tiempo de ejecución en el remitente. La app del remitente o la app de Cast hace referencia a una app que también se ejecuta en el remitente. La app del receptor web se refiere a la aplicación HTML que se ejecuta en el dispositivo compatible con Cast.
El framework del remitente usa un diseño de devolución de llamada asíncrono para informar al remitente. de eventos y para hacer la transición entre varios estados de la vida de la app de Cast ciclo.
Flujo de la app
En los siguientes pasos, se describe el flujo de ejecución de alto nivel típico de un remitente Aplicación para Android:
- El framework de Cast se inicia automáticamente.
MediaRouter
la detección de dispositivos según el ciclo de vida deActivity
. - Cuando el usuario hace clic en el botón para transmitir, el framework presenta la transmisión con la lista de dispositivos de transmisión detectados.
- Cuando el usuario selecciona un dispositivo de transmisión, el framework intenta iniciar el App receptora web en el dispositivo de transmisión
- El framework invoca las devoluciones de llamada en la app emisora para confirmar que la Web Se inició la app receptora.
- El framework crea un canal de comunicación entre el remitente y la Web Apps receptoras.
- El framework usa el canal de comunicación para cargar y controlar el contenido multimedia la reproducción en el receptor web.
- El framework sincroniza el estado de reproducción del contenido multimedia entre el emisor y Receptor web: cuando el usuario realiza acciones de IU de remitente, el framework pasa estas solicitudes de control de contenido multimedia al receptor web envía actualizaciones de estado del contenido multimedia, el framework actualiza el estado de la IU del remitente.
- Cuando el usuario hace clic en el botón para transmitir a fin de desconectarse del dispositivo de transmisión, el framework desconectará la app emisora del receptor web.
Para obtener una lista completa de todas las clases, los métodos y los eventos de Google Cast Android SDK, consulta la referencia de la API de Google Cast Sender para Android En las siguientes secciones, se describen los pasos que debes seguir para agregar la transmisión a tu app para Android.
Cómo configurar el manifiesto de Android
El archivo AndroidManifest.xml de tu app requiere que configures lo siguiente: para el SDK de Cast:
uses-sdk
Establece los niveles de API de Android mínimos y objetivo que admite el SDK de Cast. Actualmente, el nivel mínimo es el nivel de API 23 y el objetivo es Nivel de API 34.
<uses-sdk
android:minSdkVersion="23"
android:targetSdkVersion="34" />
android:theme
Configura el tema de tu app en función de la versión mínima del SDK de Android. Por ejemplo,
no estás implementando tu propio tema, debes usar una variante de
Theme.AppCompat
cuando se orienta a una versión mínima del SDK de Android que es
versiones anteriores a Lollipop.
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.AppCompat" >
...
</application>
Cómo inicializar el contexto de transmisión
El framework tiene un objeto singleton global, el CastContext
, que coordina
todas las interacciones del framework.
Tu app debe implementar
OptionsProvider
para proporcionar las opciones necesarias para inicializar el
CastContext
singleton. OptionsProvider
proporciona una instancia de
CastOptions
que contiene opciones que afectan el comportamiento del framework. El más
importante de estos es el ID de aplicación del receptor web, que se usa para filtrar
de descubrimiento y de iniciar la app del receptor web cuando se ejecute
empezaste.
class CastOptionsProvider : OptionsProvider { override fun getCastOptions(context: Context): CastOptions { return Builder() .setReceiverApplicationId(context.getString(R.string.app_id)) .build() } override fun getAdditionalSessionProviders(context: Context): List<SessionProvider>? { return null } }
public class CastOptionsProvider implements OptionsProvider { @Override public CastOptions getCastOptions(Context context) { CastOptions castOptions = new CastOptions.Builder() .setReceiverApplicationId(context.getString(R.string.app_id)) .build(); return castOptions; } @Override public List<SessionProvider> getAdditionalSessionProviders(Context context) { return null; } }
Debes declarar el nombre completamente calificado del OptionsProvider
implementado
como campo de metadatos en el archivo AndroidManifest.xml de la app emisora:
<application>
...
<meta-data
android:name=
"com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="com.foo.CastOptionsProvider" />
</application>
CastContext
se inicializa de forma diferida cuando CastContext.getSharedInstance()
.
se llama.
class MyActivity : FragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { val castContext = CastContext.getSharedInstance(this) } }
public class MyActivity extends FragmentActivity { @Override public void onCreate(Bundle savedInstanceState) { CastContext castContext = CastContext.getSharedInstance(this); } }
Los widgets de Cast UX
El framework de Cast proporciona los widgets que cumplen con el diseño de Cast. Lista de tareas:
Superposición introductoria: El framework proporciona una vista personalizada,
IntroductoryOverlay
: que se muestra al usuario para llamar la atención sobre el botón para transmitir que primera vez que hay un receptor disponible. La app de Sender personalizar el texto y la posición del título texto.Botón para transmitir: El botón para transmitir estará visible independientemente de la disponibilidad de los dispositivos de transmisión. Cuando el usuario hace clic en el botón para transmitir por primera vez, se muestra el diálogo correspondiente que enumera los dispositivos descubiertos. Cuando el usuario hace clic en el botón para transmitir Mientras el dispositivo está conectado, muestra los metadatos del contenido multimedia actual (como título, el nombre del estudio de grabación y una imagen en miniatura) o permite al usuario para que se desconecte del dispositivo de transmisión. El "botón para transmitir" a veces se llama como el "ícono de Cast".
Minicontrol: Cuando el usuario está transmitiendo contenido y salió del entorno actual la página de contenido o el controlador expandido a otra pantalla en la app emisora, el el minicontrol se muestra en la parte inferior de la pantalla para permitir que el usuario ver los metadatos de los medios que se están transmitiendo y controlar la reproducción.
Control expandido: Cuando el usuario está transmitiendo contenido, si hace clic en la notificación multimedia o minicontrol, se inicia el control expandido, que muestra que actualmente reproduce metadatos multimedia y ofrece varios botones para controlar la reproducción de contenido multimedia.
Notificación: Solo para Android. Cuando el usuario transmite contenido y sale de la app emisora, aparecerá una notificación multimedia con la transmisión en ese momento. metadatos multimedia y controles de reproducción.
Pantalla de bloqueo: Solo para Android. Cuando el usuario transmite contenido y navega (o el dispositivo se agota el tiempo de espera) a la pantalla de bloqueo, se muestra un control de pantalla de bloqueo multimedia que muestra los metadatos del contenido multimedia que se están transmitiendo y los controles de reproducción.
La siguiente guía incluye descripciones sobre cómo agregar estos widgets tu app.
Cómo agregar un botón para transmitir
Android
MediaRouter
Las APIs están diseñadas para habilitar la visualización y reproducción de contenido multimedia en dispositivos secundarios.
Las apps para Android que usan la API de MediaRouter
deben incluir un botón para transmitir.
de su interfaz de usuario, para permitir que los usuarios seleccionen una ruta de contenido multimedia en la que reproducirlos
un dispositivo secundario, como un dispositivo de transmisión.
El framework hace que agregar una
MediaRouteButton
como
Cast button
muy fácil. Primero debes agregar un elemento de menú o MediaRouteButton
en el XML.
que define tu menú y usar
CastButtonFactory
para conectarlo con el framework.
// To add a Cast button, add the following snippet.
// menu.xml
<item
android:id="@+id/media_route_menu_item"
android:title="@string/media_route_menu_title"
app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
app:showAsAction="always" />
// Then override the onCreateOptionMenu() for each of your activities. // MyActivity.kt override fun onCreateOptionsMenu(menu: Menu): Boolean { super.onCreateOptionsMenu(menu) menuInflater.inflate(R.menu.main, menu) CastButtonFactory.setUpMediaRouteButton( applicationContext, menu, R.id.media_route_menu_item ) return true }
// Then override the onCreateOptionMenu() for each of your activities. // MyActivity.java @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); getMenuInflater().inflate(R.menu.main, menu); CastButtonFactory.setUpMediaRouteButton(getApplicationContext(), menu, R.id.media_route_menu_item); return true; }
Entonces, si tu Activity
hereda de
FragmentActivity
,
puedes agregar un
MediaRouteButton
a tu diseño.
// activity_layout.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal" >
<androidx.mediarouter.app.MediaRouteButton
android:id="@+id/media_route_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:mediaRouteTypes="user"
android:visibility="gone" />
</LinearLayout>
// MyActivity.kt override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_layout) mMediaRouteButton = findViewById<View>(R.id.media_route_button) as MediaRouteButton CastButtonFactory.setUpMediaRouteButton(applicationContext, mMediaRouteButton) mCastContext = CastContext.getSharedInstance(this) }
// MyActivity.java @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_layout); mMediaRouteButton = (MediaRouteButton) findViewById(R.id.media_route_button); CastButtonFactory.setUpMediaRouteButton(getApplicationContext(), mMediaRouteButton); mCastContext = CastContext.getSharedInstance(this); }
Para configurar la apariencia del botón para transmitir con un tema, consulta Personaliza el botón para transmitir.
Cómo configurar la detección de dispositivos
La detección de dispositivos es completamente administrada por el
CastContext
Cuando se inicializa CastContext, la app emisora especifica el receptor web.
ID de aplicación y, opcionalmente, puede solicitar que se filtre el espacio de nombres mediante la configuración
supportedNamespaces
in
CastOptions
CastContext
tiene una referencia a MediaRouter
internamente y comenzará
el proceso de descubrimiento en las siguientes condiciones:
- Se basa en un algoritmo diseñado para equilibrar la latencia de detección uso de batería, la detección a veces se inicia automáticamente cuando la app emisora pasa al primer plano.
- Se abrió el diálogo de transmisión.
- El SDK de Cast está intentando recuperar una sesión de transmisión.
El proceso de detección se detendrá cuando se cierre el cuadro de diálogo de transmisión o cuando la app emisora pasa a segundo plano.
class CastOptionsProvider : OptionsProvider { companion object { const val CUSTOM_NAMESPACE = "urn:x-cast:custom_namespace" } override fun getCastOptions(appContext: Context): CastOptions { val supportedNamespaces: MutableList<String> = ArrayList() supportedNamespaces.add(CUSTOM_NAMESPACE) return CastOptions.Builder() .setReceiverApplicationId(context.getString(R.string.app_id)) .setSupportedNamespaces(supportedNamespaces) .build() } override fun getAdditionalSessionProviders(context: Context): List<SessionProvider>? { return null } }
class CastOptionsProvider implements OptionsProvider { public static final String CUSTOM_NAMESPACE = "urn:x-cast:custom_namespace"; @Override public CastOptions getCastOptions(Context appContext) { List<String> supportedNamespaces = new ArrayList<>(); supportedNamespaces.add(CUSTOM_NAMESPACE); CastOptions castOptions = new CastOptions.Builder() .setReceiverApplicationId(context.getString(R.string.app_id)) .setSupportedNamespaces(supportedNamespaces) .build(); return castOptions; } @Override public List<SessionProvider> getAdditionalSessionProviders(Context context) { return null; } }
Cómo funciona la administración de sesiones
El SDK de Cast presenta el concepto de sesión de transmisión, el establecimiento principal que combina los pasos para conectarse a un dispositivo, iniciar (o unirse) a una Web App receptora, conectándose a esa app e inicializando un canal de control multimedia. Ver el receptor web Guía del ciclo de vida de la aplicación para obtener más información sobre las sesiones de transmisión y el ciclo de vida del receptor web.
La clase administra las sesiones
SessionManager
:
a la que tu app puede acceder mediante
CastContext.getSessionManager()
Las sesiones individuales se representan mediante subclases de la clase
Session
Por ejemplo:
CastSession
representa sesiones con dispositivos de transmisión. Tu app puede acceder a la API activa
Sesión de transmisión con
SessionManager.getCurrentCastSession()
Tu app puede usar la
SessionManagerListener
para supervisar los eventos de sesión, como creación, suspensión, reanudación y
de rescisión. El framework intenta reanudar automáticamente desde un
Interrupción anormal o abrupta mientras una sesión estaba activa.
Las sesiones se crean y destruyen automáticamente en respuesta a los gestos del usuario.
de los diálogos de MediaRouter
.
Para comprender mejor los errores de inicio de Cast, las apps pueden usar
CastContext#getCastReasonCodeForCastStatusCode(int)
para convertir el error de inicio de sesión en
CastReasonCodes
Ten en cuenta que algunos errores de inicio de sesión (p.ej., CastReasonCodes#CAST_CANCELLED
)
son el comportamiento previsto y no deben registrarse como errores.
Si necesitas conocer los cambios de estado de la sesión, puedes implementar
SessionManagerListener
En este ejemplo, se escucha la disponibilidad de un
CastSession
en un objeto Activity
.
class MyActivity : Activity() { private var mCastSession: CastSession? = null private lateinit var mCastContext: CastContext private lateinit var mSessionManager: SessionManager private val mSessionManagerListener: SessionManagerListener<CastSession> = SessionManagerListenerImpl() private inner class SessionManagerListenerImpl : SessionManagerListener<CastSession?> { override fun onSessionStarting(session: CastSession?) {} override fun onSessionStarted(session: CastSession?, sessionId: String) { invalidateOptionsMenu() } override fun onSessionStartFailed(session: CastSession?, error: Int) { val castReasonCode = mCastContext.getCastReasonCodeForCastStatusCode(error) // Handle error } override fun onSessionSuspended(session: CastSession?, reason Int) {} override fun onSessionResuming(session: CastSession?, sessionId: String) {} override fun onSessionResumed(session: CastSession?, wasSuspended: Boolean) { invalidateOptionsMenu() } override fun onSessionResumeFailed(session: CastSession?, error: Int) {} override fun onSessionEnding(session: CastSession?) {} override fun onSessionEnded(session: CastSession?, error: Int) { finish() } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) mCastContext = CastContext.getSharedInstance(this) mSessionManager = mCastContext.sessionManager mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession::class.java) } override fun onResume() { super.onResume() mCastSession = mSessionManager.currentCastSession } override fun onDestroy() { super.onDestroy() mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession::class.java) } }
public class MyActivity extends Activity { private CastContext mCastContext; private CastSession mCastSession; private SessionManager mSessionManager; private SessionManagerListener<CastSession> mSessionManagerListener = new SessionManagerListenerImpl(); private class SessionManagerListenerImpl implements SessionManagerListener<CastSession> { @Override public void onSessionStarting(CastSession session) {} @Override public void onSessionStarted(CastSession session, String sessionId) { invalidateOptionsMenu(); } @Override public void onSessionStartFailed(CastSession session, int error) { int castReasonCode = mCastContext.getCastReasonCodeForCastStatusCode(error); // Handle error } @Override public void onSessionSuspended(CastSession session, int reason) {} @Override public void onSessionResuming(CastSession session, String sessionId) {} @Override public void onSessionResumed(CastSession session, boolean wasSuspended) { invalidateOptionsMenu(); } @Override public void onSessionResumeFailed(CastSession session, int error) {} @Override public void onSessionEnding(CastSession session) {} @Override public void onSessionEnded(CastSession session, int error) { finish(); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mCastContext = CastContext.getSharedInstance(this); mSessionManager = mCastContext.getSessionManager(); mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession.class); } @Override protected void onResume() { super.onResume(); mCastSession = mSessionManager.getCurrentCastSession(); } @Override protected void onDestroy() { super.onDestroy(); mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession.class); } }
Transferencia de transmisión
Preservar el estado de la sesión es la base de la transferencia de transmisión, en la que los usuarios pueden transferir transmisiones de audio y video existentes entre dispositivos mediante comandos por voz, Google Home una app o una pantalla inteligente. El contenido multimedia deja de reproducirse en un dispositivo (la fuente) y continúa en otro (el destino). Cualquier dispositivo de transmisión con el firmware más reciente puede funcionar como fuente o destino en un de transmisión continua.
Para obtener el nuevo dispositivo de destino durante una transferencia o expansión de transmisión, haz lo siguiente:
registrar un
Cast.Listener
con el
CastSession#addCastListener
Luego, llama
CastSession#getCastDevice()
durante la devolución de llamada de onDeviceNameChanged
.
Consulta Transferencia de transmisión en Web Receiver para obtener más información.
Reconexión automática
El framework proporciona un
ReconnectionService
que la aplicación emisora puede habilitar para manejar la reconexión de manera sutil
casos excepcionales, como los siguientes:
- Cómo recuperarse de una pérdida temporal de Wi-Fi
- Cómo recuperarse del dispositivo
- Cómo recuperarse de la app en segundo plano
- Cómo recuperar el estado si la app falló
Este servicio está activado de forma predeterminada y puede desactivarse en
CastOptions.Builder
Este servicio puede combinarse automáticamente en el manifiesto de tu app si la combinación automática esté habilitado en tu archivo de Gradle.
El framework iniciará el servicio cuando haya una sesión multimedia y lo detendrá cuando finaliza la sesión multimedia.
Cómo funciona el control multimedia
El framework de Cast da de baja la
RemoteMediaPlayer
de Cast 2.x para dar lugar a una nueva clase
RemoteMediaClient
,
que brinda la misma funcionalidad en un conjunto de APIs más convenientes.
evita tener que pasar un GoogleApiClient.
Cuando tu app establece un
CastSession
con una app receptora web que admita el espacio de nombres multimedia, una instancia de
El framework creará automáticamente RemoteMediaClient
. tu app puede
acceder a él llamando al método getRemoteMediaClient()
en CastSession
instancia.
Todos los métodos de RemoteMediaClient
que envían solicitudes al receptor web harán lo siguiente:
devolver un objeto PendingResult que se puede usar para hacer un seguimiento de esa solicitud.
Se espera que la instancia de RemoteMediaClient
se comparta
varias partes de tu aplicación y, de hecho, algunos componentes internos de la
como los minicontroladores persistentes y
el servicio de notificaciones.
Para ello, esta instancia admite el registro de varias instancias de
RemoteMediaClient.Listener
Configura metadatos de contenido multimedia
El
MediaMetadata
representa la información sobre un elemento multimedia que deseas transmitir. El
En el siguiente ejemplo, se crea una nueva instancia de MediaMetadata de una película y se establece la
título, subtítulo y dos imágenes.
val movieMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE) movieMetadata.putString(MediaMetadata.KEY_TITLE, mSelectedMedia.getTitle()) movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, mSelectedMedia.getStudio()) movieMetadata.addImage(WebImage(Uri.parse(mSelectedMedia.getImage(0)))) movieMetadata.addImage(WebImage(Uri.parse(mSelectedMedia.getImage(1))))
MediaMetadata movieMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE); movieMetadata.putString(MediaMetadata.KEY_TITLE, mSelectedMedia.getTitle()); movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, mSelectedMedia.getStudio()); movieMetadata.addImage(new WebImage(Uri.parse(mSelectedMedia.getImage(0)))); movieMetadata.addImage(new WebImage(Uri.parse(mSelectedMedia.getImage(1))));
Consulta Selección de imágenes sobre el uso de imágenes con metadatos de medios.
Cargar contenido multimedia
Tu app puede cargar un elemento multimedia, como se muestra en el siguiente código. Primer uso
MediaInfo.Builder
con los metadatos de los medios para crear
MediaInfo
instancia. Obtén el
RemoteMediaClient
del CastSession
actual y, luego, carga el MediaInfo
en ese
RemoteMediaClient
Usa RemoteMediaClient
para reproducir, pausar y mucho más
controlar una app de reproducción multimedia que se ejecuta en el receptor web
val mediaInfo = MediaInfo.Builder(mSelectedMedia.getUrl()) .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED) .setContentType("videos/mp4") .setMetadata(movieMetadata) .setStreamDuration(mSelectedMedia.getDuration() * 1000) .build() val remoteMediaClient = mCastSession.getRemoteMediaClient() remoteMediaClient.load(MediaLoadRequestData.Builder().setMediaInfo(mediaInfo).build())
MediaInfo mediaInfo = new MediaInfo.Builder(mSelectedMedia.getUrl()) .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED) .setContentType("videos/mp4") .setMetadata(movieMetadata) .setStreamDuration(mSelectedMedia.getDuration() * 1000) .build(); RemoteMediaClient remoteMediaClient = mCastSession.getRemoteMediaClient(); remoteMediaClient.load(new MediaLoadRequestData.Builder().setMediaInfo(mediaInfo).build());
Consulta también la sección sobre usar pistas de medios.
Formato de video 4K
Para comprobar qué formato de video es tu contenido multimedia, usa
getVideoInfo()
en MediaStatus para obtener la instancia actual de
VideoInfo
Esta instancia contiene el tipo de formato de TV HDR y la altura de la pantalla
y el ancho en píxeles. Las variantes del formato 4K se indican con constantes
HDR_TYPE_*
Notificaciones de control remoto para varios dispositivos
Cuando un usuario transmite contenido, otros dispositivos Android conectados a la misma red una notificación para permitirles también controlar la reproducción. Cualquier persona cuyo dispositivo recibe esas notificaciones, puede desactivarlas para ese dispositivo en la Configuración en Google > Google Cast > Mostrar notificaciones del control remoto (Las notificaciones incluyen un acceso directo a la app de Configuración). Para obtener más detalles, consulta Cómo transmitir notificaciones del control remoto
Agregar minicontrol
Según el estudio Cast Design Lista de tareas, una aplicación emisora debe proporcionar un control persistente conocido como el prefijo mini responsable del tratamiento de datos que debería aparecer cuando el usuario salga de la página de contenido actual otra parte de la app emisora. El minicontrol muestra un recordatorio visible al usuario de la sesión de transmisión actual. Si presionas el minicontrol, puede volver a la vista del control expandido a pantalla completa de Cast.
El framework proporciona un objeto View personalizado, MiniControllerFragment, que puedes agregar. en la parte inferior del archivo de diseño de cada actividad en la que quieras mostrar la minicontrol.
<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" />
Cuando tu app emisora reproduce una transmisión en vivo de audio o video, el SDK muestra automáticamente un botón de reproducción/detener en lugar del botón de reproducción/pausa en el minicontrolador.
Para definir la apariencia del texto del título y subtítulo de esta vista personalizada, haz lo siguiente: y para elegir botones, consulta Personalizar el minicontrol.
Agregar control expandido
La lista de tareas de diseño de Google Cast requiere que la app emisora proporcione una responsable para el contenido multimedia que se transmite. El control expandido es una versión de pantalla completa de el minicontrol.
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 de menú para que el control expandido proporcione el botón para transmitir:
<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 una clase nueva que extienda ExpandedControllerActivity
.
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 } }
public class ExpandedControlsActivity extends ExpandedControllerActivity { @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); getMenuInflater().inflate(R.menu.expanded_controller, menu); CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.media_route_menu_item); return true; } }
Ahora, declara tu nueva actividad en el manifiesto de la app dentro de la etiqueta application
:
<application>
...
<activity
android:name=".expandedcontrols.ExpandedControlsActivity"
android:label="@string/app_name"
android:launchMode="singleTask"
android:theme="@style/Theme.CastVideosDark"
android:screenOrientation="portrait"
android:parentActivityName="com.google.sample.cast.refplayer.VideoBrowserActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
</intent-filter>
</activity>
...
</application>
Edita el CastOptionsProvider
, cambia NotificationOptions
y
CastMediaOptions
para establecer la actividad objetivo en tu nueva actividad:
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() }
public CastOptions getCastOptions(Context context) { NotificationOptions notificationOptions = new NotificationOptions.Builder() .setTargetActivityClassName(ExpandedControlsActivity.class.getName()) .build(); CastMediaOptions mediaOptions = new CastMediaOptions.Builder() .setNotificationOptions(notificationOptions) .setExpandedControllerActivityClassName(ExpandedControlsActivity.class.getName()) .build(); return new CastOptions.Builder() .setReceiverApplicationId(context.getString(R.string.app_id)) .setCastMediaOptions(mediaOptions) .build(); }
Actualiza el método LocalPlayerActivity
loadRemoteMedia
para mostrar tu
actividad nueva cuando se carga el contenido multimedia remoto:
private fun loadRemoteMedia(position: Int, autoPlay: Boolean) { 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(mSelectedMedia) .setAutoplay(autoPlay) .setCurrentTime(position.toLong()).build() ) }
private void loadRemoteMedia(int position, boolean autoPlay) { if (mCastSession == null) { return; } final RemoteMediaClient remoteMediaClient = mCastSession.getRemoteMediaClient(); if (remoteMediaClient == null) { return; } remoteMediaClient.registerCallback(new RemoteMediaClient.Callback() { @Override public void onStatusUpdated() { Intent intent = new Intent(LocalPlayerActivity.this, ExpandedControlsActivity.class); startActivity(intent); remoteMediaClient.unregisterCallback(this); } }); remoteMediaClient.load(new MediaLoadRequestData.Builder() .setMediaInfo(mSelectedMedia) .setAutoplay(autoPlay) .setCurrentTime(position).build()); }
Cuando tu app emisora reproduce una transmisión en vivo de audio o video, el SDK muestra automáticamente un botón de reproducción/detener en lugar del botón de reproducción/pausa en el control expandido.
Para configurar la apariencia con temas, elige qué botones mostrar, y agregar botones personalizados, consulta Personaliza el control expandido.
Control de volumen
El framework administra automáticamente el volumen para la app emisora. El marco de trabajo sincroniza automáticamente las apps del remitente y del receptor web para que el La IU siempre informa el volumen que especifica el receptor web.
Control de volumen del botón físico
En Android, los botones físicos del dispositivo emisor se pueden usar para cambiar la de transmisión en el receptor web de forma predeterminada para cualquier dispositivo que use Jelly Bean o una versión más reciente
Control de volumen del botón físico anterior a Jelly Bean
Para usar las teclas de volumen físicas para controlar el volumen del dispositivo receptor web en
Dispositivos Android anteriores a Jelly Bean, la app emisora debe anular
dispatchKeyEvent
en sus actividades y llamar
CastContext.onDispatchVolumeKeyEventBeforeJellyBean()
:
class MyActivity : FragmentActivity() { override fun dispatchKeyEvent(event: KeyEvent): Boolean { return (CastContext.getSharedInstance(this) .onDispatchVolumeKeyEventBeforeJellyBean(event) || super.dispatchKeyEvent(event)) } }
class MyActivity extends FragmentActivity { @Override public boolean dispatchKeyEvent(KeyEvent event) { return CastContext.getSharedInstance(this) .onDispatchVolumeKeyEventBeforeJellyBean(event) || super.dispatchKeyEvent(event); } }
Agrega controles multimedia a las notificaciones y a la pantalla de bloqueo
Solo en Android, la lista de tareas de diseño de Google Cast requiere que una app emisora
implementar controles multimedia
notificación
y en la bloquea
pantalla,
en la que el remitente está transmitiendo, pero la app emisora no está enfocada. El
proporciona
MediaNotificationService
y
MediaIntentReceiver
para ayudar a la app emisora a crear controles multimedia en una notificación y en el bloqueo
en la pantalla.
MediaNotificationService
se ejecuta cuando el remitente está transmitiendo y mostrará un
notificación con miniatura de imagen e información sobre la transmisión actual
un elemento, un botón de reproducción/pausa y un botón de detención.
MediaIntentReceiver
es un BroadcastReceiver
que controla las acciones del usuario desde
la notificación.
Tu app puede configurar las notificaciones y el control multimedia desde la pantalla de bloqueo hasta
NotificationOptions
Tu app puede configurar los botones de control que se mostrarán en la notificación.
qué Activity
abrir cuando el usuario presiona la notificación Acciones If
no se proporcionan explícitamente, los valores predeterminados,
MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK
y
Se usará MediaIntentReceiver.ACTION_STOP_CASTING
.
// Example showing 4 buttons: "rewind", "play/pause", "forward" and "stop casting". val buttonActions: MutableList<String> = ArrayList() buttonActions.add(MediaIntentReceiver.ACTION_REWIND) buttonActions.add(MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK) buttonActions.add(MediaIntentReceiver.ACTION_FORWARD) buttonActions.add(MediaIntentReceiver.ACTION_STOP_CASTING) // Showing "play/pause" and "stop casting" in the compat view of the notification. val compatButtonActionsIndices = intArrayOf(1, 3) // Builds a notification with the above actions. Each tap on the "rewind" and "forward" buttons skips 30 seconds. // Tapping on the notification opens an Activity with class VideoBrowserActivity. val notificationOptions = NotificationOptions.Builder() .setActions(buttonActions, compatButtonActionsIndices) .setSkipStepMs(30 * DateUtils.SECOND_IN_MILLIS) .setTargetActivityClassName(VideoBrowserActivity::class.java.name) .build()
// Example showing 4 buttons: "rewind", "play/pause", "forward" and "stop casting". List<String> buttonActions = new ArrayList<>(); buttonActions.add(MediaIntentReceiver.ACTION_REWIND); buttonActions.add(MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK); buttonActions.add(MediaIntentReceiver.ACTION_FORWARD); buttonActions.add(MediaIntentReceiver.ACTION_STOP_CASTING); // Showing "play/pause" and "stop casting" in the compat view of the notification. int[] compatButtonActionsIndices = new int[]{1, 3}; // Builds a notification with the above actions. Each tap on the "rewind" and "forward" buttons skips 30 seconds. // Tapping on the notification opens an Activity with class VideoBrowserActivity. NotificationOptions notificationOptions = new NotificationOptions.Builder() .setActions(buttonActions, compatButtonActionsIndices) .setSkipStepMs(30 * DateUtils.SECOND_IN_MILLIS) .setTargetActivityClassName(VideoBrowserActivity.class.getName()) .build();
La opción que muestra los controles multimedia de las notificaciones y la pantalla de bloqueo está activado a
de forma predeterminada y se puede inhabilitar llamando
setNotificationOptions
con un valor nulo en
CastMediaOptions.Builder
Actualmente, la función de la pantalla de bloqueo está activada, siempre y cuando la notificación sea
activado.
// ... continue with the NotificationOptions built above val mediaOptions = CastMediaOptions.Builder() .setNotificationOptions(notificationOptions) .build() val castOptions: CastOptions = Builder() .setReceiverApplicationId(context.getString(R.string.app_id)) .setCastMediaOptions(mediaOptions) .build()
// ... continue with the NotificationOptions built above CastMediaOptions mediaOptions = new CastMediaOptions.Builder() .setNotificationOptions(notificationOptions) .build(); CastOptions castOptions = new CastOptions.Builder() .setReceiverApplicationId(context.getString(R.string.app_id)) .setCastMediaOptions(mediaOptions) .build();
Cuando tu app emisora reproduce una transmisión en vivo de audio o video, el SDK muestra automáticamente un botón de reproducción/detener en lugar del botón de reproducción/pausa en el control de notificaciones, pero no en el control de la pantalla de bloqueo.
Nota: Para mostrar los controles de la pantalla de bloqueo en dispositivos anteriores a la versión Lollipop, haz lo siguiente:
RemoteMediaClient
solicitará automáticamente el foco de audio por ti.
Cómo solucionar errores
Es muy importante que las apps emisoras manejen todas las devoluciones de llamada de error y decidan la mejor respuesta para cada etapa del ciclo de vida de Cast. La app puede mostrar los diálogos de error al usuario o puede decidir anular la conexión Receptor web.