

# SDK de Transmissão do IVS: Guia do Android \$1 Streaming em tempo real
<a name="broadcast-android"></a>

O SDK de Transmissão do streaming em tempo real do IVS para Android possibilita que os participantes enviem e recebam vídeos no Android.

O pacote `com.amazonaws.ivs.broadcast` implementa a interface descrita neste documento. O SDK oferece suporte para as seguintes operações:
+ Entrar em um palco 
+ Publicar mídia para outros participantes do palco
+ Inscrever-se na mídia de outros participantes do palco
+ Gerenciar e monitorar vídeos e áudios publicados no palco
+ Obter estatísticas WebRTC para cada conexão de pares
+ Todas as operações do SDK de Transmissão do streaming de baixa latência do IVS para Android

**Versão mais recente do SDK de transmissão para Android:** 1.40.0 ([Notas de release](https://docs.aws.amazon.com/ivs/latest/RealTimeUserGuide/release-notes.html#mar12-26-broadcast-android-rt)) 

**Documentação de referência:** para obter informações sobre os métodos mais importantes disponíveis no SDK de Transmissão do Amazon IVS para Android, consulte a documentação de referência em [https://aws.github.io/amazon-ivs-broadcast-docs/1.40.0/android/](https://aws.github.io/amazon-ivs-broadcast-docs/1.40.0/android/).

**Código de amostra: **consulte o repositório de amostra do Android no GitHub: [https://github.com/aws-samples/amazon-ivs-real-time-streaming-android-samples](https://github.com/aws-samples/amazon-ivs-real-time-streaming-android-samples).

**Requisitos da plataforma:** Android 9.0\$1

# Introdução ao SDK de Transmissão para Android do IVS \$1 Streaming em tempo real
<a name="broadcast-android-getting-started"></a>

Este documento descreve as etapas envolvidas ao começar a usar o SDK de Transmissão para Android para streaming em tempo real do IVS.

## Instalar a biblioteca
<a name="broadcast-android-install"></a>

Há várias maneiras de adicionar a biblioteca de transmissão do Amazon IVS para Android ao seu ambiente de desenvolvimento Android: use o Gradle diretamente, use os catálogos das versões do Gradle ou instale o SDK manualmente.

**Usar o Gradle diretamente**: adicione a biblioteca ao arquivo `build.gradle` do módulo, conforme mostrado aqui (para a versão mais recente do SDK de Transmissão do IVS):

```
repositories {
    mavenCentral()
}
 
dependencies {
     implementation 'com.amazonaws:ivs-broadcast:1.40.0:stages@aar'
}
```

**Usar os catálogos de versões do Gradle**: primeiro inclua isso no arquivo `build.gradle` do módulo:

```
implementation(libs.ivs){
   artifact {
      classifier = "stages"
      type = "aar"
   }
}
```

Em seguida, inclua o seguinte no arquivo `libs.version.toml` (para a versão mais recente do SDK de Transmissão do IVS):

```
[versions]
ivs="1.40.0"

[libraries]
ivs = {module = "com.amazonaws:ivs-broadcast", version.ref = "ivs"}
```

**Instalar o SDK manualmente**: faça o download da versão mais recente neste local:

[https://search.maven.org/artifact/com.amazonaws/ivs-broadcast](https://search.maven.org/artifact/com.amazonaws/ivs-broadcast)

Certifique-se de fazer download do `aar` com `-stages` em anexo.

**Também permitir controle do SDK sobre o alto-falante**: independentemente do método de instalação que escolher, também adicione a seguinte permissão ao manifesto para permitir que o SDK habilite e desabilite o alto-falante:

```
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
```

## Usar o SDK com símbolos de depuração
<a name="broadcast-android-using-debug-symbols-rt"></a>

Também publicamos uma versão do SDK de Transmissão para Android que inclui símbolos de depuração. Você pode usar essa versão para melhorar a qualidade dos relatórios de depuração (rastreamentos de pilha) no Firebase Crashlytics, caso encontre falhas no SDK de Transmissão do IVS, ou seja, `libbroadcastcore.so`. Quando você relata essas falhas à equipe do SDK do IVS, os rastreamentos de pilha de maior qualidade facilitam a correção dos problemas.

Para usar essa versão do SDK, coloque o seguinte nos arquivos de compilação do Gradle:

```
implementation "com.amazonaws:ivs-broadcast:$version:stages-unstripped@aar"
```

Usar a linha acima em vez desta:

```
implementation "com.amazonaws:ivs-broadcast:$version:stages@aar"
```

### Upload de símbolos para o Firebase Crashlytics
<a name="android-debug-symbols-rt-firebase-crashlytics"></a>

Certifique-se de que os arquivos de compilação do Gradle estejam configurados para o Firebase Crashlytics. Siga as instruções do Google aqui:

[https://firebase.google.com/docs/crashlytics/ndk-reports](https://firebase.google.com/docs/crashlytics/ndk-reports)

Certifique-se de incluir `com.google.firebase:firebase-crashlytics-ndk` como dependência.

Ao criar a aplicação para lançamento, o plug-in do Firebase Crashlytics deve fazer o upload dos símbolos automaticamente. Para fazer o upload dos símbolos manualmente, execute um dos seguintes comandos:

```
gradle uploadCrashlyticsSymbolFileRelease
```

```
./gradlew uploadCrashlyticsSymbolFileRelease
```

(Não haverá problema algum se os símbolos forem carregados duas vezes, tanto automática quanto manualmente.)

### Impedir que o arquivo .apk de lançamento fique maior
<a name="android-debug-symbols-rt-sizing-apk"></a>

Antes de empacotar o arquivo `.apk` de lançamento, o plug-in do Gradle para Android tenta automaticamente remover as informações de depuração das bibliotecas compartilhadas (incluindo a biblioteca `libbroadcastcore.so` do SDK de Transmissão do IVS). No entanto, às vezes isso não acontece. Como resultado, o arquivo `.apk` pode ficar maior e é possível que você receba uma mensagem de aviso do plug-in do Gradle para Android informando que ele não consegue remover os símbolos de depuração e que está empacotando os arquivos `.so` da forma como estão. Se isso acontecer, faça o seguinte:
+ Instale um Android NDK. Qualquer versão recente funcionará.
+ Adicione `ndkVersion <your_installed_ndk_version_number>` ao arquivo `build.gradle` da aplicação. Faça isso mesmo que a aplicação não contenha código nativo.

Para obter mais informações, consulte este [relatório de problemas](https://issuetracker.google.com/issues/353554169).

## Solicitar permissões
<a name="broadcast-android-permissions"></a>

Sua aplicação deverá solicitar permissão para acessar a câmera e o microfone do usuário. (Isso não é específico do Amazon IVS; é necessário para qualquer aplicação que precise acessar câmeras e microfones.)

Aqui, verificamos se o usuário já concedeu permissões e, caso contrário, nós as solicitamos:

```
final String[] requiredPermissions =
         { Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO };

for (String permission : requiredPermissions) {
    if (ContextCompat.checkSelfPermission(this, permission) 
                != PackageManager.PERMISSION_GRANTED) {
        // If any permissions are missing we want to just request them all.
        ActivityCompat.requestPermissions(this, requiredPermissions, 0x100);
        break;
    }
}
```

Aqui, recebemos a resposta do usuário:

```
@Override
public void onRequestPermissionsResult(int requestCode, 
                                      @NonNull String[] permissions,
                                      @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode,
               permissions, grantResults);
    if (requestCode == 0x100) {
        for (int result : grantResults) {
            if (result == PackageManager.PERMISSION_DENIED) {
                return;
            }
        }
        setupBroadcastSession();
    }
}
```

# Publicação e assinatura com o SDK de Transmissão para Android do IVS \$1 Streaming em tempo real
<a name="android-publish-subscribe"></a>

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
<a name="android-publish-subscribe-concepts"></a>

Existem três conceitos principais que fundamentam a funcionalidade em tempo real: [palco](#android-publish-subscribe-concepts-stage), [estratégia](#android-publish-subscribe-concepts-strategy) e [renderizador](#android-publish-subscribe-concepts-renderer). 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 name="android-publish-subscribe-concepts-stage"></a>

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
```

### Estratégia
<a name="android-publish-subscribe-concepts-strategy"></a>

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
<a name="android-publish-subscribe-concepts-strategy-participants"></a>

```
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
<a name="android-publish-subscribe-concepts-strategy-participants-config"></a>

```
SubscribeConfiguration subscribeConfigurationForParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo);
```

Se um participante remoto estiver fazendo uma assinatura (consulte [Assinatura de participantes](#android-publish-subscribe-concepts-strategy-participants)), 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](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/interfaces/SubscribeConfiguration) na documentação de referência do SDK.

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
<a name="android-publish-subscribe-concepts-strategy-publishing"></a>

```
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
<a name="android-publish-subscribe-concepts-strategy-streams"></a>

```
@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](#android-publish-subscribe-publish-stream).

#### Como atualizar a estratégia
<a name="android-publish-subscribe-concepts-strategy-updates"></a>

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, será possível 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 name="android-publish-subscribe-concepts-renderer"></a>

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);
                
void onStreamAdaptionChanged(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull RemoteStageStream stream, boolean adaption);

void onStreamLayersChanged(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull RemoteStageStream stream, @NonNull List<RemoteStageStream.Layer> layers);

void onStreamLayerSelected(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull RemoteStageStream stream, @Nullable RemoteStageStream.Layer layer, @NonNull RemoteStageStream.LayerSelectedReason reason);
```

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
<a name="android-publish-subscribe-publish-stream"></a>

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
<a name="android-publish-subscribe-participants"></a>

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` para `AUDIO_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
<a name="android-publish-subscribe-mute-streams"></a>

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
<a name="android-publish-subscribe-mute-state"></a>

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
<a name="android-publish-subscribe-webrtc-stats"></a>

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
<a name="android-publish-subscribe-participant-attributes"></a>

Se você especificar atributos na solicitação da operação `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());
	}
}
```

## Incorporar mensagens
<a name="android-publish-subscribe-embed-messages"></a>

O método `embedMessage` em ImageDevice permite inserir cargas de metadados diretamente nos quadros de vídeo durante a publicação. Isso torna possível o envio de mensagens sincronizadas aos quadros para aplicações em tempo real. A incorporação de mensagens está disponível somente quando o SDK é usado para publicação em tempo real (e não publicação de baixa latência).

Não há garantias de que as mensagens incorporadas chegarão aos assinantes, pois elas são incorporadas diretamente aos quadros de vídeo e transmitidas por UDP, o que não garante a entrega de pacotes. A perda de pacotes durante a transmissão pode resultar na perda de mensagens, especialmente em condições de rede precárias. Para mitigar esse problema, o método `embedMessage` inclui um parâmetro `repeatCount` que duplica a mensagem em vários quadros consecutivos, aumentando a confiabilidade da entrega. Esse recurso está disponível somente para fluxos de vídeo.

### Usar embedMessage
<a name="android-embed-messages-using-embedmessage"></a>

Os clientes de publicação podem incorporar cargas úteis de mensagens em seus fluxos de vídeo usando o método `embedMessage` em ImageDevice. A carga útil deve ter mais de 0 KB e menos de 1 KB. O número de mensagens incorporadas inseridas não deve exceder 10 KB por segundo. 

```
val surfaceSource: SurfaceSource = imageStream.device as SurfaceSource
val message = "hello world"
val messageBytes = message.toByteArray(StandardCharsets.UTF_8)

try {
    surfaceSource.embedMessage(messageBytes, 0)
} catch (e: BroadcastException) {
    Log.e("EmbedMessage", "Failed to embed message: ${e.message}")
}
```

### Repetir cargas úteis das mensagens
<a name="android-embed-messages-repeat-payloads"></a>

Use `repeatCount` para duplicar a mensagem em vários quadros para melhorar a confiabilidade. Esse valor deve ser entre 0 e 30. Os clientes destinatários devem ter uma lógica para desduplicar a mensagem.

```
try {
    surfaceSource.embedMessage(messageBytes, 5)
    // repeatCount: 0-30, receiving clients should handle duplicates
} catch (e: BroadcastException) {
    Log.e("EmbedMessage", "Failed to embed message: ${e.message}")
}
```

### Ler mensagens incorporadas
<a name="android-embed-messages-read-messages"></a>

Consulte "Obter informações de aprimoramento suplementares (SEI)" abaixo para saber como ler mensagens incorporadas de fluxos recebidos.

## Obter informações de aprimoramento suplementares (SEI)
<a name="android-publish-subscribe-sei-attributes"></a>

A unidade NAL de informações de aprimoramento suplementar (SEI) é usada para armazenar metadados alinhados ao quadro ao lado do vídeo. Os clientes assinantes podem ler as cargas úteis do SEI de um publicador que está publicando um vídeo H.264 inspecionando a propriedade `embeddedMessages` nos objetos `ImageDeviceFrame` que saem do `ImageDevice` do publicador. Para fazer isso, adquira o `ImageDevice` de um publicador e, em seguida, observe cada quadro por meio de um retorno de chamada fornecido para `setOnFrameCallback`, conforme mostrado no exemplo a seguir:

```
// in a StageRenderer’s onStreamsAdded function, after acquiring the new ImageStream

val imageDevice = imageStream.device as ImageDevice
imageDevice.setOnFrameCallback(object : ImageDevice.FrameCallback {
	override fun onFrame(frame: ImageDeviceFrame) {
    		for (message in frame.embeddedMessages) {
        		if (message is UserDataUnregisteredSeiMessage) {
            		val seiMessageBytes = message.data
            		val seiMessageUUID = message.uuid
           	 
            		// interpret the message's data based on the UUID
        		}
    		}
	}
})
```

## Continuação da sessão em segundo plano
<a name="android-publish-subscribe-background-session"></a>

Quando a aplicação entra em segundo plano, Pode ser que você deseje 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();
}
```

## Codificação em camadas com transmissão simultânea
<a name="android-publish-subscribe-layered-encoding-simulcast"></a>

A codificação em camadas com transmissão simultânea é um atributo de streaming em tempo real do IVS que permite que os publicadores enviem várias camadas de vídeo de qualidade diferentes e que os assinantes configurem essas camadas de forma dinâmica ou manual. O atributo é descrito mais detalhadamente no documento [Otimizações de streaming](real-time-streaming-optimization.md).

### Configuração da codificação em camadas (Publicador)
<a name="android-layered-encoding-simulcast-configure-publisher"></a>

Como publicador, para habilitar a codificação em camadas com a transmissão simultânea, adicione a seguinte configuração à sua `LocalStageStream` na instanciação:

```
// Enable Simulcast
StageVideoConfiguration config = new StageVideoConfiguration();
config.simulcast.setEnabled(true);

ImageLocalStageStream cameraStream = new ImageLocalStageStream(frontCamera, config);

// Other Stage implementation code
```

Dependendo da resolução definida na configuração do vídeo, um determinado número de camadas será codificado e enviado conforme definido na seção [Camadas, qualidades e taxas de quadros padrão](real-time-streaming-optimization.md#real-time-streaming-optimization-default-layers) de *Otimizações de streaming*.

Também é possível configurar opcionalmente camadas individuais a partir da configuração do simulcast: 

```
// Enable Simulcast
StageVideoConfiguration config = new StageVideoConfiguration();
config.simulcast.setEnabled(true);

List<StageVideoConfiguration.Simulcast.Layer> simulcastLayers = new ArrayList<>();
simulcastLayers.add(StagePresets.SimulcastLocalLayer.DEFAULT_720);
simulcastLayers.add(StagePresets.SimulcastLocalLayer.DEFAULT_180);

config.simulcast.setLayers(simulcastLayers);

ImageLocalStageStream cameraStream = new ImageLocalStageStream(frontCamera, config);

// Other Stage implementation code
```

Como alternativa, é possível criar suas próprias configurações de camada personalizadas para até três camadas. Se você fornecer uma matriz vazia ou não fornecer um valor, serão usados os padrões descritos acima. As camadas são descritas com os seguintes definidores de propriedade obrigatórios:
+ `setSize: Vec2;`
+ `setMaxBitrate: integer;`
+ `setMinBitrate: integer;`
+ `setTargetFramerate: integer;`

Começando com as predefinições, você pode substituir propriedades individuais ou criar uma configuração totalmente nova:

```
// Enable Simulcast
StageVideoConfiguration config = new StageVideoConfiguration();
config.simulcast.setEnabled(true);

List<StageVideoConfiguration.Simulcast.Layer> simulcastLayers = new ArrayList<>();

// Configure high quality layer with custom framerate
StageVideoConfiguration.Simulcast.Layer customHiLayer = StagePresets.SimulcastLocalLayer.DEFAULT_720;
customHiLayer.setTargetFramerate(15);

// Add layers to the list
simulcastLayers.add(customHiLayer);
simulcastLayers.add(StagePresets.SimulcastLocalLayer.DEFAULT_180);

config.simulcast.setLayers(simulcastLayers);

ImageLocalStageStream cameraStream = new ImageLocalStageStream(frontCamera, config);

// Other Stage implementation code
```

Para obter informações sobre valores máximos, limites e erros que podem ser acionados ao configurar camadas individuais, consulte a documentação de referência do SDK.

### Configuração da codificação em camadas (Assinante)
<a name="android-layered-encoding-simulcast-configure-subscriber"></a>

Como assinante, você não precisa fazer nada para habilitar a codificação em camadas. Se um publicador estiver enviando camadas de transmissão simultânea, por padrão, o servidor se adapta dinamicamente entre as camadas para escolher a qualidade ideal com base no dispositivo e nas condições da rede do assinante.

Alternativamente, para escolher camadas explícitas que o publicador está enviando, há várias opções, descritas abaixo.

### Opção 1: preferência de qualidade da camada inicial
<a name="android-layered-encoding-simulcast-layer-quality-preference"></a>

Usando a estratégia `subscribeConfigurationForParticipant`, é possível escolher qual camada inicial você deseja receber como assinante:

```
@Override
public SubscribeConfiguration subscribeConfigrationForParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo) {
    SubscribeConfiguration config = new SubscribeConfiguration();

    config.simulcast.setInitialLayerPreference(SubscribeSimulcastConfiguration.InitialLayerPreference.LOWEST_QUALITY);

    return config;
}
```

Por padrão, os assinantes sempre recebem primeiro a camada de qualidade mais baixa; isso aumenta lentamente até a camada de mais alta qualidade. Isso otimiza o consumo de largura de banda do usuário final e fornece o melhor tempo para vídeo, reduzindo os congelamentos iniciais de vídeo para usuários em redes mais fracas.

Essas opções estão disponíveis para `InitialLayerPreference`:
+ `LOWEST_QUALITY` — O servidor fornece primeiro a camada de vídeo de menor qualidade. Isso otimiza o consumo de largura de banda, bem como o tempo até a mídia. A qualidade é definida como a combinação de tamanho, taxa de bits e taxa de quadros do vídeo. Por exemplo, o vídeo 720p tem qualidade inferior ao vídeo 1080p.
+ `HIGHEST_QUALITY` — O servidor fornece primeiro a camada de vídeo de mais alta qualidade. Isso otimiza a qualidade, mas pode aumentar o tempo até a mídia. A qualidade é definida como a combinação de tamanho, taxa de bits e taxa de quadros do vídeo. Por exemplo, o vídeo 1080p tem qualidade superior ao vídeo 720p.

**Observação:** para que as preferências iniciais da camada (a chamada `setInitialLayerPreference`) entrem em vigor, é necessária uma nova assinatura, pois essas atualizações não se aplicam à assinatura ativa.

### Opção 2: Camada preferida para fluxo
<a name="android-layered-encoding-simulcast-preferred-layer"></a>

O método de estratégia `preferredLayerForStream` permite selecionar uma camada após o início da transmissão. Esse método de estratégia recebe as informações do participante e do stream, para que você possa selecionar uma camada participante por participante. O SDK chama esse método em resposta a eventos específicos, como quando as camadas do stream mudam, o estado do participante muda ou a aplicação host atualiza a estratégia.

O método de estratégia retorna um objeto `RemoteStageStream.Layer`, que pode ser um dos seguintes:
+ Um objeto de camada, como um retornado por `RemoteStageStream.getLayers`.
+ null, o que indica que nenhuma camada deve ser selecionada e que a adaptação dinâmica é preferida.

Por exemplo, a estratégia a seguir sempre fará com que os usuários selecionem a camada de vídeo de menor qualidade disponível:

```
@Nullable
@Override
public RemoteStageStream.Layer preferredLayerForStream(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull RemoteStageStream stream) {
    return stream.getLowestQualityLayer();
}
```

Para redefinir a seleção de camadas e retornar à adaptação dinâmica, retorne null ou undefined na estratégia. Neste exemplo, `appState` é uma variável de espaço reservado que representa o estado da aplicação do host.

```
@Nullable
@Override
public RemoteStageStream.Layer preferredLayerForStream(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull RemoteStageStream stream) {
    if (appState.isAutoMode) {
        return null;
    } else {
        return appState.layerChoice;
    }
}
```

### Opção 3: auxiliares da camada RemoteStageStream
<a name="android-layered-encoding-simulcast-remotestagestream-helpers"></a>

`RemoteStageStream` tem vários auxiliares que podem ser usados para tomar decisões sobre a seleção de camadas e exibir as seleções correspondentes aos usuários finais:
+ **Eventos de camada** — Além de `StageRenderer`, o `RemoteStageStream.Listener` tem eventos que comunicam mudanças de adaptação de camada e transmissão simultânea:
  + `void onAdaptionChanged(boolean adaption)`
  + `void onLayersChanged(@NonNull List<Layer> layers)`
  + `void onLayerSelected(@Nullable Layer layer, @NonNull LayerSelectedReason reason)`
+ **Métodos de camada** — `RemoteStageStream` tem vários métodos auxiliares que podem ser usados para obter informações sobre o fluxo e as camadas que estão sendo apresentadas. Esses métodos estão disponíveis no fluxo remoto fornecido na estratégia `preferredLayerForStream`, bem como nos fluxos remotos expostos via `StageRenderer.onStreamsAdded`.
  + `stream.getLayers`
  + `stream.getSelectedLayer`
  + `stream.getLowestQualityLayer`
  + `stream.getHighestQualityLayer`
  + `stream.getLayersWithConstraints`

Para obter detalhes, consulte a classe `RemoteStageStream` na [documentação de referência do SDK](https://aws.github.io/amazon-ivs-broadcast-docs/latest/android/). Pelo motivo `LayerSelected`, se `UNAVAILABLE` for retornado, isso indica que não foi possível selecionar a camada solicitada. Em vez disso, a melhor seleção é feita, que normalmente é uma camada de qualidade inferior para manter a estabilidade do fluxo.

## Limitações de configuração de vídeo
<a name="android-publish-subscribe-video-limits"></a>

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
<a name="android-publish-subscribe-network-issues"></a>

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
<a name="android-publish-subscribe-bluetooth-microphones"></a>

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);
```

# Problemas conhecidos e soluções alternativas no SDK de Transmissão para Android do IVS \$1 Streaming em tempo real
<a name="broadcast-android-known-issues"></a>

Este documento lista problemas conhecidos que podem ser encontrados ao usar o SDK de Transmissão para Android para streaming em tempo real do Amazon IVS e sugere possíveis soluções alternativas.
+ Quando um dispositivo Android entra e sai do modo de suspensão, é possível que a visualização prévia fique em um estado de congelamento.

  **Solução alternativa:** crie e use um novo `Stage`.
+ Quando um participante entra com um token que está sendo usado por outro participante, a primeira conexão é desconectada sem um erro específico.

  **Solução alternativa:** nenhuma. 
+ Há um problema raro em que o publicador está publicando, mas o estado de publicação que os inscritos recebem é `inactive`.

  **Solução alternativa:** tente sair e, em seguida, entrar novamente na sessão. Se o problema persistir, crie um novo token para o publicador.
+ Um problema raro de distorção de áudio pode ocorrer intermitentemente durante uma sessão de palco, geralmente em chamadas com maior duração.

  **Solução alternativa:** o participante com áudio distorcido pode sair e entrar novamente na sessão ou cancelar a publicação e republicar o áudio para corrigir o problema.
+ Não há suporte para microfones externos ao publicar em um palco.

  **Solução alternativa:** não use um microfone externo conectado por meio de USB para realizar publicações em um palco.
+ Não há suporte para publicação em um palco com compartilhamento de tela usando `createSystemCaptureSources`.

  **Solução alternativa:** gerencie a captura do sistema manualmente usando fontes de entrada de imagem e fontes de entrada de áudio personalizadas.
+ Quando uma `ImagePreviewView` é removida de uma visualização principal (por exemplo, `removeView()` é chamada na visualização principal), a `ImagePreviewView` é liberada imediatamente. A `ImagePreviewView` não apresenta nenhum quadro quando é adicionada a outra visualização principal.

  **Solução alternativa:** solicite outra visualização prévia usando `getPreview`.
+ Ao entrar em um palco com um Samsung Galaxy S22/\$1 que tem o Android 12, é possível que você encontre um erro 1401 e o dispositivo local pode falhar ao entrar no palco ou entrar, mas não terá o áudio.

  **Solução alternativa:** atualize para o Android 13.
+ Ao entrar em um palco com um Nokia X20 que tem o Android 13, a câmera pode falhar ao abrir e uma exceção ser aberta.

  **Solução alternativa:** nenhuma.
+ Dispositivos com o chipset MediaTek Helio podem não renderizar vídeos de participantes remotos corretamente.

  **Solução alternativa:** nenhuma.
+ Em alguns dispositivos, o sistema operacional do dispositivo pode escolher um microfone diferente daquele selecionado por meio do SDK. Isso ocorre porque o SDK de Transmissão do Amazon IVS não pode controlar como a rota de áudio `VOICE_COMMUNICATION` é definida, pois ela varia de acordo com diferentes fabricantes de dispositivos.

  **Solução alternativa:** nenhuma.
+ Alguns codificadores de vídeo para Android não podem ser configurados com um tamanho de vídeo menor que 176 x 176. Configurar um tamanho menor causa um erro e impede a transmissão.

  **Solução alternativa:** não configure o tamanho do vídeo para ser menor que 176 x 176.

# Tratamento de erros no SDK de Transmissão para Android do IVS \$1 Streaming em tempo real
<a name="broadcast-android-error-handling"></a>

Esta seção é uma visão geral das condições de erros, como o SDK de Transmissão para Android para streaming em tempo real do IVS os relata à aplicação e o que uma aplicação deve fazer quando esses erros são encontrados.

## Erros fatais x Erros não fatais
<a name="broadcast-android-fatal-vs-nonfatal-errors"></a>

O objeto de erro tem um campo booleano “is fatal” de `BroadcastException`.

Em geral, erros fatais estão relacionados à conexão com o servidor do Stages (ou uma conexão não pode ser estabelecida ou foi perdida e não pode ser recuperada). O aplicativo deve recriar o estágios e se associar novamente, possivelmente com um novo token ou quando a conectividade do dispositivo se recuperar.

Erros não fatais geralmente estão relacionados ao estado de publicação/assinatura e são tratados pelo SDK, que repete a operação de publicação/assinatura.

Você pode verificar essa propriedade:

```
try {
  stage.join(...)
} catch (e: BroadcastException) {
  If (e.isFatal) { 
    // the error is fatal
```

## Erros de entrada
<a name="broadcast-android-stage-join-errors"></a>

### Token malformado
<a name="broadcast-android-stage-join-errors-malformed-token"></a>

Isso acontece quando o token de estágio está malformado.

O SDK gera uma exceção Java de uma chamada para `stage.join`, com o código de erro = 1000 e fatal = true.

**Ação**: crie um token válido e tente entrar novamente.

### Token expirado
<a name="broadcast-android-stage-join-errors-expired-token"></a>

Isso acontece quando o token de estágio expirou.

O SDK gera uma exceção Java de uma chamada para `stage.join`, com o código de erro = 1001 e fatal = true.

**Ação**: crie um novo token e tente entrar novamente.

### Token inválido ou revogado
<a name="broadcast-android-stage-join-errors-invalid-token"></a>

Isso acontece quando o token do estágio não está malformado, mas é rejeitado pelo servidor do Stages. Isso é relatado de forma assíncrona por meio do renderizador de estágio fornecido pela aplicação.

O SDK chama `onConnectionStateChanged` com uma exceção, com o código de erro = 1026 e fatal = true.

**Ação**: crie um token válido e tente entrar novamente.

### Erros de rede para entrada inicial
<a name="broadcast-android-stage-join-errors-network-initial-join"></a>

Isso acontece quando o SDK não consegue entrar em contato com o servidor do Stages para estabelecer uma conexão. Isso é relatado de forma assíncrona por meio do renderizador de estágio fornecido pela aplicação.

O SDK chama `onConnectionStateChanged` com uma exceção, com o código de erro = 1300 e fatal = true.

**Ação**: aguarde até que a conectividade do dispositivo se recupere e tente entrar novamente.

### Erros de rede quando já ingressado
<a name="broadcast-android-stage-join-errors-network-already-joined"></a>

Se a conexão de rede do dispositivo cair, o SDK poderá perder sua conexão com os servidores de Estágios. Isso é relatado de forma assíncrona por meio do renderizador de estágio fornecido pela aplicação.

O SDK chama `onConnectionStateChanged` com uma exceção, com o código de erro = 1300 e fatal = true.

**Ação**: aguarde até que a conectividade do dispositivo se recupere e tente entrar novamente.

## Erros de publicação/assinatura
<a name="broadcast-android-publish-subscribe-errors"></a>

### Inicial
<a name="broadcast-android-publish-subscribe-errors-initial"></a>

Há vários erros:
+ MultihostSessionOfferCreationFailPublish (1.020)
+ MultihostSessionOfferCreationFailSubscribe (1.021)
+ MultihostSessionNoIceCandidates (1.022)
+ MultihostSessionStageAtCapacity (1.024)
+ SignallingSessionCannotRead (1.201)
+ SignallingSessionCannotSend (1.202)
+ SignallingSessionBadResponse (1.203)

Estes são relatados de forma assíncrona por meio do renderizador de estágio fornecido pela aplicação.

O SDK repete a operação por um número limitado de vezes. as novas tentativas, o estado de publicação/assinatura é `ATTEMPTING_PUBLISH` / `ATTEMPTING_SUBSCRIBE`. Se as tentativas de repetição forem bem-sucedidas, o estado mudará para `PUBLISHED` / `SUBSCRIBED`.

O SDK chama `onError` com o código de erro relevante e fatal = false.

**Ação**: nenhuma ação é necessária, pois o SDK tenta novamente de forma automática. Opcionalmente, a aplicação pode atualizar a estratégia para forçar mais tentativas.

### Já estabelecido, em seguida reprovar
<a name="broadcast-android-publish-subscribe-errors-established"></a>

Uma publicação ou assinatura pode falhar depois de ser estabelecida, provavelmente devido a um erro de rede. O código de erro para “conexão de peer perdida devido a um erro de rede” é 1400.

Isso é relatado de forma assíncrona por meio do renderizador de estágio fornecido pela aplicação.

O SDK tenta novamente a operação de publicação/assinatura. as novas tentativas, o estado de publicação/assinatura é `ATTEMPTING_PUBLISH` / `ATTEMPTING_SUBSCRIBE`. Se as tentativas de repetição forem bem-sucedidas, o estado mudará para `PUBLISHED` / `SUBSCRIBED`.

O SDK chama `onError` com o código de erro = 1400 e fatal = false.

**Ação**: nenhuma ação é necessária, pois o SDK tenta novamente de forma automática. Opcionalmente, a aplicação pode atualizar a estratégia para forçar mais tentativas. Se houver perda total de conectividade, é provável que a conexão com o Stages também falhe.