Publicação e assinatura com o SDK de Transmissão para Android do IVS | Streaming em tempo real
Este documento descreve as etapas envolvidas na publicação e assinatura de um estágio usando o SDK de Transmissão para Android para streaming em tempo real do IVS.
Conceitos
Existem três conceitos principais que fundamentam a funcionalidade em tempo real: palco, estratégia e renderizador. O objetivo do projeto é minimizar a quantidade de lógica do lado do cliente que é necessária para desenvolver um produto funcional.
Estágio
A classe Stage
corresponde ao principal ponto de interação entre a aplicação de host e o SDK. Ela representa o próprio palco e é usada para entrar e sair do palco. Criar e entrar em um palco requer uma string de token válida e não expirada do ambiente de gerenciamento (representada como token
). Entrar e sair de um palco é simples.
Stage stage = new Stage(context, token, strategy); try { stage.join(); } catch (BroadcastException exception) { // handle join exception } stage.leave();
Na classe Stage
, também é possível anexar o StageRenderer
:
stage.addRenderer(renderer); // multiple renderers can be added
Strategy
A interface Stage.Strategy
fornece uma maneira para a aplicação de host comunicar o estado desejado do palco ao SDK. Três funções precisam ser implementadas: shouldSubscribeToParticipant
, shouldPublishFromParticipant
e stageStreamsToPublishForParticipant
. Todas serão discutidas abaixo.
Como se inscrever como participante
Stage.SubscribeType shouldSubscribeToParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo);
Quando um participante remoto entra no palco, o SDK consulta a aplicação de host sobre o estado de inscrição desejado para esse participante. As opções são NONE
, AUDIO_ONLY
e AUDIO_VIDEO
. Ao retornar um valor para essa função, a aplicação de host não precisa se preocupar com o estado de publicação, o estado atual da inscrição ou o estado da conexão do palco. Se AUDIO_VIDEO
for retornado, o SDK aguardará até que o participante remoto esteja publicando antes de inscrever e atualizará a aplicação de host por meio do renderizador durante todo o processo.
Veja a seguir uma amostra de implementação:
@Override Stage.SubscribeType shouldSubscribeToParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo) { return Stage.SubscribeType.AUDIO_VIDEO; }
Esta é a implementação completa desta função para uma aplicação de host que sempre deseja que todos os participantes se vejam, por exemplo, uma aplicação de bate-papo por vídeo.
Implementações mais avançadas também são possíveis. Use a propriedade userInfo
em ParticipantInfo
para se inscrever, de forma seletiva, como participante com base nos recursos fornecidos pelo servidor:
@Override Stage.SubscribeType shouldSubscribeToParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo) { switch(participantInfo.userInfo.get(“role”)) { case “moderator”: return Stage.SubscribeType.NONE; case “guest”: return Stage.SubscribeType.AUDIO_VIDEO; default: return Stage.SubscribeType.NONE; } }
Isso pode ser usado para criar um palco no qual os moderadores podem monitorar todos os convidados sem serem vistos ou ouvidos. A aplicação de host pode usar uma lógica de negócios adicional para permitir que os moderadores se vejam, mas permaneçam invisíveis para os convidados.
Configuração da assinatura de participantes
SubscribeConfiguration subscribeConfigurationForParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo);
Se um participante remoto estiver fazendo uma assinatura (consulte Assinatura de participantes), o SDK consultará a aplicação host sobre uma configuração de assinatura personalizada para esse participante. Essa configuração é opcional e permite que a aplicação host controle certos aspectos do comportamento do assinante. Para obter informações sobre o que pode ser configurado, consulte SubscribeConfiguration
Veja a seguir uma amostra de implementação:
@Override public SubscribeConfiguration subscribeConfigrationForParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo) { SubscribeConfiguration config = new SubscribeConfiguration(); config.jitterBuffer.setMinDelay(JitterBufferConfiguration.JitterBufferDelay.MEDIUM()); return config; }
Essa implementação atualiza o atraso mínimo do buffer de instabilidade para todos os participantes assinantes para uma predefinição de MEDIUM
.
Como com shouldSubscribeToParticipant
, implementações mais avançadas são possíveis. As ParticipantInfo
fornecidas podem ser usadas para atualizar seletivamente a configuração de assinatura para participantes específicos.
Recomendamos usar os valores padrão. Especifique a configuração personalizada somente se houver um comportamento específico que você queira alterar.
Publicação
boolean shouldPublishFromParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo);
Uma vez conectado ao palco, o SDK consulta a aplicação de host para visualizar se um determinado participante deve realizar uma publicação. Isso é invocado somente para participantes locais que têm permissão para realizar publicações com base no token fornecido.
Veja a seguir uma amostra de implementação:
@Override boolean shouldPublishFromParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo) { return true; }
Isso é para uma aplicação de bate-papo por vídeo padrão na qual os usuários sempre desejam realizar publicações. Eles podem ativar e desativar o áudio e o vídeo para serem ocultados ou vistos/ouvidos instantaneamente. (Também é possível usar publicar/cancelar a publicação, mas isso é muito mais lento. Ativar/Desativar o áudio é preferível para casos de uso em que é desejável alterar a visibilidade com frequência.)
Como escolher streams para realizar publicações
@Override List<LocalStageStream> stageStreamsToPublishForParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo); }
Ao realizar publicações, isso é usado para determinar quais streams de áudio e de vídeo devem ser publicados. Isso será abordado com mais detalhes posteriormente em Publish a Media Stream.
Como atualizar a estratégia
A estratégia pretende ser dinâmica, ou seja, os valores retornados de qualquer uma das funções acima podem ser alterados a qualquer momento. Por exemplo, se a aplicação de host não desejar realizar publicações até que o usuário final toque em um botão, você poderá retornar uma variável de shouldPublishFromParticipant
(algo como hasUserTappedPublishButton
). Quando essa variável for alterada com base em uma interação do usuário final, chame stage.refreshStrategy()
para sinalizar ao SDK que ele deve consultar a estratégia para obter os valores mais recentes, aplicando somente o que sofreu alterações. Se o SDK observar que o valor shouldPublishFromParticipant
foi alterado, ele iniciará o processo de publicação. Se as consultas do SDK e todas as funções retornarem o mesmo valor anterior, a chamada refreshStrategy
não realizará nenhuma modificação no palco.
Se o valor de retorno de shouldSubscribeToParticipant
for alterado de AUDIO_VIDEO
para AUDIO_ONLY
, a transmissão de vídeo será removida para todos os participantes com os valores retornados alterados, caso uma transmissão de vídeo tenha existido anteriormente.
Geralmente, o palco usa a estratégia para aplicar com mais eficiência a diferença entre as estratégias anteriores e atuais, sem que a aplicação de host precise se preocupar com todo o estado necessário para realizar o gerenciamento adequado. Por causa disso, pense na chamada stage.refreshStrategy()
como uma operação barata, porque ela não faz nada a menos que a estratégia seja alterada.
Renderizador
A interface StageRenderer
comunica o estado do palco à aplicação de host. Geralmente, as atualizações na interface do usuário da aplicação de host podem ser baseadas inteiramente nos eventos fornecidos pelo renderizador. O renderizador fornece as seguintes funções:
void onParticipantJoined(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo); void onParticipantLeft(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo); void onParticipantPublishStateChanged(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull Stage.PublishState publishState); void onParticipantSubscribeStateChanged(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull Stage.SubscribeState subscribeState); void onStreamsAdded(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull List<StageStream> streams); void onStreamsRemoved(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull List<StageStream> streams); void onStreamsMutedChanged(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull List<StageStream> streams); void onError(@NonNull BroadcastException exception); void onConnectionStateChanged(@NonNull Stage stage, @NonNull Stage.ConnectionState state, @Nullable BroadcastException exception);
Para a maioria desses métodos, os correspondentes Stage
e ParticipantInfo
são fornecidos.
Não é esperado que as informações fornecidas pelo renderizador impactem os valores de retorno da estratégia. Por exemplo, não se espera que o valor de retorno de shouldSubscribeToParticipant
seja alterado quando onParticipantPublishStateChanged
for chamado. Se a aplicação de host desejar inscrever um determinado participante, ele deverá retornar o tipo de inscrição desejado, independentemente do estado de publicação desse participante. O SDK é responsável por garantir que o estado desejado da estratégia seja acionado no momento correto com base no estado do palco.
O StageRenderer
pode ser anexado à classe de palco:
stage.addRenderer(renderer); // multiple renderers can be added
Observe que somente a publicação de participantes aciona onParticipantJoined
e, sempre que um participante interrompe as publicações ou sai da sessão de palco, onParticipantLeft
é acionado.
Publicação de uma transmissão de mídia
Dispositivos locais, como microfones e câmeras integrados, são descobertos por meio de DeviceDiscovery
. Veja a seguir um exemplo de como selecionar a câmera frontal e o microfone padrão e, em seguida, retorná-los como LocalStageStreams
para serem publicados pelo SDK:
DeviceDiscovery deviceDiscovery = new DeviceDiscovery(context); List<Device> devices = deviceDiscovery.listLocalDevices(); List<LocalStageStream> publishStreams = new ArrayList<LocalStageStream>(); Device frontCamera = null; Device microphone = null; // Create streams using the front camera, first microphone for (Device device : devices) { Device.Descriptor descriptor = device.getDescriptor(); if (!frontCamera && descriptor.type == Device.Descriptor.DeviceType.Camera && descriptor.position = Device.Descriptor.Position.FRONT) { front Camera = device; } if (!microphone && descriptor.type == Device.Descriptor.DeviceType.Microphone) { microphone = device; } } ImageLocalStageStream cameraStream = new ImageLocalStageStream(frontCamera); AudioLocalStageStream microphoneStream = new AudioLocalStageStream(microphoneDevice); publishStreams.add(cameraStream); publishStreams.add(microphoneStream); // Provide the streams in Stage.Strategy @Override @NonNull List<LocalStageStream> stageStreamsToPublishForParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo) { return publishStreams; }
Exibição e remoção de participantes
Após a conclusão da inscrição, você receberá uma matriz de objetos StageStream
por meio da função onStreamsAdded
do renderizador. É possível recuperar a visualização prévia de uma ImageStageStream
:
ImagePreviewView preview = ((ImageStageStream)stream).getPreview(); // Add the view to your view hierarchy LinearLayout previewHolder = findViewById(R.id.previewHolder); preview.setLayoutParams(new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT)); previewHolder.addView(preview);
É possível recuperar as estatísticas de nível de áudio de uma AudioStageStream
:
((AudioStageStream)stream).setStatsCallback((peak, rms) -> { // handle statistics });
Quando um participante interrompe as publicações ou cancela a inscrição, a função onStreamsRemoved
é chamada com as transmissões que foram removidas. As aplicações de host devem usar isso como um sinal para remover a transmissão de vídeo do participante da hierarquia de visualização.
onStreamsRemoved
é invocada para todos os cenários em que uma transmissão pode ser removida, incluindo:
-
Um participante remoto que interrompe as publicações.
-
Um dispositivo local que cancela a inscrição ou altera a inscrição de
AUDIO_VIDEO
paraAUDIO_ONLY
. -
Um participante remoto que sai do palco.
-
Um participante local que sai do palco.
Como onStreamsRemoved
é invocada para todos os cenários, nenhuma lógica de negócios personalizada é necessária para remover participantes da IU durante operações de saída remotas ou locais.
Ativação ou desativação do áudio para transmissões de mídia
Os objetos LocalStageStream
têm uma função setMuted
que controla se a transmissão é silenciada. Essa função pode ser chamada na transmissão antes ou depois de ser retornada da função de estratégia streamsToPublishForParticipant
.
Importante: se uma nova instância de objeto LocalStageStream
for retornada por streamsToPublishForParticipant
após uma chamada para refreshStrategy
, o estado mudo do novo objeto de transmissão será aplicado ao palco. Tenha cuidado ao criar novas instâncias LocalStageStream
para garantir que o estado mudo esperado seja mantido.
Monitoramento do estado mudo da mídia do participante remoto
Quando um participante altera o estado mudo de sua transmissão de vídeo ou áudio, a função onStreamMutedChanged
do renderizador é invocada com uma lista de transmissões que foram alteradas. Use o método getMuted
na StageStream
para atualizar a IU adequadamente.
@Override void onStreamsMutedChanged(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull List<StageStream> streams) { for (StageStream stream : streams) { boolean muted = stream.getMuted(); // handle UI changes } }
Obtenção de estatísticas WebRTC
Para obter as estatísticas WebRTC mais recentes para uma transmissão de publicação ou uma transmissão de inscrição, use requestRTCStats
em StageStream
. Quando uma coleta for concluída, você receberá as estatísticas por meio do StageStream.Listener
, que pode ser definido em StageStream
.
stream.requestRTCStats(); @Override void onRTCStats(Map<String, Map<String, String>> statsMap) { for (Map.Entry<String, Map<String, string>> stat : statsMap.entrySet()) { for(Map.Entry<String, String> member : stat.getValue().entrySet()) { Log.i(TAG, stat.getKey() + “ has member “ + member.getKey() + “ with value “ + member.getValue()); } } }
Obtenção de atributos do participante
Se você especificar atributos na solicitação de endpoint CreateParticipantToken
, poderá visualizar os atributos nas propriedades ParticipantInfo
:
@Override void onParticipantJoined(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo) { for (Map.Entry<String, String> entry : participantInfo.userInfo.entrySet()) { Log.i(TAG, “attribute: “ + entry.getKey() + “ = “ + entry.getValue()); } }
Continuação da sessão em segundo plano
Quando a aplicação entra em segundo plano, você pode desejar interromper as publicações ou se inscrever somente para ouvir o áudio de outros participantes remotos. Para fazer isso, atualize a implementação de sua Strategy
para interromper as publicações e se inscreva como AUDIO_ONLY
(ou NONE
, se aplicável).
// Local variables before going into the background boolean shouldPublish = true; Stage.SubscribeType subscribeType = Stage.SubscribeType.AUDIO_VIDEO; // Stage.Strategy implementation @Override boolean shouldPublishFromParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo) { return shouldPublish; } @Override Stage.SubscribeType shouldSubscribeToParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo) { return subscribeType; } // In our Activity, modify desired publish/subscribe when we go to background, then call refreshStrategy to update the stage @Override void onStop() { super.onStop(); shouldPublish = false; subscribeTpye = Stage.SubscribeType.AUDIO_ONLY; stage.refreshStrategy(); }
Habilitação ou desabilitação da codificação em camadas com a transmissão simultânea
Ao publicar um stream de mídia, o SDK transmite streams de vídeo de alta e de baixa qualidade, para que os participantes remotos possam se inscrever no streaming, mesmo que tenham largura de banda de downlink limitada. Por padrão, a codificação em camadas com a transmissão simultânea está ativada. É possível desativá-la ao usar a classe StageVideoConfiguration.Simulcast
:
// Disable Simulcast StageVideoConfiguration config = new StageVideoConfiguration(); config.simulcast.setEnabled(false); ImageLocalStageStream cameraStream = new ImageLocalStageStream(frontCamera, config); // Other Stage implementation code
Limitações de configuração de vídeo
O SDK não oferece suporte para impor o modo retrato ou paisagem usando StageVideoConfiguration.setSize(BroadcastConfiguration.Vec2 size)
. Na orientação retrato, a dimensão menor é usada como largura, enquanto que, na orientação paisagem, essa dimensão é usada como altura. Isso significa que as seguintes duas chamadas para setSize
têm o mesmo efeito na configuração de vídeo:
StageVideo Configuration config = new StageVideo Configuration(); config.setSize(BroadcastConfiguration.Vec2(720f, 1280f); config.setSize(BroadcastConfiguration.Vec2(1280f, 720f);
Tratamento de problemas de rede
Quando a conexão de rede do dispositivo local é perdida, o SDK tenta se reconectar internamente sem nenhuma ação do usuário. Em alguns casos, o SDK não obtém êxito e a ação do usuário é necessária. Existem dois erros principais relacionados à perda da conexão de rede:
-
Código de erro 1400 com a mensagem: “PeerConnection foi perdido devido a um erro de rede desconhecido”.
-
Código de erro 1300 com a mensagem: “Tentativas de repetição esgotadas”.
Se o primeiro erro for recebido, mas o segundo não, o SDK ainda estará conectado ao palco e tentará restabelecer as conexões automaticamente. Como proteção, você pode chamar refreshStrategy
sem nenhuma alteração nos valores de retorno do método de estratégia para acionar uma tentativa de reconexão manual.
Se o segundo erro for recebido, as tentativas de reconexão do SDK falharam e o dispositivo local não está mais conectado ao palco. Nesse caso, tente entrar novamente no palco ao chamar join
depois que sua conexão de rede for restabelecida.
Em geral, encontrar erros após entrar em um palco com êxito indica que o SDK não conseguiu restabelecer uma conexão. Crie um novo objeto Stage
e tente entrar quando as condições da rede melhorarem.
Uso de microfones Bluetooth
Para publicar usando microfones Bluetooth, você deve iniciar uma conexão por Bluetooth SCO:
Bluetooth.startBluetoothSco(context); // Now bluetooth microphones can be used … // Must also stop bluetooth SCO Bluetooth.stopBluetoothSco(context);