Adicionar recursos avançados ao app Android

Intervalos de anúncio

O SDK do Android Sender oferece suporte a intervalos de anúncio e anúncios complementares em um determinado stream de mídia.

Consulte a Visão geral dos intervalos de anúncio do receptor da Web para mais informações sobre como os intervalos de anúncio funcionam.

Embora os intervalos possam ser especificados no remetente e no destinatário, é recomendável que eles sejam especificados no Web Receiver e no Android TV Receiver para manter um comportamento consistente em todas as plataformas.

No Android, especifique intervalos de anúncios em um comando de carregamento usando AdBreakClipInfo e AdBreakInfo:

Kotlin
val breakClip1: AdBreakClipInfo =
    AdBreakClipInfo.Builder("bc0")
        .setTitle("Clip title")
        .setPosterUrl("https://www.some.url")
        .setDuration(60000)
        .setWhenSkippableInMs(5000)  // Set this field so that the ad is skippable
        .build()

val breakClip2: AdBreakClipInfo = …
val breakClip3: AdBreakClipInfo = …

val break1: AdBreakClipInfo =
    AdBreakInfo.Builder(/* playbackPositionInMs= */ 10000)
        .setId("b0")
        .setBreakClipIds({"bc0","bc1","bc2"})
        …
        .build()

val mediaInfo: MediaInfo = MediaInfo.Builder()
    …
    .setAdBreaks({break1})
    .setAdBreakClips({breakClip1, breakClip2, breakClip3})
    .build()

val mediaLoadRequestData: MediaLoadRequestData = MediaInfo.Builder()
    …
    .setMediaInfo(mediaInfo)
    .build()

remoteMediaClient.load(mediaLoadRequestData)
Java
AdBreakClipInfo breakClip1 =
    new AdBreakClipInfo.Builder("bc0")
        .setTitle("Clip title")
        .setPosterUrl("https://www.some.url")
        .setDuration(60000)
        .setWhenSkippableInMs(5000)  // Set this field so that the ad is skippable
        .build();

AdBreakClipInfo breakClip2 = …
AdBreakClipInfo breakClip3 = …

AdBreakInfo break1 =
    new AdBreakInfo.Builder(/* playbackPositionInMs= */ 10000)
        .setId("b0")
        .setBreakClipIds({"bc0","bc1","bc2"})
        …
        .build();

MediaInfo mediaInfo = new MediaInfo.Builder()
    …
    .setAdBreaks({break1})
    .setAdBreakClips({breakClip1, breakClip2, breakClip3})
    .build();

MediaLoadRequestData mediaLoadRequestData = new MediaInfo.Builder()
    …
    .setMediaInfo(mediaInfo)
    .build();

remoteMediaClient.load(mediaLoadRequestData);

Adicionar ações personalizadas

Um app remetente pode estender MediaIntentReceiver para processar ações personalizadas ou substituir o comportamento dele. Se você tiver implementado seu próprio MediaIntentReceiver, será necessário adicioná-lo ao manifesto e definir o nome dele no CastMediaOptions. Este exemplo fornece ações personalizadas que substituim a reprodução remota de mídia, pressionando o botão de mídia e outros tipos de ações.

// In AndroidManifest.xml
<receiver android:name="com.example.MyMediaIntentReceiver" />
Kotlin
// In your OptionsProvider
var mediaOptions = CastMediaOptions.Builder()
    .setMediaIntentReceiverClassName(MyMediaIntentReceiver::class.java.name)
    .build()

// Implementation of MyMediaIntentReceiver
internal class MyMediaIntentReceiver : MediaIntentReceiver() {
    override fun onReceiveActionTogglePlayback(currentSession: Session) {
    }

    override fun onReceiveActionMediaButton(currentSession: Session, intent: Intent) {
    }

    override fun onReceiveOtherAction(context: Context?, action: String, intent: Intent) {
    }
}
Java
// In your OptionsProvider
CastMediaOptions mediaOptions = new CastMediaOptions.Builder()
        .setMediaIntentReceiverClassName(MyMediaIntentReceiver.class.getName())
        .build();

// Implementation of MyMediaIntentReceiver
class MyMediaIntentReceiver extends MediaIntentReceiver {
    @Override
    protected void onReceiveActionTogglePlayback(Session currentSession) {
    }

    @Override
    protected void onReceiveActionMediaButton(Session currentSession, Intent intent) {
    }

    @Override
    protected void onReceiveOtherAction(Context context, String action, Intent intent) {
    }
}

Adicionar um canal personalizado

Para que o app remetente se comunique com o receptor, seu app precisa criar um canal personalizado. O remetente pode usar o canal personalizado para enviar mensagens de string ao destinatário. Cada canal personalizado é definido por um namespace exclusivo e precisa começar com o prefixo urn:x-cast:, por exemplo, urn:x-cast:com.example.custom. É possível ter vários canais personalizados, cada um com um namespace exclusivo. O app receptor também pode enviar e receber mensagens usando o mesmo namespace.

O canal personalizado é implementado com a interface Cast.MessageReceivedCallback:

Kotlin
class HelloWorldChannel : MessageReceivedCallback {
    val namespace: String
        get() = "urn:x-cast:com.example.custom"

    override fun onMessageReceived(castDevice: CastDevice, namespace: String, message: String) {
        Log.d(TAG, "onMessageReceived: $message")
    }
}
Java
class HelloWorldChannel implements Cast.MessageReceivedCallback {
    public String getNamespace() {
        return "urn:x-cast:com.example.custom";
    }
    @Override
    public void onMessageReceived(CastDevice castDevice, String namespace, String message) {
        Log.d(TAG, "onMessageReceived: " + message);
    }
}

Depois que o app remetente estiver conectado ao app receptor, o canal personalizado poderá ser criado usando o método setMessageReceivedCallbacks:

Kotlin
try {
    mCastSession.setMessageReceivedCallbacks(
        mHelloWorldChannel.namespace,
        mHelloWorldChannel)
} catch (e: IOException) {
    Log.e(TAG, "Exception while creating channel", e)
}
Java
try {
    mCastSession.setMessageReceivedCallbacks(
            mHelloWorldChannel.getNamespace(),
            mHelloWorldChannel);
} catch (IOException e) {
    Log.e(TAG, "Exception while creating channel", e);
}

Depois que o canal personalizado é criado, o remetente pode usar o método sendMessage para enviar mensagens de string ao destinatário por esse canal:

Kotlin
private fun sendMessage(message: String) {
    if (mHelloWorldChannel != null) {
        try {
            mCastSession.sendMessage(mHelloWorldChannel.namespace, message)
                .setResultCallback { status ->
                    if (!status.isSuccess) {
                        Log.e(TAG, "Sending message failed")
                    }
                }
        } catch (e: Exception) {
            Log.e(TAG, "Exception while sending message", e)
        }
    }
}
Java
private void sendMessage(String message) {
    if (mHelloWorldChannel != null) {
        try {
            mCastSession.sendMessage(mHelloWorldChannel.getNamespace(), message)
                .setResultCallback( status -> {
                    if (!status.isSuccess()) {
                        Log.e(TAG, "Sending message failed");
                    }
                });
        } catch (Exception e) {
            Log.e(TAG, "Exception while sending message", e);
        }
    }
}

Compatibilidade com reprodução automática

Consulte a seção APIs de reprodução automática e fila.

Substituir a seleção de imagem para widgets de UX

Vários componentes do framework (ou seja, a caixa de diálogo do Google Cast, o mini controlador e o UIMediaController, se configurado) vão mostrar artes para a mídia transmitida no momento. Os URLs para a arte da imagem normalmente são incluídos no MediaMetadata da mídia, mas o app remetente pode ter uma fonte alternativa para os URLs.

A classe ImagePicker define um meio de selecionar uma imagem adequada da lista de imagens em um MediaMetadata, com base no uso da imagem. Por exemplo, miniatura de notificação ou plano de fundo em tela cheia. A implementação padrão de ImagePicker sempre escolhe a primeira imagem ou retorna um valor nulo se nenhuma imagem estiver disponível na MediaMetadata. Seu app pode criar uma subclasse para ImagePicker e substituir o método onPickImage(MediaMetadata, ImageHints) para fornecer uma implementação alternativa e, em seguida, selecionar essa subclasse com o método setImagePicker de CastMediaOptions.Builder. ImageHints fornece dicas para uma ImagePicker sobre o tipo e o tamanho de uma imagem a ser selecionada para exibição na interface.

Como personalizar caixas de diálogo do Google Cast

Como gerenciar o ciclo de vida da sessão

SessionManager é o lugar central para gerenciar o ciclo de vida da sessão. SessionManager detecta mudanças de estado de seleção de rota MediaRouter do Android para iniciar, retomar e encerrar sessões. Quando uma rota é selecionada, SessionManager cria um objeto Session e tenta iniciá-la ou retomá-la. Quando uma rota não é selecionada, o SessionManager encerra a sessão atual.

Portanto, para garantir que SessionManager gerencie os ciclos de vida da sessão corretamente, verifique se:

Dependendo de como você criar as caixas de diálogo do Google Cast, pode ser necessário realizar outras ações:

Estado de zero dispositivos

Se você criar caixas de diálogo de transmissão personalizadas, seu MediaRouteChooserDialog personalizado processará corretamente o caso de nenhum dispositivo ser encontrado. A caixa de diálogo precisa ter indicadores que deixem claro para os usuários quando o app ainda está tentando encontrar dispositivos e quando a tentativa de descoberta não está mais ativa.

Se você estiver usando o MediaRouteChooserDialog padrão, o estado zero de dispositivos já será processado.

Próximas etapas

Isso conclui os recursos que você pode adicionar ao app Android Sender. Agora você pode criar um app remetente para outra plataforma (iOS ou Web) ou criar um app Web Receiver.