Integrar o Google Cast ao seu app Android

Este guia para desenvolvedores descreve como adicionar compatibilidade com o Google Cast ao seu dispositivo Android app remetente usando o SDK do remetente do Android.

O dispositivo móvel ou laptop é o remetente que controla a reprodução, e o O dispositivo com Google Cast é o Receptor que exibe o conteúdo na TV.

O framework do remetente se refere ao binário da biblioteca de classes do Cast e associado recursos presentes no tempo de execução no remetente. O app remetente ou o app Google Cast refere-se a um app que também é executado no remetente. O app receptor da Web refere-se ao aplicativo HTML executado no dispositivo compatível com Cast.

A estrutura do remetente usa um design de callback assíncrono para informar o remetente de eventos e fazer a transição entre os vários estados da vida útil do app Cast ciclo.

Fluxo de aplicativos

As etapas a seguir descrevem o fluxo de execução de alto nível típico de um remetente App Android:

  • O framework do Cast é iniciado automaticamente MediaRouter descoberta de dispositivos com base no ciclo de vida da Activity.
  • Quando o usuário clica no botão Transmitir, o framework apresenta o Caixa de diálogo com a lista de dispositivos de transmissão descobertos.
  • Quando o usuário seleciona um dispositivo de transmissão, o framework tenta iniciar a App Receptor da Web no dispositivo de transmissão.
  • O framework invoca callbacks no app remetente para confirmar que a interface O app receptor foi iniciado.
  • O framework cria um canal de comunicação entre o remetente e a Web Apps receptores.
  • O framework usa o canal de comunicação para carregar e controlar mídias a reprodução no receptor da Web.
  • O framework sincroniza o estado de reprodução de mídia entre o remetente e o Receptor da Web: quando o usuário realiza ações na interface do remetente, o framework transmite essas solicitações de controle de mídia para o receptor da Web e quando ele envia atualizações de status de mídia, o framework atualiza o estado da interface do remetente.
  • Quando o usuário clicar no botão Transmitir para se desconectar do dispositivo de transmissão, O framework desconectará o app remetente do receptor da Web.

Para acessar uma lista abrangente de todas as classes, métodos e eventos no Google Cast SDK do Android, consulte a Referência da API Google Cast Sender para Android. As seções a seguir abordam as etapas para adicionar o Google Cast ao seu app Android.

Configurar o manifesto do Android

O arquivo AndroidManifest.xml do app exige que você configure o seguinte para o SDK do Cast:

uses-sdk (link em inglês)

Defina os níveis mínimos e desejados da API do Android compatíveis com o SDK do Cast. Atualmente, o nível mínimo da API é 23, e o objetivo é API de nível 34.

<uses-sdk
        android:minSdkVersion="23"
        android:targetSdkVersion="34" />

android:theme

Defina o tema do app com base na versão mínima do SDK do Android. Por exemplo, se você não estiver implementando seu próprio tema, deverá usar uma variante do Theme.AppCompat ao segmentar uma versão mínima do SDK do Android que é antes do Lollipop.

<application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/Theme.AppCompat" >
       ...
</application>

Inicializar o contexto de transmissão

O framework tem um objeto Singleton global, o CastContext, que coordena todas as interações do framework.

Seu app precisa implementar a OptionsProvider interface do usuário para fornecer as opções necessárias para inicializar o CastContext singleton. OptionsProvider fornece uma instância do CastOptions que contém opções que afetam o comportamento da estrutura. A maior mais importante é o ID do aplicativo do receptor da Web, usado para filtrar os resultados da descoberta e iniciar o app Receptor da Web quando uma sessão de transmissão for começar.

Kotlin
.
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
    }
}
Java
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;
    }
}

É necessário declarar o nome totalmente qualificado do OptionsProvider implementado como um campo de metadados no arquivo AndroidManifest.xml do app remetente:

<application>
    ...
    <meta-data
        android:name=
            "com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
        android:value="com.foo.CastOptionsProvider" />
</application>

CastContext é inicializado lentamente quando o CastContext.getSharedInstance(). for chamado.

Kotlin
.
class MyActivity : FragmentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        val castContext = CastContext.getSharedInstance(this)
    }
}
Java
public class MyActivity extends FragmentActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        CastContext castContext = CastContext.getSharedInstance(this);
    }
}

Os widgets de UX do Google Cast

O framework do Google Cast fornece os widgets que obedecem ao design do Cast Lista de verificação:

  • Sobreposição introdutória: O framework fornece uma visualização personalizada, IntroductoryOverlay, exibido ao usuário para chamar a atenção para o botão Transmitir, primeira vez que um receptor estiver disponível. O app Remetente pode personalize o texto e a posição do título ou texto.

  • Botão Transmitir: O botão Transmitir fica visível independentemente da disponibilidade dos dispositivos de transmissão. Quando o usuário clica no botão Transmitir pela primeira vez, uma caixa de diálogo é exibida. que lista os dispositivos descobertos. Quando o usuário clicar no botão Transmitir enquanto o dispositivo está conectado, ele exibe os metadados de mídia atuais (como título, nome do estúdio de gravação e uma imagem em miniatura) ou permite que o usuário para se desconectar do dispositivo de transmissão. O "botão Transmitir" às vezes é chamado como o "ícone Transmitir".

  • Minicontrole: Quando o usuário está transmitindo conteúdo e saiu da configuração página de conteúdo ou controlador expandido para outra tela no aplicativo remetente, o é exibido na parte inferior da tela para permitir que o usuário ver os metadados de mídia transmitidos no momento e controlar a reprodução.

  • Controlador expandido: Quando o usuário estiver transmitindo conteúdo ou clicar na notificação de mídia ou minicontrole, o controle expandido é iniciado, mostrando a metadados de mídia em reprodução no momento e fornece vários botões para controlar a reprodução de mídia.

  • Notificação: Somente para Android. Quando o usuário está transmitindo conteúdo e sai da app remetente, uma notificação de mídia vai aparecer mostrando a transmissão metadados de mídia e controles de reprodução.

  • Tela de bloqueio: Somente para Android. Quando o usuário estiver transmitindo conteúdo e navegando (ou ao dispositivo expirar) na tela de bloqueio, um controle de tela de bloqueio de mídia é exibido que mostra os metadados de mídia e os controles de mídia sendo transmitidos no momento.

O guia a seguir inclui descrições de como adicionar esses widgets ao seu app.

Adicionar um botão Transmitir

O Android MediaRouter As APIs foram projetadas para permitir a exibição e a reprodução de mídia em dispositivos secundários. Os apps Android que usam a API MediaRouter precisam incluir um botão "Transmitir" como parte da interface do usuário, para que os usuários possam selecionar um roteamento de mídia para reproduzir um dispositivo secundário, como um de transmissão.

O framework faz com que a adição MediaRouteButton como Cast button muito fácil. Primeiro, adicione um item de menu ou um MediaRouteButton no XML que define seu menu e usar CastButtonFactory para conectá-lo ao 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" />
Kotlin
.
// 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
}
Java
// 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;
}

Então, se Activity herdar de FragmentActivity, é possível adicionar MediaRouteButton ao seu layout.

// 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>
Kotlin
.
// 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)
}
Java
// 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 definir a aparência do botão Transmitir usando um tema, consulte Personalizar o botão Transmitir.

Configurar a descoberta de dispositivos

A descoberta de dispositivos é completamente gerenciada pelo CastContext Ao inicializar o CastContext, o app remetente especifica o receptor da Web ID do aplicativo e, opcionalmente, solicitar filtragem de namespace definindo supportedNamespaces pol. CastOptions. O CastContext mantém uma referência interna ao MediaRouter e vai começar o processo de descoberta sob as seguintes condições:

  • Baseado em um algoritmo projetado para equilibrar a latência de descoberta do dispositivo e uso da bateria, a descoberta poderá ser iniciada automaticamente quando o app remetente entra em primeiro plano.
  • A caixa de diálogo "Transmitir" está aberta.
  • O SDK do Cast está tentando recuperar uma sessão do Google Cast.

O processo de descoberta será interrompido quando a caixa de diálogo "Transmitir" for fechada ou o app remetente entra em segundo plano.

Kotlin
.
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
    }
}
Java
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;
    }
}

Como funciona o gerenciamento de sessões

O SDK do Cast apresenta o conceito de uma sessão do Google Cast, a que combina as etapas de conexão a um dispositivo, inicialização (ou entrada) de um App receptor, conectando-se a ele e inicializando um canal de controle de mídia. Consulte o receptor da Web Guia do ciclo de vida do aplicativo para mais informações sobre as sessões de transmissão e o ciclo de vida do receptor da Web.

As sessões são gerenciadas pela turma SessionManager, que seu app pode acessar via CastContext.getSessionManager() As sessões individuais são representadas por subclasses da classe Session Por exemplo: CastSession representa sessões com dispositivos de transmissão. Seu app pode acessar o público-alvo ativo no momento Transmitir sessão via SessionManager.getCurrentCastSession()

Seu app pode usar o SessionManagerListener para monitorar eventos de sessão, como criação, suspensão, retomada e da conta. O framework tenta retomar automaticamente de um encerramento anormal/abrupto enquanto uma sessão estava ativa.

As sessões são criadas e eliminadas automaticamente em resposta a gestos do usuário nas caixas de diálogo MediaRouter.

Para entender melhor os erros de inicialização da transmissão, os apps podem usar CastContext#getCastReasonCodeForCastStatusCode(int) para converter o erro de início da sessão em CastReasonCodes Alguns erros ao iniciar a sessão (por exemplo, CastReasonCodes#CAST_CANCELLED) são comportamentos intencionais e não devem ser registrados como erros.

Se você precisar estar ciente das mudanças de estado da sessão, poderá implementar um SessionManagerListener. Este exemplo detecta a disponibilidade de um CastSession em um Activity.

Kotlin
.
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)
    }
}
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);
    }
}

Transferência de stream

A preservação do estado da sessão é a base da transferência de stream, em que Os usuários podem mover streams de áudio e vídeo entre dispositivos usando comandos de voz, o Google Home no app ou em smart displays. A mídia é interrompida em um dispositivo (a fonte) e continua em outro (a destino). Qualquer dispositivo de transmissão com o firmware mais recente pode servir como origem ou destino em um transferência de stream.

Para usar o novo dispositivo de destino durante uma expansão ou transferência de stream: registrar um Cast.Listener usando o método CastSession#addCastListener Em seguida, chame CastSession#getCastDevice() durante o callback onDeviceNameChanged.

Consulte Transferência de stream no receptor da Web para mais informações.

Reconexão automática

O framework oferece ReconnectionService que pode ser ativado pelo app remetente para lidar com a reconexão de forma sutil casos extremos, como:

  • Recuperar-se de uma perda temporária de Wi-Fi
  • Recuperar-se do modo de inatividade do dispositivo
  • Recuperar-se do app em segundo plano
  • Fazer a recuperação em caso de falha do app

Esse serviço é ativado por padrão, mas pode ser desativado em CastOptions.Builder

Com a mesclagem automática, esse serviço pode ser integrado automaticamente ao manifesto do app está ativada no seu arquivo do Gradle.

O framework vai iniciar e interromper o serviço quando houver uma sessão de mídia. quando a sessão de mídia termina.

Como funciona o controle de mídia

O framework do Cast descontinua o RemoteMediaPlayer do Cast 2.x em favor de uma nova classe RemoteMediaClient, que fornece a mesma funcionalidade em um conjunto de APIs mais convenientes, e evita ter que transmitir um GoogleApiClient.

Quando seu app estabelece uma CastSession com um app receptor da Web compatível com o namespace de mídia, uma instância do RemoteMediaClient serão criadas automaticamente pelo framework; seu app pode Para acessá-lo, chame o método getRemoteMediaClient() no CastSession instância.

Todos os métodos de RemoteMediaClient que emitem solicitações para o receptor da Web retornam um objeto PendingResult que pode ser usado para acompanhar essa solicitação.

Espera-se que a instância de RemoteMediaClient seja compartilhada pela várias partes do app e, de fato, alguns componentes internos do como os minicontroles persistentes e ao serviço de notificação. Para isso, esta instância suporta o registro de múltiplas instâncias de RemoteMediaClient.Listener:

Definir metadados de mídia

A MediaMetadata representa as informações sobre um item de mídia que você quer transmitir. A exemplo a seguir cria uma nova instância MediaMetadata de um filme e define o título, subtítulo e duas imagens.

Kotlin
.
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))))
Java
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))));

Consulte Seleção de imagens sobre o uso de imagens com metadados de mídia.

Carregar mídia

Seu app pode carregar um item de mídia, conforme mostrado no código a seguir. Primeiro uso MediaInfo.Builder com os metadados da mídia para criar MediaInfo instância. Acesse o RemoteMediaClient do CastSession atual e, em seguida, carregue o MediaInfo nesse RemoteMediaClient. Use RemoteMediaClient para tocar, pausar e outras atividades controlar um app de player de mídia em execução no receptor da Web.

Kotlin
.
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())
Java
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());

Consulte também a seção sobre usando faixas de mídia.

Formato de vídeo 4K

Para verificar qual é o formato de vídeo da sua mídia, use getVideoInfo() em MediaStatus para obter a instância atual do VideoInfo Esta instância contém o tipo de formato de TV HDR e a altura da tela e largura em pixels. As variantes do formato 4K são indicadas por constantes HDR_TYPE_*

Notificações de controle remoto para vários dispositivos

Quando um usuário está transmitindo, outros dispositivos Android na mesma rede vão receber uma notificação para permitir que ele controle a reprodução. Qualquer pessoa cujo dispositivo receber essas notificações poderá desativá-las no dispositivo em "Configurações" app no Google > Google Cast > Mostrar notificações de controle remoto. As notificações incluem um atalho para o aplicativo Configurações. Para mais detalhes, consulte Notificações de controle remoto do Google Cast

Adicionar minicontrole

De acordo com o Design do elenco lista de verificação, um app remetente deve fornecer um controle persistente conhecido como mini controlador que deve aparecer quando o usuário sai da página de conteúdo atual para outra parte do app remetente. O minicontrole oferece um lembrete visível para o usuário da sessão de transmissão atual. Ao tocar no minicontrole, o o usuário poderá retornar à visualização do controlador expandida em tela cheia do Google Cast.

O framework fornece uma visualização personalizada, MiniControllerFragment, que você pode adicionar ao final do arquivo de layout de cada atividade em que você quer mostrar minicontrole.

<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" />

Quando o app remetente estiver reproduzindo uma transmissão ao vivo de vídeo ou áudio, o SDK exibe automaticamente um botão reproduzir/parar no lugar do botão reproduzir/pausar no minicontrole.

Para definir a aparência do texto do título e subtítulo dessa visualização personalizada, e escolher botões, consulte Personalizar o minicontrole.

Adicionar controle expandido

A Lista de verificação de design do Google Cast exige que um app remetente forneça uma lista de verificação controlador da mídia transmitida. O controle expandido é uma versão em tela cheia do o minicontrole.

O SDK do Cast fornece um widget para o controle expandido chamado ExpandedControllerActivity Essa é uma classe abstrata que você precisa transformar em subclasse para adicionar um botão "Transmitir".

Primeiro, crie um novo arquivo de recurso de menu para o controlador expandido fornecer o botão 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>

Crie uma nova classe que estenda ExpandedControllerActivity.

Kotlin
.
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
    }
}
Java
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;
    }
}

Agora, declare a nova atividade no manifesto do app dentro da tag 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>

Edite a CastOptionsProvider, mude NotificationOptions e CastMediaOptions para definir a atividade de destino de acordo com sua nova atividade:

Kotlin
.
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()
}
Java
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();
}

Atualize o método loadRemoteMedia do LocalPlayerActivity para mostrar nova atividade quando a mídia remota for carregada:

Kotlin
.
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()
    )
}
Java
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());
}

Quando o app remetente estiver reproduzindo uma transmissão ao vivo de vídeo ou áudio, o SDK exibe automaticamente um botão reproduzir/parar no lugar do botão reproduzir/pausar no controle expandido.

Para definir a aparência usando temas, escolha quais botões exibir, e adicionar botões personalizados, consulte Personalizar o controlador expandido.

Controle do volume

O framework gerencia automaticamente o volume do app remetente. A estrutura sincroniza automaticamente os apps do remetente e do receptor da Web para que o remetente A interface sempre informa o volume especificado pelo receptor da Web.

Controle de volume do botão físico

No Android, os botões físicos no dispositivo remetente podem ser usados para alterar a da sessão de transmissão no receptor da Web por padrão em qualquer dispositivo que use Jelly Bean ou versão mais recente.

Controle de volume do botão físico antes do Jelly Bean

Para usar as teclas de volume físicas para controlar o volume do dispositivo receptor da Web em Dispositivos Android mais antigos que o Jelly Bean, o app remetente deverá modificar dispatchKeyEvent em suas atividades e chame CastContext.onDispatchVolumeKeyEventBeforeJellyBean():

Kotlin
.
class MyActivity : FragmentActivity() {
    override fun dispatchKeyEvent(event: KeyEvent): Boolean {
        return (CastContext.getSharedInstance(this)
            .onDispatchVolumeKeyEventBeforeJellyBean(event)
                || super.dispatchKeyEvent(event))
    }
}
Java
class MyActivity extends FragmentActivity {
    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        return CastContext.getSharedInstance(this)
            .onDispatchVolumeKeyEventBeforeJellyBean(event)
            || super.dispatchKeyEvent(event);
    }
}

Adicionar controles de mídia às notificações e à tela de bloqueio

Somente no Android, a Lista de verificação de design do Google Cast exige que um app remetente faça o seguinte: implementar controles de mídia em uma notificação e no bloqueio tela, em que o remetente está transmitindo, mas o app remetente não está em foco. A oferece MediaNotificationService e MediaIntentReceiver para ajudar o aplicativo remetente a criar controles de mídia em uma notificação e no bloqueio tela.

O MediaNotificationService é executado quando o remetente está transmitindo e mostra uma notificação com miniatura de imagem e informações sobre a transmissão atual um botão "Reproduzir/pausar" e um botão "Parar".

O MediaIntentReceiver é um BroadcastReceiver que processa as ações do usuário a notificação.

Seu app pode configurar notificações e controle de mídia da tela de bloqueio NotificationOptions O app pode configurar quais botões de controle serão exibidos na notificação. qual Activity abrir quando o usuário tocar na notificação. Se ações não são fornecidos explicitamente, os valores padrão, MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK e MediaIntentReceiver.ACTION_STOP_CASTING será usado.

Kotlin
.
// 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()
Java
// 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();

A exibição dos controles de mídia da notificação e da tela de bloqueio é ativada por padrão, e podem ser desativados ao chamar setNotificationOptions com nulo em CastMediaOptions.Builder No momento, o recurso de tela de bloqueio fica ativado, desde que a notificação esteja ativado.

Kotlin
.
// ... 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()
Java
// ... 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();

Quando o app remetente estiver reproduzindo uma transmissão ao vivo de vídeo ou áudio, o SDK exibe automaticamente um botão reproduzir/parar no lugar do botão reproduzir/pausar no controle de notificações, mas não no controle da tela de bloqueio.

Observação: para exibir os controles da tela de bloqueio em dispositivos com versões anteriores ao Lollipop, O RemoteMediaClient vai solicitar automaticamente a seleção de áudio em seu nome.

Solucionar erros

É muito importante que os apps remetentes lidem com todos os callbacks de erro e decidam a melhor resposta para cada estágio do ciclo de vida do Google Cast. O app pode mostrar caixas de diálogo de erro para o usuário ou pode decidir encerrar a conexão com o Receptor da Web.