

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

O SDK de Transmissão do streaming em tempo real do Amazon Interactive Video Services (IVS) é destinado aos desenvolvedores que estão criando aplicações com o Amazon IVS. Este SDK foi projetado para aproveitar a arquitetura do Amazon IVS e receberá continuamente melhorias e novos recursos, juntamente com o Amazon IVS. Como SDK de Transmissão nativo, foi projetado para minimizar o impacto na performance em sua aplicação e nos dispositivos com os quais seus usuários acessam sua aplicação.

Observe que o SDK de transmissão é usado para enviar e receber vídeos, ou seja, você usa o mesmo SDK para hosts e espectadores. Nenhum SDK do reprodutor separado é necessário.

Sua aplicação pode aproveitar os principais recursos do Amazon IVS Broadcast SDK:
+ **Transmissões de alta qualidade**: o SDK de Transmissão oferece suporte a transmissões de alta qualidade. Capture vídeos usando sua câmera e codifique-os em até 720p.
+ **Ajustes de taxas de bits automáticos**: como os usuários de smartphones são móveis, suas condições de rede podem mudar ao longo de uma transmissão. O SDK de Transmissão do Amazon IVS ajusta automaticamente a taxa de bits de vídeo para acomodar as condições de rede em alteração.
+ **Compatível com retrato e paisagem**: não importa como seus usuários seguram os dispositivos, a imagem é exibida na posição certa e dimensionada corretamente. O SDK de Transmissão oferece suporte aos formatos de tela de retrato e paisagem. Ele gerencia automaticamente a proporção quando os usuários rodam o dispositivo para uma orientação diferente da configurada.
+ **Transmissões seguras**: as transmissões dos usuários são criptografadas usando TLS, para que eles possam manter as transmissões seguras.
+ **Dispositivos de áudio externos**: o Amazon IVS Broadcast SDK oferece suporte a conectores de áudio, USB e microfones externos Bluetooth SCO.

## Requisitos da plataforma
<a name="broadcast-platform-requirements"></a>

### Plataformas nativas
<a name="broadcast-native-platforms"></a>


| Plataforma | Versões compatíveis | 
| --- | --- | 
| Android |  Versão 9.0\$1: observe que os clientes podem desenvolver com a versão 6.0\$1, mas não poderão usar a funcionalidade de streaming em tempo real.  | 
| iOS |  14\$1  | 

O IVS é compatível com pelo menos 4 versões principais do iOS e 6 versões principais do Android. Nosso suporte à versão atual pode ir além desses mínimos. Os clientes serão notificados por meio das notas de lançamento do SDK pelo menos 3 meses antes do fim do suporte para uma versão principal.

### Navegadores desktop
<a name="browser-desktop"></a>


| Navegador | Plataformas com suporte | Versões compatíveis | 
| --- | --- | --- | 
| Chrome | Windows, macOS | Duas versões principais (versão anterior atual e mais recente) | 
| Firefox | Windows, macOS | Duas versões principais (versão anterior atual e mais recente) | 
| Borda | Windows 8.1\$1 | Duas versões principais (versão anterior atual e mais recente) Exclui o Edge Legacy | 
| Safari | macOS | Duas versões principais (versão anterior atual e mais recente) | 

### Navegadores móveis (iOS e Android)
<a name="browser-mobile"></a>


| Navegador | Plataformas com suporte | Versões compatíveis | 
| --- | --- | --- | 
| Chrome | iOS, Android | Duas versões principais (versão anterior atual e mais recente) | 
| Firefox | Android | Duas versões principais (versão anterior atual e mais recente) | 
| Safari | iOS | Duas versões principais (versão anterior atual e mais recente) | 

#### Limitações conhecidas
<a name="browser-mobile-limitations"></a>
+ Em todos os navegadores da Web para dispositivos móveis, recomendamos a publicação/assinatura com no máximo três editores simultâneos, devido a restrições de desempenho que causam artefatos de vídeo e telas pretas. Se você precisar de mais publicadores, configure a [publicação e a inscrição somente de áudio](web-publish-subscribe.md#web-publish-subscribe-concepts-strategy-updates).
+ Não recomendamos compor um palco e transmiti-lo para um canal no Android móvel na Web, devido a considerações de desempenho e possíveis falhas. Se a funcionalidade de transmissão for necessária, integre o [SDK de Transmissão do streaming em tempo real do IVS para Android](broadcast-android.md).

## Visualizações da Web
<a name="broadcast-webviews"></a>

O SDK de Transmissão da Web não oferece suporte para visualizações da Web ou de ambientes semelhantes à Web (como TVs, consoles etc.). Para implementações móveis, consulte o Guia do SDK de transmissão do streaming em tempo real para [Android](broadcast-android.md) e para [iOS](broadcast-ios.md).

## Acesso ao dispositivo necessário
<a name="broadcast-device-access"></a>

O SDK de Transmissão necessita de acesso às câmeras e microfones do dispositivo, tanto as incorporadas no dispositivo como as conectadas por Bluetooth, USB ou conector de áudio.

## Suporte
<a name="broadcast-support"></a>

O SDK de transmissão é aprimorado continuamente. Consulte [Notas de release do Amazon IVS](release-notes.md) para ver as versões disponíveis e problemas corrigidos. Se for apropriado, antes de entrar em contato com o suporte, atualize sua versão do SDK de Transmissão e veja se isso resolve seu problema.

### Versionamento
<a name="broadcast-support-versioning"></a>

Os SDKs de transmissão do Amazon IVS usam [versionamento semântico](https://semver.org/).

Para esta discussão, suponha que:
+ A versão mais recente é 4.1.3.
+ A versão mais recente da versão principal anterior é 3.2.4.
+ A versão mais recente da versão 1.x é 1.5.6.

Novos recursos compatíveis com versões anteriores são adicionados como versões secundárias da versão mais recente. Nesse caso, o próximo conjunto de novos recursos vai ser adicionado como versão 4.2.0.

Compatíveis com versões anteriores, pequenas correções de bugs são adicionadas como lançamentos de patch da versão mais recente. Aqui, o próximo conjunto de pequenas correções de bugs vai ser adicionado como versão 4.1.4.

Compatíveis com versões anteriores, as principais correções de bugs são tratadas de forma diferente; estas são adicionadas a várias versões:
+ Versão do patch da versão mais recente. Aqui, esta é a versão 4.1.4.
+ Lançamento do patch da versão secundária anterior. Aqui, esta é a versão 3.2.5.
+ Versão do patch da versão 1.x mais recente. Aqui, esta é a versão 1.5.7.

As principais correções de bugs são definidas pela equipe de produtos do Amazon IVS. Exemplos típicos são atualizações de segurança críticas e outras correções selecionadas necessárias para os clientes.

**Observação:** nos exemplos acima, versões lançadas incrementam sem ignorar nenhum número (por exemplo, de 4.1.3 para 4.1.4). Na realidade, um ou mais números de patch podem permanecer internos e não ser liberados, de modo que a versão lançada pode ser incrementada de 4.1.3 para, digamos, 4.1.6.

# SDK de Transmissão do IVS: Guia para a Web \$1 Streaming em tempo Real
<a name="broadcast-web"></a>

O SDK de Transmissão do streaming em tempo real do IVS para Web fornece aos desenvolvedores as ferramentas necessárias para criar experiências interativas e em tempo real na Web. Esse SDK destina-se a desenvolvedores que estão criando aplicações para a Web com o Amazon IVS.

O SDK de Transmissão da Web possibilita que os participantes enviem e recebam vídeos. 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 Web

**Versão mais recente do SDK de transmissão para a Web:** 1.33.0 ([Notas de release](https://docs.aws.amazon.com/ivs/latest/RealTimeUserGuide/release-notes.html#mar12-26-broadcast-web-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 a Web, consulte [https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference). Verifique se a versão mais atual do SDK está selecionada.

**Código de exemplo**: os exemplos abaixo são um bom lugar para aprender rapidamente a usar o SDK:
+ [Reprodução simples](https://codepen.io/amazon-ivs/pen/RNwVBRK)
+ [Publicação e inscrição simples](https://codepen.io/amazon-ivs/pen/ZEqgrpo)
+ [Demonstração abrangente de colaboração em tempo real do React](https://github.com/aws-samples/amazon-ivs-real-time-collaboration-web-demo/tree/main)

**Requisitos de plataforma**: consulte [SDK de Transmissão do Amazon IVS](https://docs.aws.amazon.com//ivs/latest/RealTimeUserGuide/broadcast.html) para obter uma lista das plataformas compatíveis.

**Observação:** publicar de um navegador é conveniente para os usuários finais, pois não requer a instalação de software adicional. No entanto, a publicação baseada em navegador está sujeita às restrições e à variabilidade desse ambiente. Caso você precise priorizar a estabilidade (por exemplo, para streaming de eventos), geralmente recomendamos publicar de fontes que não sejam o navegador (por exemplo, OBS Studio ou outros codificadores dedicados), que costumam ter acesso direto aos recursos do sistema e evitam limitações do navegador. Para saber mais sobre as opções de publicação fora do navegador, consulte a documentação do [Stream Ingest](rt-stream-ingest.md).

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

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

## Importações
<a name="broadcast-web-getting-started-imports"></a>

Os blocos de criação para tempo real estão localizados em um namespace diferente dos módulos de transmissão raiz.

### Uso de uma etiqueta de script
<a name="broadcast-web-getting-started-imports-script"></a>

O SDK de Transmissão da Web é distribuído como uma biblioteca de JavaScript e pode ser recuperado em [https://web-broadcast.live-video.net/1.33.0/amazon-ivs-web-broadcast.js](https://web-broadcast.live-video.net/1.33.0/amazon-ivs-web-broadcast.js).

As classes e as enumerações definidas nos exemplos abaixo podem ser encontradas no objeto global `IVSBroadcastClient`:

```
const { Stage, SubscribeType } = IVSBroadcastClient;
```

### Uso de npm
<a name="broadcast-web-getting-started-imports-npm"></a>

Para instalar o pacote `npm` 

```
npm install amazon-ivs-web-broadcast
```

As classes, as enumerações e os tipos também podem ser importados do módulo do pacote:

```
import { Stage, SubscribeType, LocalStageStream } from 'amazon-ivs-web-broadcast'
```

### Suporte para renderização do servidor
<a name="broadcast-web-getting-started-imports-server-side-rendering"></a>

A biblioteca dos palcos do SDK de Transmissão para a Web não pode ser carregada em um contexto do servidor, pois faz referência às primitivas do navegador necessárias para o funcionamento da biblioteca quando carregada. Para contornar isso, carregue a biblioteca dinamicamente, conforme demonstrado na [Demonstração de transmissão da Web usando Next e React](https://github.com/aws-samples/amazon-ivs-broadcast-web-demo/blob/main/hooks/useBroadcastSDK.js#L26-L31).

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

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

Aqui está um exemplo de função que mostra como é possível solicitar e capturar permissões para ambos os dispositivos de áudio e vídeo:

```
async function handlePermissions() {
   let permissions = {
       audio: false,
       video: false,
   };
   try {
       const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
       for (const track of stream.getTracks()) {
           track.stop();
       }
       permissions = { video: true, audio: true };
   } catch (err) {
       permissions = { video: false, audio: false };
       console.error(err.message);
   }
   // If we still don't have permissions after requesting them display the error message
   if (!permissions.video) {
       console.error('Failed to get video permissions.');
   } else if (!permissions.audio) {
       console.error('Failed to get audio permissions.');
   }
}
```

Para obter informações adicionais, consulte a [API de permissões](https://developer.mozilla.org/en-US/docs/Web/API/Permissions_API) e [MediaDevices.getUserMedia()](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia).

## Listar dispositivos disponíveis
<a name="broadcast-web-request-list-devices"></a>

Para ver quais dispositivos estão disponíveis para captura, consulte o método [MediaDevices.enumerateDevices()](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/enumerateDevices) do navegador:

```
const devices = await navigator.mediaDevices.enumerateDevices();
window.videoDevices = devices.filter((d) => d.kind === 'videoinput');
window.audioDevices = devices.filter((d) => d.kind === 'audioinput');
```

## Recuperar um MediaStream de um dispositivo
<a name="broadcast-web-retrieve-mediastream"></a>

Depois de adquirir a lista de dispositivos disponíveis, é possível recuperar um stream de qualquer número de dispositivos. Por exemplo, é possível usar o método `getUserMedia()` para recuperar um stream de uma câmera.

Se você quiser especificar de qual dispositivo capturar o stream, é possível definir explicitamente o `deviceId` na seção `audio` ou `video` das restrições de mídia. Como alternativa, é possível omitir o `deviceId` e fazer com que os usuários selecionem seus dispositivos no prompt do navegador.

Também é possível especificar uma resolução de câmera ideal usando as restrições `width` e `height`. (Leia mais sobre essas restrições [aqui](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints#properties_of_video_tracks).) O SDK aplica automaticamente restrições de largura e altura que correspondem à resolução máxima de transmissão, mas é melhor você mesmo também aplicá-las para garantir que a proporção da fonte não seja alterada depois que ela for adicionada ao SDK.

Para streaming em tempo real, certifique-se de que a mídia esteja restrita à resolução de 720p. Especificamente, seus valores de restrição `getUserMedia` e `getDisplayMedia` para largura e altura não devem exceder 921600 (1280\$1720) quando multiplicados juntos. 

```
const videoConfiguration = {
  maxWidth: 1280,
  maxHeight: 720,
  maxFramerate: 30,
}

window.cameraStream = await navigator.mediaDevices.getUserMedia({
   video: {
       deviceId: window.videoDevices[0].deviceId,
       width: {
           ideal: videoConfiguration.maxWidth,
       },
       height: {
           ideal:videoConfiguration.maxHeight,
       },
   },
});
window.microphoneStream = await navigator.mediaDevices.getUserMedia({
   audio: { deviceId: window.audioDevices[0].deviceId },
});
```

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

Este documento descreve as etapas envolvidas na publicação e assinatura de um estágio usando o SDK de Transmissão na Web para streaming em tempo real do IVS.

## Conceitos
<a name="web-publish-subscribe-concepts"></a>

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

```
const stage = new Stage(token, strategy)

try {
   await stage.join();
} catch (error) {
   // handle join exception
}

stage.leave();
```

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

A interface `StageStrategy` 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`, `shouldPublishParticipant` e `stageStreamsToPublish`. Todas serão discutidas abaixo.

Para usar uma estratégia definida, passe-a para o `Stage` de criação. Veja a seguir um exemplo completo de uma aplicação que usa uma estratégia para publicar a webcam de um participante no palco e realizar a inscrição para todos os participantes. A finalidade de cada função de estratégia necessária é explicada em detalhes nas seções subsequentes.

```
const devices = await navigator.mediaDevices.getUserMedia({ 
   audio: true,
   video: {
        width: { max: 1280 },
        height: { max: 720 },
    } 
});
const myAudioTrack = new LocalStageStream(devices.getAudioTracks()[0]);
const myVideoTrack = new LocalStageStream(devices.getVideoTracks()[0]);

// Define the stage strategy, implementing required functions
const strategy = {
   audioTrack: myAudioTrack,
   videoTrack: myVideoTrack,

   // optional
   updateTracks(newAudioTrack, newVideoTrack) {
      this.audioTrack = newAudioTrack;
      this.videoTrack = newVideoTrack;
   },

   // required
   stageStreamsToPublish() {
      return [this.audioTrack, this.videoTrack];
   },

   // required
   shouldPublishParticipant(participant) {
      return true;
   },

   // required
   shouldSubscribeToParticipant(participant) {
      return SubscribeType.AUDIO_VIDEO;
   }
};

// Initialize the stage and start publishing
const stage = new Stage(token, strategy);
await stage.join();


// To update later (e.g. in an onClick event handler)
strategy.updateTracks(myNewAudioTrack, myNewVideoTrack);
stage.refreshStrategy();
```

#### Como se inscrever como participante
<a name="web-publish-subscribe-concepts-strategy-participants"></a>

```
shouldSubscribeToParticipant(participant: StageParticipantInfo): SubscribeType
```

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 ao emitir eventos durante todo o processo.

Veja a seguir uma amostra de implementação:

```
const strategy = {
   
   shouldSubscribeToParticipant: (participant) => {
      return SubscribeType.AUDIO_VIDEO;
   }

   // ... other strategy functions
}
```

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. Por exemplo, suponha que o aplicativo forneça um atributo `role` ao criar o token com CreateParticipantToken. A aplicação pode usar a propriedade `attributes` em `StageParticipantInfo` para se inscrever, de forma seletiva, como participante com base nos recursos fornecidos pelo servidor:

```
const strategy = {
   
   shouldSubscribeToParticipant(participant) {
      switch (participant.attributes.role) {
         case 'moderator':
            return SubscribeType.NONE;
         case 'guest':
            return SubscribeType.AUDIO_VIDEO;
         default:
            return SubscribeType.NONE;
      }
   }
   // . . . other strategies properties
}
```

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

```
subscribeConfiguration(participant: StageParticipantInfo): SubscribeConfiguration
```

Se um participante remoto estiver fazendo uma assinatura (consulte [Assinatura de participantes](#web-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:

```
const strategy = {
   
   subscribeConfiguration: (participant) => {
      return {
         jitterBuffer: {
            minDelay: JitterBufferMinDelay.MEDIUM
         }  
      }

   // ... other strategy functions
}
```

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

```
shouldPublishParticipant(participant: StageParticipantInfo): boolean
```

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:

```
const strategy = {
   
   shouldPublishParticipant: (participant) => {
      return true;
   }

   // . . . other strategies properties
}
```

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

```
stageStreamsToPublish(): LocalStageStream[];
```

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

#### Como atualizar a estratégia
<a name="web-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 `shouldPublishParticipant` (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 observa que o valor `shouldPublishParticipant` foi alterado, ele inicia 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 modificará o 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.

### Eventos
<a name="web-publish-subscribe-concepts-events"></a>

Uma instância `Stage` é um emissor de eventos. Ao usar `stage.on()`, o estado do palco é comunicado à aplicação de host. Geralmente, as atualizações na interface do usuário da aplicação de host podem ser totalmente apoiadas pelos eventos. Os eventos são os seguintes:

```
stage.on(StageEvents.STAGE_CONNECTION_STATE_CHANGED, (state) => {})
stage.on(StageEvents.STAGE_PARTICIPANT_JOINED, (participant) => {})
stage.on(StageEvents.STAGE_PARTICIPANT_LEFT, (participant) => {})
stage.on(StageEvents.STAGE_PARTICIPANT_PUBLISH_STATE_CHANGED, (participant, state) => {})
stage.on(StageEvents.STAGE_PARTICIPANT_SUBSCRIBE_STATE_CHANGED, (participant, state) => {})
stage.on(StageEvents.STAGE_PARTICIPANT_STREAMS_ADDED, (participant, streams) => {})
stage.on(StageEvents.STAGE_PARTICIPANT_STREAMS_REMOVED, (participant, streams) => {})
stage.on(StageEvents.STAGE_STREAM_ADAPTION_CHANGED, (participant, stream, isAdapting) => ())
stage.on(StageEvents.STAGE_STREAM_LAYERS_CHANGED, (participant, stream, layers) => ())
stage.on(StageEvents.STAGE_STREAM_LAYER_SELECTED, (participant, stream, layer, reason) => ())
stage.on(StageEvents.STAGE_STREAM_MUTE_CHANGED, (participant, stream) => {})
stage.on(StageEvents.STAGE_STREAM_SEI_MESSAGE_RECEIVED, (participant, stream) => {})
```

Para a maioria desses eventos, o correspondente `ParticipantInfo` é fornecido.

Não é esperado que as informações fornecidas pelos eventos impactem os valores de retorno da estratégia. Por exemplo, não se espera que o valor de retorno de `shouldSubscribeToParticipant` seja alterado quando `STAGE_PARTICIPANT_PUBLISH_STATE_CHANGED` 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.

## Publicação de uma transmissão de mídia
<a name="web-publish-subscribe-publish-stream"></a>

Dispositivos locais, como microfones e câmeras, são recuperados usando as mesmas etapas descritas acima em [Recuperar um MediaStream de um dispositivo](broadcast-web-getting-started.md#broadcast-web-retrieve-mediastream). No exemplo, usamos `MediaStream` para criar uma lista de objetos `LocalStageStream` usados ​​para publicação pelo SDK:

```
try {
    // Get stream using steps outlined in document above
    const stream = await getMediaStreamFromDevice();

    let streamsToPublish = stream.getTracks().map(track => {
        new LocalStageStream(track)
    });

    // Create stage with strategy, or update existing strategy
    const strategy = {
        stageStreamsToPublish: () => streamsToPublish
    }
}
```

## Publicação de um compartilhamento de tela
<a name="web-publish-subscribe-publish-screenshare"></a>

Geralmente, as aplicações precisam publicar um compartilhamento de tela além da câmera da Web do usuário. A publicação de um compartilhamento de tela exige a criação de um token adicional para o palco, especificamente para a publicação da mídia do compartilhamento de tela. Use `getDisplayMedia` e restrinja a resolução a um máximo de 720p. Depois disso, as etapas são semelhantes à publicação de uma câmera no palco.

```
// Invoke the following lines to get the screenshare's tracks
const media = await navigator.mediaDevices.getDisplayMedia({
   video: {
      width: {
         max: 1280,
      },
      height: {
         max: 720,
      }
   }
});
const screenshare = { videoStream: new LocalStageStream(media.getVideoTracks()[0]) };
const screenshareStrategy = {
   stageStreamsToPublish: () => {
      return [screenshare.videoStream];
   },
   shouldPublishParticipant: (participant) => {
      return true;
   },
   shouldSubscribeToParticipant: (participant) => {
      return SubscribeType.AUDIO_VIDEO;
   }
}
const screenshareStage = new Stage(screenshareToken, screenshareStrategy);
await screenshareStage.join();
```

## Exibição e remoção de participantes
<a name="web-publish-subscribe-participants"></a>

Após a conclusão da inscrição, você recebe uma matriz de objetos `StageStream` por meio do evento `STAGE_PARTICIPANT_STREAMS_ADDED`. O evento também fornece informações do participante para ajudar na exibição de transmissões de mídia:

```
stage.on(StageEvents.STAGE_PARTICIPANT_STREAMS_ADDED, (participant, streams) => {
    let streamsToDisplay = streams;

    if (participant.isLocal) {
        // Ensure to exclude local audio streams, otherwise echo will occur
        streamsToDisplay = streams.filter(stream => stream.streamType === StreamType.VIDEO)
    }

    // Create or find video element already available in your application
    const videoEl = getParticipantVideoElement(participant.id);

    // Attach the participants streams
    videoEl.srcObject = new MediaStream();
    streamsToDisplay.forEach(stream => videoEl.srcObject.addTrack(stream.mediaStreamTrack));
})
```

Quando um participante interrompe as publicações ou cancela a inscrição de uma transmissão, a função `STAGE_PARTICIPANT_STREAMS_REMOVED` é 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 do DOM.

`STAGE_PARTICIPANT_STREAMS_REMOVED` é 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 `STAGE_PARTICIPANT_STREAMS_REMOVED` é 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="web-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 `stageStreamsToPublish`.

**Importante**: se uma nova instância de objeto `LocalStageStream` for retornada por `stageStreamsToPublish` 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="web-publish-subscribe-mute-state"></a>

Quando os participantes alteram o estado mudo do vídeo ou do áudio, o evento `STAGE_STREAM_MUTE_CHANGED` é acionado com uma lista de transmissões que foram alteradas. Use a propriedade `isMuted` na `StageStream` para atualizar a IU adequadamente:

```
stage.on(StageEvents.STAGE_STREAM_MUTE_CHANGED, (participant, stream) => {
   if (stream.streamType === 'video' && stream.isMuted) {
       // handle UI changes for video track getting muted
   }
})
```

Além disso, você pode consultar [StageParticipantInfo](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference#stageparticipantinfo) para saber se o áudio ou o vídeo estão silenciados:

```
stage.on(StageEvents.STAGE_STREAM_MUTE_CHANGED, (participant, stream) => {
   if (participant.videoStopped || participant.audioMuted) {
       // handle UI changes for either video or audio
   }
})
```

## Obtenção de estatísticas WebRTC
<a name="web-publish-subscribe-webrtc-stats"></a>

O método `requestQualityStats()` fornece acesso a estatísticas detalhadas do WebRTC para fluxos locais e remotos. Isso está disponível nos objetos LocalStageStream e RemoteStageStream. Ele retorna métricas de qualidade abrangentes, incluindo qualidade de rede, estatísticas de pacotes, informações de taxa de bits e métricas relacionadas a quadros.

Este é um método assíncrono com o qual estatísticas podem ser recuperadas utilizando await ou encadeando uma promessa. Ele retorna `undefined` quando as estatísticas não estão disponíveis; por exemplo, o fluxo não está ativo ou as estatísticas internas não estão disponíveis. Se as estatísticas estiverem disponíveis, e dependendo da transmissão (remota ou local, vídeo ou áudio), o método retornará um objeto [LocalVideoStats](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/interfaces/LocalVideoStats), [LocalAudioStats](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/interfaces/LocalAudioStats), [RemoteVideoStats](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/interfaces/RemoteVideoStats) ou [RemoteAudioStats](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/interfaces/RemoteAudioStats).

Observe que, para transmissões de vídeo com transmissão simultânea, a matriz contém vários objetos estatísticos (um por camada).

**Práticas recomendadas**
+ Frequência de pesquisa: chame `requestQualityStats()` em intervalos razoáveis (1 a 5 segundos) para evitar impacto no desempenho
+ Tratamento de erros: sempre verifique se o valor retornado é `undefined` antes do processamento
+ Gerenciamento de memória: intervalos e tempos limite claros quando os fluxos não são mais necessários
+ Qualidade da rede: use `networkQuality` para feedback do usuário sobre possíveis degradações causadas pela rede. Para obter detalhes, consulte [NetworkQuality](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/enumerations/NetworkQuality).

**Exemplo de uso**

```
// For local streams
const localStats = await localVideoStream.requestQualityStats();
const audioStats = await localAudioStream.requestQualityStats();

// For remote streams
const remoteVideoStats = await remoteVideoStream.requestQualityStats();
const remoteAudioStats = await remoteAudioStream.requestQualityStats();

// Example: Monitor stats every 10 seconds
const statsInterval = setInterval(async () => {
   const stats = await localVideoStream.requestQualityStats();
   if (stats) {
      // Note: If simulcast is enabled, you may receive multiple 
      // stats records for each layer
      stats.forEach(layer => {
         const rid = layer.rid || 'default';
         console.log(`Layer ${rid}:`, {
            active: layer.active,
            networkQuality: layer.networkQuality,
            packetsSent: layer.packetsSent,
            bytesSent: layer.bytesSent,
            resolution: `${layer.frameWidth}x${layer.frameHeight}`,
            fps: layer.framesPerSecond
         });
      });
   }
}, 10000);
```

## Otimização de mídia
<a name="web-publish-subscribe-optimizing-media"></a>

É recomendável limitar as chamadas `getUserMedia` e `getDisplayMedia` para as seguintes restrições com a finalidade de obter a melhor performance:

```
const CONSTRAINTS = {
    video: {
        width: { ideal: 1280 }, // Note: flip width and height values if portrait is desired
        height: { ideal: 720 },
        framerate: { ideal: 30 },
    },
};
```

É possível restringir ainda mais a mídia por meio de opções adicionais passadas para o construtor `LocalStageStream`:

```
const localStreamOptions = {
    minBitrate?: number;
    maxBitrate?: number;
    maxFramerate?: number;
    simulcast: {
        enabled: boolean
    }
}
const localStream = new LocalStageStream(track, localStreamOptions)
```

No código acima:
+ `minBitrate` define uma taxa de bits mínima que o navegador deve usar. No entanto, um stream de vídeo de baixa complexidade pode impulsionar o codificador a diminuir a taxa de bits.
+ `maxBitrate` define uma taxa de bits máxima que o navegador não deve exceder para este stream.
+ `maxFramerate` define uma taxa de quadros máxima que o navegador não deve exceder para este stream.
+ A opção `simulcast` pode ser usada somente em navegadores baseados no Chromium. Ela possibilita o envio de três camadas de representação do stream.
  + Isso permite que o servidor escolha qual representação enviar aos outros participantes, com base em suas limitações de rede.
  + Quando `simulcast` é especificada junto com um valor `maxBitrate` e/ou `maxFramerate`, espera-se que a camada de representação mais alta seja configurada considerando esses valores, desde que `maxBitrate` não seja inferior ao valor de `maxBitrate` padrão para a segunda camada mais alta do SDK interno de 900 kbps.
  + Se `maxBitrate` for especificada com um valor muito inferior em comparação com o valor padrão da segunda camada mais alta, a `simulcast` será desabilitada.
  + A `simulcast` não pode ser ativada e desativada sem republicar a mídia por meio de uma combinação de `shouldPublishParticipant` retornar `false`, chamar `refreshStrategy`, fazer `shouldPublishParticipant` retornar `true` e chamar `refreshStrategy` novamente.

## Obtenção de atributos do participante
<a name="web-publish-subscribe-participant-attributes"></a>

Se você especificar atributos na solicitação da operação `CreateParticipantToken`, poderá visualizar os atributos nas propriedades `StageParticipantInfo`:

```
stage.on(StageEvents.STAGE_PARTICIPANT_JOINED, (participant) => {
   console.log(`Participant ${participant.id} info:`, participant.attributes);
})
```

## Informações complementares aprimoradas (SEI)
<a name="web-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. Ele pode ser usado ao publicar e assinar streams de vídeo H.264. Não é garantido que as cargas úteis de SEI cheguem aos assinantes, especialmente em condições de rede pouco satisfatórias. Como a carga útil do SEI armazena dados diretamente na estrutura do quadro H.264, esse recurso não pode ser aproveitado para streams somente de áudio.

### Inserindo cargas úteis de SEI
<a name="sei-attributes-inserting-sei-payloads"></a>

Os clientes de publicação podem inserir cargas úteis de SEI em um stream de preparação que está será publicado, configurando o LocalStageStream de vídeo para habilitar `inBandMessaging` e, posteriormente, invocando o método `insertSeiMessage`. Observe que a habilitação de `inBandMessaging` aumenta o uso de memória do SDK.

As cargas úteis devem ser do tipo [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer). A carga útil deve ter mais de 0 KB e menos de 1 KB. O número de mensagens de SEI inseridas não deve exceder 10 KB por segundo.

```
const config = {
    inBandMessaging: { enabled: true }
};
const vidStream = new LocalStageStream(videoTrack, config);
const payload = new TextEncoder().encode('hello world').buffer;
vidStream.insertSeiMessage(payload);
```

#### Repetir cargas úteis de SEI
<a name="sei-attributes-repeating-sei-payloads"></a>

Opcionalmente, forneça um `repeatCount` para repetir a inserção de cargas úteis de SEI nos próximos N quadros enviados. Isso pode ser útil para reduzir a perda inerente que pode ocorrer devido ao protocolo de transporte UDP subjacente usado para enviar vídeo. Esse valor deve ser de 0 a 30. Os clientes destinatários devem ter uma lógica para desduplicar a mensagem.

```
vidStream.insertSeiMessage(payload, { repeatCount: 5 }); // Optional config, repeatCount must be between 0 and 30
```

### Ler cargas úteis de SEI
<a name="sei-attributes-reading-sei-payloads"></a>

Os clientes assinantes podem ler as cargas úteis de SEI de um publicador que esteja publicando vídeo H.264, se presente, configurando o(s) assinante(s) `SubscribeConfiguration` para habilitar `inBandMessaging` e ouvir o evento `StageEvents.STAGE_STREAM_SEI_MESSAGE_RECEIVED`, conforme mostrado no exemplo a seguir:

```
const strategy = {
    subscribeConfiguration: (participant) => {
        return {
            inBandMessaging: {
                enabled: true
            }
        }
    }
    // ... other strategy functions
}

stage.on(StageEvents.STAGE_STREAM_SEI_MESSAGE_RECEIVED, (participant, seiMessage) => {
    console.log(seiMessage.payload, seiMessage.uuid);
});
```

## Codificação em camadas com transmissão simultânea
<a name="web-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 alterem essas camadas de forma dinâmica ou manual. O atributo é descrito mais detalhadamente no documento [Otimizações de streaming](https://docs.aws.amazon.com//ivs/latest/RealTimeUserGuide/real-time-streaming-optimization.html).

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

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

```
// Enable Simulcast
let cameraStream = new LocalStageStream(cameraDevice, {
   simulcast: { enabled: true }
})
```

Dependendo da resolução de entrada do seu dispositivo de câmera, 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:

```
import { SimulcastLayerPresets } from ‘amazon-ivs-web-broadcast’

// Enable Simulcast
let cameraStream = new LocalStageStream(cameraDevice, {
   simulcast: {
      enabled: true,
      layers: [
         SimulcastLayerPresets.DEFAULT_720,
          SimulcastLayerPresets.DEFAULT_360,
          SimulcastLayerPresets.DEFAULT_180, 
   }
})
```

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 as seguintes propriedades obrigatórias:
+ `height: number;`
+ `width: number;`
+ `maxBitrateKbps: number;`
+ `maxFramerate: number;`

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

```
import { SimulcastLayerPresets } from ‘amazon-ivs-web-broadcast’

const custom720pLayer = {
   ...SimulcastLayerPresets.DEFAULT_720,
   maxFramerate: 15,
}

const custom360pLayer = {
       maxBitrateKbps: 600,
       maxFramerate: 15,
       width: 640,
       height: 360,
}

// Enable Simulcast
let cameraStream = new LocalStageStream(cameraDevice, {
   simulcast: {
      enabled: true,
      layers: [
         custom720pLayer,
         custom360pLayer, 
   }
})
```

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="web-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="web-layered-encoding-simulcast-layer-quality-preference"></a>

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

```
const strategy = {
    subscribeConfiguration: (participant) => {
        return {
            simulcast: {
                initialLayerPreference: InitialLayerPreference.LOWEST_QUALITY
            }
        }
    }
    // ... other strategy functions
}
```

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 `initialLayerPreference`) 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="web-layered-encoding-simulcast-preferred-layer"></a>

Depois que um fluxo for iniciado, você poderá usar o método de estratégia `preferredLayerForStream `. Esse método de estratégia expõe o participante e as informações da transmissão.

O método de estratégia pode ser retornado com o seguinte:
+ O objeto de camada diretamente, com base no que `RemoteStageStream.getLayers` retorna 
+ A string do rótulo do objeto de camada, com base em `StageStreamLayer.label`
+ Undefined ou 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:

```
const strategy = {
    preferredLayerForStream: (participant, stream) => {
        return stream.getLowestQualityLayer();
    }
    // ... other strategy functions
}
```

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 fictícia que representa o possível estado da aplicação.

```
const strategy = {
    preferredLayerForStream: (participant, stream) => {
        if (appState.isAutoMode) {
            return null;
        } else {
            return appState.layerChoice
        }
    }
    // ... other strategy functions
}
```

### Opção 3: auxiliares da camada RemoteStageStream
<a name="web-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 `StageEvents`, o próprio objeto `RemoteStageStream` tem eventos que comunicam as mudanças de adaptação de camada e transmissão simultânea:
  + `stream.on(RemoteStageStreamEvents.ADAPTION_CHANGED, (isAdapting) => {})`
  + `stream.on(RemoteStageStreamEvents.LAYERS_CHANGED, (layers) => {})`
  + `stream.on(RemoteStageStreamEvents.LAYER_SELECTED, (layer, 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 `StageEvents.STAGE_PARTICIPANT_STREAMS_ADDED`.
  + `stream.getLayers`
  + `stream.getSelectedLayer`
  + `stream.getLowestQualityLayer`
  + `stream.getHighestQualityLayer`

Para obter detalhes, consulte a classe `RemoteStageStream` na [documentação de referência do SDK](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference). Pelo motivo `LAYER_SELECTED`, 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.

## Tratamento de problemas de rede
<a name="web-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.

De maneira geral, o estado do palco pode ser tratado por meio do evento `STAGE_CONNECTION_STATE_CHANGED`:

```
stage.on(StageEvents.STAGE_CONNECTION_STATE_CHANGED, (state) => {
   switch (state) {
      case StageConnectionState.DISCONNECTED:
         // handle disconnected UI
         return;
      case StageConnectionState.CONNECTING:
         // handle establishing connection UI
         return;
      case StageConnectionState.CONNECTED:
         // SDK is connected to the Stage
         return;
      case StageConnectionState.ERRORED:
         // SDK encountered an error and lost its connection to the stage. Wait for CONNECTED.
         return;
    }
})
```

Em geral, você pode ignorar um estado de erro encontrado após ingressar com êxito em um palco, pois o SDK tentará se recuperar internamente. Se o SDK reportar um estado de `ERRORED` e o estágio permanecer no estado `CONNECTING` por um longo período de tempo (por exemplo, 30 segundos ou mais), você provavelmente está desconectado da rede.

## Transmissão do palco para um canal do IVS
<a name="web-publish-subscribe-broadcast-stage"></a>

Para transmitir um palco, crie uma sessão `IVSBroadcastClient` separada e, em seguida, siga as instruções usuais para transmissão com o SDK, descritas acima. A lista de `StageStream` expostas por meio de `STAGE_PARTICIPANT_STREAMS_ADDED` pode ser usada para recuperar as transmissões de mídia participantes que podem ser aplicadas à composição do fluxo de transmissão da seguinte forma:

```
// Setup client with preferred settings
const broadcastClient = getIvsBroadcastClient();

stage.on(StageEvents.STAGE_PARTICIPANT_STREAMS_ADDED, (participant, streams) => {
    streams.forEach(stream => {
        const inputStream = new MediaStream([stream.mediaStreamTrack]);
        switch (stream.streamType) {
            case StreamType.VIDEO:
                broadcastClient.addVideoInputDevice(inputStream, `video-${participant.id}`, {
                    index: DESIRED_LAYER,
                    width: MAX_WIDTH,
                    height: MAX_HEIGHT
                });
                break;
            case StreamType.AUDIO:
                broadcastClient.addAudioInputDevice(inputStream, `audio-${participant.id}`);
                break;
        }
    })
})
```

Você também pode compor um palco e transmiti-lo para um canal de baixa latência do IVS para alcançar um público maior. Consulte [Enabling Multiple Hosts on an Amazon IVS Stream](https://docs.aws.amazon.com//ivs/latest/LowLatencyUserGuide/multiple-hosts.html) no Guia do usuário do streaming de baixa latência do IVS.

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

Este documento lista problemas conhecidos que podem ser encontrados ao usar o SDK de Transmissão na Web para streaming em tempo real do Amazon IVS e sugere possíveis soluções alternativas.
+ Ao fechar as guias do navegador ou sair dos navegadores sem chamar `stage.leave()`, os usuários ainda podem aparecer na sessão com um quadro congelado ou tela preta por até dez segundos.

  **Solução alternativa:** nenhuma.
+ As sessões do Safari aparecem intermitentemente com uma tela preta para os usuários que entram após o início de uma sessão.

  **Solução alternativa:** atualize o navegador e reconecte a sessão.
+ O Safari não se recupera normalmente da troca de redes.

  **Solução alternativa:** atualize o navegador e reconecte a sessão.
+ O console do desenvolvedor repete um erro `Error: UnintentionalError at StageSocket.onClose`.

  **Solução alternativa:** somente um palco pode ser criado por token de participante. Esse erro ocorre quando mais de uma instância `Stage` é criada com o mesmo token de participante, independentemente de a instância estar em um dispositivo ou em vários dispositivos.
+ Você pode ter problemas para manter um estado `StageParticipantPublishState.PUBLISHED` e pode receber estados `StageParticipantPublishState.ATTEMPTING_PUBLISH` repetidos ao receber o evento `StageEvents.STAGE_PARTICIPANT_PUBLISH_STATE_CHANGED`.

  **Solução alternativa:** restrinja a resolução do vídeo a 720p ao invocar `getUserMedia` ou `getDisplayMedia`. Especificamente, seus valores de restrição `getUserMedia` e `getDisplayMedia` para largura e altura não devem exceder 921600 (1280\$1720) quando multiplicados juntos.
+ Quando `stage.leave()` é invocado ou um participante remoto sai, um erro 404 DELETE aparece no console de depuração do navegador.

  **Solução alternativa:** nenhuma. Esse é um erro inofensivo.

## Limitações do Safari
<a name="broadcast-web-safari-limitations"></a>
+ Para negar um prompt de permissões, é necessário redefinir a permissão nas configurações do site do Safari no nível do sistema operacional.
+ O Safari não detecta nativamente todos os dispositivos com a mesma eficácia que o Firefox ou o Chrome. Por exemplo, a câmera virtual OBS não é detectada.

## Limitações do Firefox
<a name="broadcast-web-firefox-limitations"></a>
+ As permissões do sistema precisam estar habilitadas para que o Firefox compartilhe a tela. Depois de habilitar, o usuário deverá reiniciar o Firefox para que ele funcione corretamente; caso contrário, se as permissões forem percebidas como bloqueadas, o navegador emitirá uma exceção [NotFoundError](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia#exceptions).
+ O método `getCapabilities` está ausente. Isso significa que os usuários não conseguem obter a resolução ou a proporção da faixa de mídia. Veja este [tópico do bugzilla](https://bugzilla.mozilla.org/show_bug.cgi?id=1179084).
+ Várias propriedades `AudioContext` estão ausentes; por exemplo, latência e contagem de canais. Isso pode representar um problema para usuários avançados que desejem manipular as faixas de áudio.
+ Os feeds de câmera de `getUserMedia` são restritos a uma proporção de 4:3 no macOS. Veja o [tópico 1 do bugzilla](https://bugzilla.mozilla.org/show_bug.cgi?id=1193640) e o [tópico 2 do bugzilla](https://bugzilla.mozilla.org/show_bug.cgi?id=1306034).
+ Não há suporte para a captura de áudio com `getDisplayMedia`. Veja este [tópico do bugzilla](https://bugzilla.mozilla.org/show_bug.cgi?id=1541425).
+ A taxa de quadros na captura de tela está abaixo do ideal (aproximadamente a 15 fps?). Veja este [tópico do bugzilla](https://bugzilla.mozilla.org/show_bug.cgi?id=1703522).

## Limitações da Web móvel
<a name="broadcast-web-mobile-web-limitations"></a>
+ O compartilhamento de tela [getDisplayMedia](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia#browser_compatibility) não é suportado em dispositivos móveis.

  **Solução alternativa:** nenhuma.
+ O participante leva de 15 a 30 segundos para sair ao fechar um navegador sem chamar `leave()`.

  **Solução alternativa**: adicione uma interface de usuário que incentive os usuários a se desconectarem adequadamente.
+ A aplicação em segundo plano faz com que a publicação do vídeo pare.

  **Solução alternativa**: exiba uma lista de interface do usuário quando o publicador estiver pausado.
+ A taxa de quadros do vídeo cai por aproximadamente 5 segundos após ativar o som de uma câmera em dispositivos Android.

  **Solução alternativa:** nenhuma.
+ O feed de vídeo fica esticado em rotação para o iOS 16.0.

  **Solução alternativa**: exiba uma interface de usuário descrevendo esse problema conhecido do sistema operacional.
+ A troca do dispositivo de entrada de áudio alterna automaticamente o dispositivo de saída de áudio.

  **Solução alternativa:** nenhuma.
+ Colocar o navegador em segundo plano faz com que o stream de publicação fique preto e produza somente áudio.

  **Solução alternativa:** nenhuma. Isso é por motivos de segurança.

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

Esta seção é uma visão geral das condições de erros, como o SDK de Transmissão para a Web os relata à aplicação e o que uma aplicação deve fazer quando esses erros são encontrados. Os erros são relatados pelo SDK aos receptores do evento `StageEvents.ERROR`:

```
stage.on(StageEvents.ERROR, (error: StageError) => {
    // log or handle errors here
    console.log(`${error.code}, ${error.category}, ${error.message}`);
});
```

## Erros de palco
<a name="web-error-handling-stage-errors"></a>

Um StageError é relatado quando o SDK encontra um problema do qual não consegue se recuperar e geralmente requer intervenção da aplicação e reconexão de rede para se recuperar.

Cada `StageError` relatado tem um código (ou `StageErrorCode`), uma mensagem (string) e uma categoria (`StageErrorCategory`). Cada um está relacionado a uma categoria de operação subjacente.

A categoria de operação do erro é determinada com base no fato de estar relacionada à conexão com o palco (`JOIN_ERROR`), ao envio de mídia para o palco (`PUBLISH_ERROR`) ou ao recebimento de um stream de mídia de entrada do palco (`SUBSCRIBE_ERROR`).

A propriedade de código de um `StageError` relata o problema específico:


| Nome | Código | Ação recomendada | 
| --- | --- | --- | 
| TOKEN\$1MALFORMED | 1 | Crie um token válido e tente instanciar o palco novamente. | 
| TOKEN\$1EXPIRED | 2 | Crie um token não expirado e tente instanciar o palco novamente. | 
| TIMEOUT | 3 | A operação expirou. Se o palco existir e o token for válido, essa falha provavelmente é um problema de rede. Nesse caso, aguarde até que a conectividade do dispositivo se recupere. | 
| FAILED | 4 | Uma condição fatal foi encontrada ao tentar uma operação. Verifique os detalhes do erro. Se o palco existir e o token for válido, essa falha provavelmente é um problema de rede. Nesse caso, aguarde até que a conectividade do dispositivo se recupere. Para a maioria das falhas relacionadas à estabilidade da rede, o SDK tentará novamente internamente por um período de até 30 segundos antes de emitir um erro FAILED.  | 
| CANCELED | 5 | Verifique o código da aplicação e certifique-se de que não haja invocações `join`, `refreshStrategy` ou `replaceStrategy` repetidas, que podem fazer com que operações repetidas sejam iniciadas e canceladas antes da conclusão. | 
| STAGE\$1AT\$1CAPACITY | 6 | Esse erro indica que o estágio ou sua conta está na capacidade máxima. Se o estágio atingiu seu limite de participantes, tente a operação novamente quando o estágio não estiver mais na capacidade máxima, atualizando a estratégia. Se sua conta atingiu sua cota de assinaturas ou publicadores simultâneos, reduza o uso ou solicite um aumento de cota por meio do [console de Service Quotas da AWS.](https://console.aws.amazon.com/servicequotas/)  | 
| CODEC\$1MISMATCH | 7 | O codec não é compatível com o palco. Verifique se o navegador e a plataforma são compatíveis com o codec. Para streaming em tempo real do IVS, os navegadores devem ser compatíveis com o codec H.264 para vídeo e o codec Opus para áudio. | 
| TOKEN\$1NOT\$1ALLOWED | 8 | O token não tem permissão para a operação. Recrie o token com as permissões corretas e tente novamente. | 
| STAGE\$1DELETED | 9 | Nenhum; a tentativa de ingressar em um estágio excluído aciona esse erro. | 
| PARTICIPANT\$1DISCONNECTED | 10 | Nenhum; tentar entrar com um token de um participante desconectado aciona esse erro. | 

### Exemplo de tratamento de StageError
<a name="web-error-handling-stage-errors-example"></a>

Use o código StageError para determinar se o erro é devido a um token expirado:

```
stage.on(StageEvents.ERROR, (error: StageError) => {
    if (error.code === StageError.TOKEN_EXPIRED) {
        // recreate the token and stage instance and re-join
    }
});
```

### Erros de rede quando já ingressado
<a name="web-error-handling-stage-errors-network"></a>

Se a conexão de rede do dispositivo cair, o SDK poderá perder a conexão com os servidores dos palcos. Você pode ver erros no console porque o SDK não consegue mais acessar serviços de backend. POSTs em https://broadcast.stats.live-video.net falharão.

Se estiver publicando e/ou assinando, você verá erros no console relacionados a tentativas de publicação/assinatura.

Internamente, o SDK tentará se reconectar com uma estratégia de recuo exponencial.

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

## Estados de erro
<a name="web-error-handling-errored-states"></a>

Recomendamos que você use esses estados para registro em log das aplicações e para exibir mensagens aos usuários que os alertem sobre problemas de conectividade no palco de um determinado participante.

### Publicar
<a name="errored-states-publish"></a>

O SDK relata `ERRORED` quando uma publicação falha.

```
stage.on(StageEvents.STAGE_PARTICIPANT_PUBLISH_STATE_CHANGED, (participantInfo, state) => {
  if (state === StageParticipantPublishState.ERRORED) {
      // Log and/or display message to user
  }
});
```

### Assinar
<a name="errored-states-subscribe"></a>

O SDK relata `ERRORED` quando uma assinatura falha. Pode ocorrer devido às condições da rede ou quando um estágio está lotado para assinantes.

```
stage.on(StageEvents.STAGE_PARTICIPANT_SUBSCRIBE_STATE_CHANGED, (participantInfo, state) => {
  if (state === StageParticipantSubscribeState.ERRORED) {
    // Log and/or display message to user
  }
});
```

# 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.

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

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

O módulo `AmazonIVSBroadcast` implementa a interface descrita neste documento. Há 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 iOS

**Versão mais recente do SDK de transmissão para iOS:** 1.40.0 ([Notas de release](https://docs.aws.amazon.com/ivs/latest/RealTimeUserGuide/release-notes.html#mar12-26-broadcast-ios-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 iOS, consulte a documentação de referência em [https://aws.github.io/amazon-ivs-broadcast-docs/1.40.0/ios/](https://aws.github.io/amazon-ivs-broadcast-docs/1.40.0/ios/).

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

**Requisitos da plataforma:** iOS 14\$1

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

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

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

Recomendamos integrar o SDK de transmissão usando o Swift Package Manager. (Se preferir, você pode adicionar manualmente a estrutura do framework a seu projeto.)

### Recomendado: integre o SDK de transmissão (Swift Package Manager)
<a name="broadcast-ios-install-swift"></a>

1. Baixe o arquivo Package.swift de [https://broadcast.live-video.net/1.40.0/Package.swift](https://broadcast.live-video.net/1.40.0/Package.swift).

1. Em seu projeto, crie um novo diretório denominado AmazonIVSBroadcast e adicione-o ao controle de versão.

1. Coloque o arquivo Package.swift que foi baixado no novo diretório.

1. No Xcode, vá para **Arquivo > Adicionar dependências de pacote** e selecione **Adicionar local...**.

1. **Navegue e selecione o diretório AmazonIVSBroadcast que você criou e selecione Adicionar pacote**.

1. **Quando receber o prompt **Escolha produtos de pacote para AmazonIVSBroadcast**, selecione **AmazonIVSBroadcastStages** como seu **produto de pacote** definindo o destino da aplicação na seção Adicionar ao destino**.

1. Selecione **Adicionar pacote**.

**Importante:** o SDK de transmissão de streaming em tempo real do IVS inclui todos os atributos do SDK de transmissão de streaming de baixa latência do IVS. Não é possível integrar os dois SDKs no mesmo projeto.

### Abordagem alternativa: instalar o framework manualmente
<a name="broadcast-ios-install-manual"></a>

1. Faça download da versão mais recente em [ https://broadcast.live-video.net/1.40.0/AmazonIVSBroadcast-Stages.xcframework.zip](https://broadcast.live-video.net/1.40.0/AmazonIVSBroadcast-Stages.xcframework.zip).

1. Extraia o conteúdo do arquivo. `AmazonIVSBroadcast.xcframework` contém o SDK para dispositivo e para o simulador.

1. Incorporado o `AmazonIVSBroadcast.xcframework` arrastando-o para a seção **Frameworks, Libraries, and Embedded Content** (Frameworks, bibliotecas e conteúdo incorporado) da guia **General** (Geral) para o destino de sua aplicação.  
![\[A seção Estruturas de trabalho, bibliotecas e conteúdo incorporado da guia Geral para o destino da sua aplicação.\]](http://docs.aws.amazon.com/pt_br/ivs/latest/RealTimeUserGuide/images/iOS_Broadcast_SDK_Guide_xcframework.png)

## Solicitar permissões
<a name="broadcast-ios-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; caso contrário, nós as solicitamos:

```
switch AVCaptureDevice.authorizationStatus(for: .video) {
case .authorized: // permission already granted.
case .notDetermined:
   AVCaptureDevice.requestAccess(for: .video) { granted in
       // permission granted based on granted bool.
   }
case .denied, .restricted: // permission denied.
@unknown default: // permissions unknown.
}
```

É necessário fazer isso para os tipos de mídia `.video` e `.audio`, se você quiser acesso a câmeras e microfones, respectivamente.

Também é necessário adicionar entradas para `NSCameraUsageDescription` e `NSMicrophoneUsageDescription` no `Info.plist`. Caso contrário, sua aplicação falhará ao tentar solicitar permissões.

## Desativar o temporizador de ociosidade da aplicação
<a name="broadcast-ios-disable-idle-timer"></a>

Isso é opcional, porém é recomendado. Isso impede que seu dispositivo entre em modo de suspensão enquanto usa o SDK de Transmissão, o que interromperia a transmissão.

```
override func viewDidAppear(_ animated: Bool) {
   super.viewDidAppear(animated)
   UIApplication.shared.isIdleTimerDisabled = true
}
override func viewDidDisappear(_ animated: Bool) {
   super.viewDidDisappear(animated)
   UIApplication.shared.isIdleTimerDisabled = false
}
```

# Publicação e assinatura com o SDK de Transmissão para iOS do IVS \$1 Streaming em tempo real
<a name="ios-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 iOS para streaming em tempo real do IVS.

## Conceitos
<a name="ios-publish-subscribe-concepts"></a>

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

A classe `IVSStage` corresponde ao principal ponto de interação entre a aplicação de host e o SDK. A classe representa o próprio palco e é usada para entrar e sair do palco. Criar ou 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.

```
let stage = try IVSStage(token: token, strategy: self)

try stage.join()

stage.leave()
```

Na classe `IVSStage`, também é possível anexar `IVSStageRenderer` e `IVSErrorDelegate`:

```
let stage = try IVSStage(token: token, strategy: self)
stage.errorDelegate = self
stage.addRenderer(self) // multiple renderers can be added
```

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

O protocolo `IVSStageStrategy` 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`, `shouldPublishParticipant` e `streamsToPublishForParticipant`. Todas serão discutidas abaixo.

#### Como se inscrever como participante
<a name="ios-publish-subscribe-concepts-strategy-participants"></a>

```
func stage(_ stage: IVSStage, shouldSubscribeToParticipant participant: IVSParticipantInfo) -> IVSStageSubscribeType
```

Quando um participante remoto entra em um 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`, `.audioOnly` e `.audioVideo`. 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 `.audioVideo` 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:

```
func stage(_ stage: IVSStage, shouldSubscribeToParticipant participant: IVSParticipantInfo) -> IVSStageSubscribeType {
    return .audioVideo
}
```

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 `attributes` em `IVSParticipantInfo` para se inscrever, de forma seletiva, como participante com base nos atributos fornecidos pelo servidor:

```
func stage(_ stage: IVSStage, shouldSubscribeToParticipant participant: IVSParticipantInfo) -> IVSStageSubscribeType {
    switch participant.attributes["role"] {
    case "moderator": return .none
    case "guest": return .audioVideo
    default: return .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="ios-publish-subscribe-concepts-strategy-participants-config"></a>

```
func stage(_ stage: IVSStage, subscribeConfigurationForParticipant participant: IVSParticipantInfo) -> IVSSubscribeConfiguration
```

Se um participante remoto estiver fazendo uma assinatura (consulte [Assinatura de participantes](#ios-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:

```
func stage(_ stage: IVSStage, subscribeConfigurationForParticipant participant: IVSParticipantInfo) -> IVSSubscribeConfiguration {
    let config = IVSSubscribeConfiguration()

    try! config.jitterBuffer.setMinDelay(.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="ios-publish-subscribe-concepts-strategy-publishing"></a>

```
func stage(_ stage: IVSStage, shouldPublishParticipant participant: IVSParticipantInfo) -> Bool
```

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:

```
func stage(_ stage: IVSStage, shouldPublishParticipant participant: IVSParticipantInfo) -> Bool {
    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="ios-publish-subscribe-concepts-strategy-streams"></a>

```
func stage(_ stage: IVSStage, streamsToPublishForParticipant participant: IVSParticipantInfo) -> [IVSLocalStageStream]
```

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

#### Como atualizar a estratégia
<a name="ios-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 `shouldPublishParticipant` (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 `shouldPublishParticipant` 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 fará nenhuma modificação no palco.

Se o valor de retorno de `shouldSubscribeToParticipant` for alterado de `.audioVideo` para `.audioOnly`, 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="ios-publish-subscribe-concepts-renderer"></a>

O protocolo `IVSStageRenderer` 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:

```
func stage(_ stage: IVSStage, participantDidJoin participant: IVSParticipantInfo)

func stage(_ stage: IVSStage, participantDidLeave participant: IVSParticipantInfo)

func stage(_ stage: IVSStage, participant: IVSParticipantInfo, didChange publishState: IVSParticipantPublishState)

func stage(_ stage: IVSStage, participant: IVSParticipantInfo, didChange subscribeState: IVSParticipantSubscribeState)

func stage(_ stage: IVSStage, participant: IVSParticipantInfo, didAdd streams: [IVSStageStream])

func stage(_ stage: IVSStage, participant: IVSParticipantInfo, didRemove streams: [IVSStageStream])

func stage(_ stage: IVSStage, participant: IVSParticipantInfo, didChangeMutedStreams streams: [IVSStageStream])

func stage(_ stage: IVSStage, didChange connectionState: IVSStageConnectionState, withError error: Error?)

func stage(_ stage: IVSStage, participant: IVSParticipantInfo, stream: IVSRemoteStageStream, didChangeStreamAdaption adaption: Bool)

func stage(_ stage: IVSStage, participant: IVSParticipantInfo, stream: IVSRemoteStageStream, didChange layers: [IVSRemoteStageStreamLayer])

func stage(_ stage: IVSStage, participant: IVSParticipantInfo, stream: IVSRemoteStageStream, didSelect layer: IVSRemoteStageStreamLayer?, reason: IVSRemoteStageStream.LayerSelectedReason)
```

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 `participant:didChangePublishState` 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.

Observe que somente a publicação de participantes aciona `participantDidJoin` e, sempre que um participante interrompe as publicações ou sai da sessão de palco, `participantDidLeave` é acionado.

## Publicação de uma transmissão de mídia
<a name="ios-publish-subscribe-publish-stream"></a>

Dispositivos locais, como microfones e câmeras integrados, são descobertos por meio de `IVSDeviceDiscovery`. Veja a seguir um exemplo de como selecionar a câmera frontal e o microfone padrão e, em seguida, retorná-los como `IVSLocalStageStreams` para serem publicados pelo SDK:

```
let devices = IVSDeviceDiscovery().listLocalDevices()

// Find the camera virtual device, choose the front source, and create a stream
let camera = devices.compactMap({ $0 as? IVSCamera }).first!
let frontSource = camera.listAvailableInputSources().first(where: { $0.position == .front })!
camera.setPreferredInputSource(frontSource)
let cameraStream = IVSLocalStageStream(device: camera)

// Find the microphone virtual device and create a stream
let microphone = devices.compactMap({ $0 as? IVSMicrophone }).first!
let microphoneStream = IVSLocalStageStream(device: microphone)

// Configure the audio manager to use the videoChat preset, which is optimized for bi-directional communication, including echo cancellation.
IVSStageAudioManager.sharedInstance().setPreset(.videoChat)

// This is a function on IVSStageStrategy
func stage(_ stage: IVSStage, streamsToPublishForParticipant participant: IVSParticipantInfo) -> [IVSLocalStageStream] {
    return [cameraStream, microphoneStream]
}
```

## Exibição e remoção de participantes
<a name="ios-publish-subscribe-participants"></a>

Após a conclusão da inscrição, você receberá uma matriz de objetos `IVSStageStream` por meio da função `didAddStreams` do renderizador. Para visualizar previamente ou receber estatísticas de nível de áudio sobre este participante, é possível acessar o objeto `IVSDevice` subjacente da transmissão:

```
if let imageDevice = stream.device as? IVSImageDevice {
    let preview = imageDevice.previewView()
    /* attach this UIView subclass to your view */
} else if let audioDevice = stream.device as? IVSAudioDevice {
    audioDevice.setStatsCallback( { stats in
        /* process stats.peak and stats.rms */
    })
}
```

Quando um participante interrompe as publicações ou cancela a inscrição, a função `didRemoveStreams` é 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.

`didRemoveStreams` é 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 `.audioVideo` para `.audioOnly`.
+ Um participante remoto que sai do palco.
+ Um participante local que sai do palco.

Como `didRemoveStreams` é 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="ios-publish-subscribe-mute-streams"></a>

Os objetos `IVSLocalStageStream` 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 `IVSLocalStageStream` 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 `IVSLocalStageStream` para garantir que o estado mudo esperado seja mantido.

## Monitoramento do estado mudo da mídia do participante remoto
<a name="ios-publish-subscribe-mute-state"></a>

Quando um participante altera o estado mudo de sua transmissão de vídeo ou áudio, a função `didChangeMutedStreams` do renderizador é invocada com uma matriz de transmissões que foram alteradas. Use a propriedade `isMuted` na `IVSStageStream` para atualizar a IU adequadamente:

```
func stage(_ stage: IVSStage, participant: IVSParticipantInfo, didChangeMutedStreams streams: [IVSStageStream]) {
    streams.forEach { stream in 
        /* stream.isMuted */
    }
}
```

## Criação de uma configuração de palco
<a name="ios-publish-subscribe-stage-config"></a>

Para personalizar os valores da configuração de vídeo de um palco, use `IVSLocalStageStreamVideoConfiguration`:

```
let config = IVSLocalStageStreamVideoConfiguration()
try config.setMaxBitrate(900_000)
try config.setMinBitrate(100_000)
try config.setTargetFramerate(30)
try config.setSize(CGSize(width: 360, height: 640))
config.degradationPreference = .balanced
```

## Obtenção de estatísticas WebRTC
<a name="ios-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 `IVSStageStream`. Quando uma coleta for concluída, você receberá as estatísticas por meio do `IVSStageStreamDelegate`, que pode ser definido em `IVSStageStream`. Para coletar estatísticas WebRTC de forma contínua, chame esta função em um `Timer`.

```
func stream(_ stream: IVSStageStream, didGenerateRTCStats stats: [String : [String : String]]) {
    for stat in stats {
      for member in stat.value {
         print("stat \(stat.key) has member \(member.key) with value \(member.value)")
      }
   }
}
```

## Obtenção de atributos do participante
<a name="ios-publish-subscribe-participant-attributes"></a>

Se você especificar atributos na solicitação da operação `CreateParticipantToken`, poderá visualizar os atributos nas propriedades `IVSParticipantInfo`:

```
func stage(_ stage: IVSStage, participantDidJoin participant: IVSParticipantInfo) {
    print("ID: \(participant.participantId)")
    for attribute in participant.attributes {
        print("attribute: \(attribute.key)=\(attribute.value)")
    }
}
```

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

O método `embedMessage` em IVSImageDevice 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="ios-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 IVSImageDevice. 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.

```
let imageDevice: IVSImageDevice = imageStream.device as! IVSImageDevice
let messageData = Data("hello world".utf8)

do {
    try imageDevice.embedMessage(messageData, withRepeatCount: 0)
} catch {
    print("Failed to embed message: \(error)")
}
```

### Repetir cargas úteis das mensagens
<a name="ios-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 imageDevice.embedMessage(messageData, withRepeatCount: 5)

// repeatCount: 0-30, receiving clients should handle duplicates
```

### Ler mensagens incorporadas
<a name="ios-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="ios-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 `IVSImageDeviceFrame` que saem do `IVSImageDevice` do publicador. Para fazer isso, adquira o `IVSImageDevice` de um publicador e observe cada quadro por meio de um retorno de chamada fornecido para `setOnFrameCallback`, conforme mostrado no exemplo a seguir:

```
// in an IVSStageRenderer’s stage:participant:didAddStreams: function, after acquiring the new IVSImageStream

let imageDevice: IVSImageDevice? = imageStream.device as? IVSImageDevice
imageDevice?.setOnFrameCallback { frame in
	for message in frame.embeddedMessages {
    		if let seiMessage = message as? IVSUserDataUnregisteredSEIMessage {
        		let seiMessageData = seiMessage.data
        		let seiMessageUUID = seiMessage.UUID

        		// interpret the message's data based on the UUID
    		}
	}
}
```

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

Quando a aplicação entra em segundo plano, é possível continuar no palco enquanto ouve o áudio remoto, embora não seja possível continuar enviando a própria imagem e áudio. Você precisará atualizar a implementação de sua `IVSStrategy` para interromper as publicações e se inscrever como `.audioOnly` (ou `.none`, se aplicável):

```
func stage(_ stage: IVSStage, shouldPublishParticipant participant: IVSParticipantInfo) -> Bool {
    return false
}
func stage(_ stage: IVSStage, shouldSubscribeToParticipant participant: IVSParticipantInfo) -> IVSStageSubscribeType {
    return .audioOnly
}
```

Em seguida, realize uma chamada para `stage.refreshStrategy()`.

## Codificação em camadas com transmissão simultânea
<a name="ios-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="ios-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 `IVSLocalStageStream` na instanciação:

```
// Enable Simulcast
let config = IVSLocalStageStreamVideoConfiguration()
config.simulcast.enabled = true

let cameraStream = IVSLocalStageStream(device: camera, configuration: 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
let config = IVSLocalStageStreamVideoConfiguration()
config.simulcast.enabled = true

let layers = [
    IVSStagePresets.simulcastLocalLayer().default720(),
    IVSStagePresets.simulcastLocalLayer().default180()
]

try config.simulcast.setLayers(layers)

let cameraStream = IVSLocalStageStream(device: camera, configuration: 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: CGSize;`
+ `setMaxBitrate: integer;`
+ `setMinBitrate: integer;`
+ `setTargetFramerate: float;`

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

```
// Enable Simulcast
let config = IVSLocalStageStreamVideoConfiguration()
config.simulcast.enabled = true

let customHiLayer = IVSStagePresets.simulcastLocalLayer().default720()
try customHiLayer.setTargetFramerate(15)

let layers = [
    customHiLayer,
    IVSStagePresets.simulcastLocalLayer().default180()
]

try config.simulcast.setLayers(layers)

let cameraStream = IVSLocalStageStream(device: camera, configuration: 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="ios-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="ios-layered-encoding-simulcast-layer-quality-preference"></a>

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

```
func stage(_ stage: IVSStage, subscribeConfigurationForParticipant participant: IVSParticipantInfo) -> IVSSubscribeConfiguration {
    let config = IVSSubscribeConfiguration()

    config.simulcast.initialLayerPreference = .lowestQuality

    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`:
+ `lowestQuality` — 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.
+ `highestQuality` — 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 `initialLayerPreference`) 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="ios-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 `IVSRemoteStageStreamLayer`, que pode ser um dos seguintes:
+ Um objeto de camada, como um retornado por `IVSRemoteStageStream.layers`.
+ 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:

```
func stage(_ stage: IVSStage, participant: IVSParticipantInfo, preferredLayerFor stream: IVSRemoteStageStream) -> IVSRemoteStageStreamLayer? {
    return stream.lowestQualityLayer
}
```

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.

```
func stage(_ stage: IVSStage, participant: IVSParticipantInfo, preferredLayerFor stream: IVSRemoteStageStream) -> IVSRemoteStageStreamLayer? {
    If appState.isAutoMode {
        return nil
    } else {
        return appState.layerChoice
    }
}
```

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

`IVSRemoteStageStream` 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 `IVSStageRenderer`, o `IVSRemoteStageStreamDelegate` tem eventos que comunicam mudanças de adaptação de camada e transmissão simultânea:
  + `func stream(_ stream: IVSRemoteStageStream, didChangeAdaption adaption: Bool)`
  + `func stream(_ stream: IVSRemoteStageStream, didChange layers: [IVSRemoteStageStreamLayer])`
  + `func stream(_ stream: IVSRemoteStageStream, didSelect layer: IVSRemoteStageStreamLayer?, reason: IVSRemoteStageStream.LayerSelectedReason)`
+ **Métodos de camada** — `IVSRemoteStageStream` 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 `func stage(_ stage: IVSStage, participant: IVSParticipantInfo, didAdd streams: [IVSStageStream])`.
  + `stream.layers`
  + `stream.selectedLayer`
  + `stream.lowestQualityLayer`
  + `stream.highestQualityLayer`
  + `stream.layers(with: IVSRemoteStageStreamLayerConstraints)`

Para obter detalhes, consulte a classe `IVSRemoteStageStream` na [documentação de referência do SDK](https://aws.github.io/amazon-ivs-broadcast-docs/latest/ios/). 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.

## Transmissão do palco para um canal do IVS
<a name="ios-publish-subscribe-broadcast-stage"></a>

Para transmitir um palco, crie uma `IVSBroadcastSession` separada e, em seguida, siga as instruções usuais para uma transmissão com o SDK, descritas acima. A propriedade `device` na `IVSStageStream` será um `IVSImageDevice` ou um `IVSAudioDevice`, conforme mostrado no trecho de código acima. Eles podem ser conectados ao `IVSBroadcastSession.mixer` para transmitir todo o palco em um layout personalizável.

Você também pode compor um palco e transmiti-lo para um canal de baixa latência do IVS para alcançar um público maior. Consulte [Enabling Multiple Hosts on an Amazon IVS Stream](https://docs.aws.amazon.com//ivs/latest/LowLatencyUserGuide/multiple-hosts.html) no Guia do usuário do streaming de baixa latência do IVS.

# Como o iOS escolhe a resolução e a taxa de quadros da câmera
<a name="ios-publish-subscribe-resolution-framerate"></a>

A câmera gerenciada pelo SDK de Transmissão otimiza sua resolução e taxa de quadros (quadros por segundo, do inglês frames-per-second [FPS]) para minimizar a produção de calor e o consumo de energia. Esta seção explica como acontece a seleção da resolução e da taxa de quadros para ajudar aplicações no host a otimizarem seus casos de uso.

Ao criar um `IVSLocalStageStream` com um `IVSCamera`, a câmera é otimizada para uma taxa de quadros de `IVSLocalStageStreamVideoConfiguration.targetFramerate` e uma resolução de `IVSLocalStageStreamVideoConfiguration.size`. A chamada de `IVSLocalStageStream.setConfiguration` atualiza a câmera com valores mais novos. 

## Prévia da câmera
<a name="resolution-framerate-camera-preview"></a>

Se você criar uma prévia de um `IVSCamera` sem anexá-lo a um `IVSBroadcastSession` ou `IVSStage`, o padrão será uma resolução de 1080p e uma taxa de quadros de 60 FPS.

## Transmissão de um palco
<a name="resolution-framerate-broadcast-stage"></a>

Ao usar um `IVSBroadcastSession` para transmitir um `IVSStage`, o SDK tenta otimizar a câmera com uma resolução e uma taxa de quadros que atendam aos critérios de ambas as sessões.

Por exemplo, se a configuração de transmissão estiver definida para ter uma taxa de quadros de 15 FPS e uma resolução de 1080p, enquanto o palco tiver uma taxa de quadros de 30 FPS e uma resolução de 720p, o SDK selecionará uma configuração de câmera com uma taxa de quadros de 30 FPS e uma resolução de 1080p. O `IVSBroadcastSession` eliminará todos os outros quadros da câmera e o `IVSStage` ajustará a escala da imagem de 1080p para 720p.

Se uma aplicação do host planeja usar ambos `IVSBroadcastSession` e `IVSStage` em conjunto com uma câmera, recomendamos que as propriedades `targetFramerate` e `size` das respectivas configurações correspondam. Uma incompatibilidade pode fazer com que a câmera se reconfigure durante a captura de vídeo, o que causará um breve atraso na entrega da amostra de vídeo.

Se a presença de valores idênticos não corresponder ao caso de uso da aplicação do host, criar primeiro a câmera de maior qualidade evitará que a câmera se reconfigure quando a sessão de qualidade inferior for adicionada. Por exemplo, se você transmitir em 1080p e 30 FPS e depois entrar em um palco definido para 720p e 30 FPS, a câmera não se reconfigurará e o vídeo continuará sem interrupções. Isso ocorre porque 720p é menor ou igual a 1080p e 30 FPS é menor ou igual a 30 FPS.

## Taxas de quadros, resoluções e taxas de proporções arbitrárias
<a name="resolution-framerate-arbitrary"></a>

A maioria dos hardwares de câmera é capaz de corresponder exatamente aos formatos comuns, como 720p a 30 FPS ou 1080p a 60 FPS. Contudo, não é possível corresponder exatamente a todos os formatos. O SDK de Transmissão escolhe a configuração da câmera com base nas seguintes regras (em ordem de prioridade):

1. A largura e a altura da resolução são maiores ou iguais à resolução desejada, mas dentro dessa restrição, a largura e a altura são as menores possíveis.

1. A taxa de quadros é maior ou igual à taxa de quadros desejada, mas dentro dessa restrição, a taxa de quadros é a mais baixa possível.

1. A taxa de proporção corresponde à proporção desejada.

1. Se houver vários formatos correspondentes, o formato com o maior campo de visão será usado.

Veja dois exemplos a seguir:
+ A aplicação do host está tentando transmitir em 4K a 120 FPS. A câmera selecionada é compatível somente com 4K a 60 FPS ou 1080p a 120 FPS. O formato selecionado será 4k a 60 FPS, porque a regra de resolução tem prioridade superior em relação à regra de taxa de quadros.
+ Uma resolução irregular é solicitada, 1910x1070. A câmera usará 1920x1080. *Cuidado: escolher uma resolução como 1921x1080 fará com que a câmera aumente para a próxima resolução disponível (como 2592x1944), o que acarretará em penalidade na largura de banda da CPU e da memória*.

## E quanto ao Android?
<a name="resolution-framerate-android"></a>

O Android não ajusta sua resolução ou taxa de quadros em tempo real, como o iOS, portanto, isso não afeta o SDK de Transmissão do Android.

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

Este documento lista problemas conhecidos que podem ser encontrados ao usar o SDK de Transmissão para iOS para streaming em tempo real do Amazon IVS e sugere possíveis soluções alternativas.
+ A alteração de rotas de áudio Bluetooth pode ser imprevisível. Se você conectar um novo dispositivo no meio da sessão, o iOS poderá ou não alterar automaticamente a rota de entrada. Além disso, não é possível escolher entre vários fones de ouvido Bluetooth conectados ao mesmo tempo. Isso acontece tanto na transmissão regular quanto nas sessões de palco.

  **Solução alternativa:** se você planeja usar um fone de ouvido Bluetooth, conecte-o antes de iniciar a transmissão ou o palco e deixe-o conectado durante toda a sessão.
+ Os participantes que usam um iPhone 14, iPhone 14 Plus, iPhone 14 Pro ou iPhone 14 Pro Max podem causar um problema de eco de áudio para os outros participantes.

  **Solução alternativa:** os participantes que usam os dispositivos afetados podem usar fones de ouvido para evitar o problema de eco para outros participantes.
+ 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.
+ Quando um participante está publicando ou se inscrevendo, é possível receber um erro com o código 1400 que indica desconexão devido a um problema de rede, mesmo quando a rede está estável.

  **Solução alternativa:** tente publicar ou se inscrever novamente.
+ 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.

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

Esta seção é uma visão geral das condições de erros, como o SDK de Transmissão para iOS 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-ios-fatal-vs-nonfatal-errors"></a>

O objeto de erro tem um booleano “is fatal”. É uma entrada do dicionário em `IVSBroadcastErrorIsFatalKey` que contém um booleano.

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:

```
let nsError = error as NSError
if nsError.userInfo[IVSBroadcastErrorIsFatalKey] as? Bool == true {
  // the error is fatal
}
```

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

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

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

O SDK gera uma exceção Swift com código de erro = 1000 e IVSBroadcastErrorIsFatalKey = YES.

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

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

Isso acontece quando o token de estágio expirou.

O SDK gera uma exceção Swift com código de erro = 1001 e IVSBroadcastErrorIsFatalKey = YES.

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

### Token inválido ou revogado
<a name="broadcast-ios-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 `stage(didChange connectionState, withError error)` com o código de erro = 1026 e IVSBroadcastErrorIsFatalKey = YES.

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

### Erros de rede para entrada inicial
<a name="broadcast-ios-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 `stage(didChange connectionState, withError error)` com o código de erro = 1300 e IVSBroadcastErrorIsFatalKey = YES.

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

### Erros de rede quando já ingressado
<a name="broadcast-ios-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 `stage(didChange connectionState, withError error)` com o código de erro = 1300 e o valor de IVSBroadcastErrorIsFatalKey = YES.

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

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

### Inicial
<a name="broadcast-ios-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 `IVSErrorDelegate:didEmitError` com o código de erro relevante e `IVSBroadcastErrorIsFatalKey == NO`.

**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-ios-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 `didEmitError` com o código de erro = 1400 e IVSBroadcastErrorIsFatalKey = NO.

**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.

# SDK de transmissão do IVS: dispositivos de mixagem
<a name="broadcast-mixed-devices"></a>

Dispositivos de mixagem são dispositivos de áudio e vídeo que recebem de várias fontes de entrada e geram uma única saída. Dispositivos de mixagem são um atributo sofisticado que permite definir e gerenciar vários elementos e faixas de áudio na tela (vídeo). Você pode combinar vídeo e áudio de várias fontes, como câmeras, microfones, capturas de tela e áudio e vídeo gerados pela sua aplicação. Você pode usar transições para mover essas fontes pelo vídeo que você transmite para o IVS, e adicionar e remover fontes durante o fluxo.

Os dispositivos de mixagem vêm nas versões de imagem e áudio. Para criar um dispositivo de imagem de mixagem, chame:

`DeviceDiscovery.createMixedImageDevice()` no Android

`IVSDeviceDiscovery.createMixedImageDevice()` no iOS

O dispositivo retornado pode ser anexado a uma `BroadcastSession` (streaming de baixa latência) ou `Stage` (streaming em tempo real), como qualquer outro dispositivo.

## Terminologia
<a name="broadcast-mixed-devices-terminology"></a>

![\[Terminologia dos dispositivos de mixagem de transmissão do IVS.\]](http://docs.aws.amazon.com/pt_br/ivs/latest/RealTimeUserGuide/images/Broadcast_SDK_Mixer_Glossary.png)



| Prazo | Descrição | 
| --- | --- | 
| Dispositivo | Um componente de hardware ou software que produz entrada de áudio ou imagem. Alguns exemplos de dispositivos são microfones, câmeras, fones de ouvido de Bluetooth e dispositivos virtuais, como capturas de tela ou entradas de imagens personalizadas. | 
| Dispositivo de mixagem | Um `Device` que pode ser anexado a uma `BroadcastSession` como qualquer outro `Device`, mas com APIs adicionais que permitem a adição de objetos `Source`. Os dispositivos de mixagem têm mixadores internos que combinam áudio ou imagens, produzindo um único fluxo de áudio e imagem de saída. Dispositivos de mixagem vêm nas versões de áudio ou imagem.  | 
| Configuração de dispositivo de mixagem | Um objeto de configuração para o dispositivo de mixagem. Para dispositivos de imagem de mixagem, isso configura propriedades como dimensões e taxa de quadros. Para dispositivos de áudio de mixagem, isso configura a quantidade de canais. | 
|  Origem | Um contêiner que define a posição de um elemento visual na tela e as propriedades de uma faixa de áudio na mixagem de áudio. Um dispositivo de mixagem pode ser configurado com zero ou mais fontes. É dada às fontes uma configuração que afeta como a mídia da fonte é usada. A imagem acima mostra quatro fontes de imagens: [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/pt_br/ivs/latest/RealTimeUserGuide/broadcast-mixed-devices.html)  | 
| Configuração de fonte |  Um objeto de configuração para a fonte que entra em dispositivo de mixagem. Os objetos de configuração completos são descritos abaixo.   | 
| Transição | Para mover um slot para uma nova posição ou alterar algumas de suas propriedades, use `MixedDevice.transitionToConfiguration()`. Esse método aceita: [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/pt_br/ivs/latest/RealTimeUserGuide/broadcast-mixed-devices.html) | 

## Dispositivo de áudio de mixagem
<a name="broadcast-mixed-audio-device"></a>

### Configuração
<a name="broadcast-mixed-audio-device-configuration"></a>

`MixedAudioDeviceConfiguration` no Android

`IVSMixedAudioDeviceConfiguration` no iOS


| Nome | Tipo | Descrição | 
| --- | --- | --- | 
| `channels` | Inteiro | Número de canais de saída do mixer de áudio. Valores válidos: 1, 2. 1 é áudio mono; em 2 é áudio estéreo. Padrão: 2. | 

### Configuração de fonte
<a name="broadcast-mixed-audio-device-source-configuration"></a>

`MixedAudioDeviceSourceConfiguration` no Android

`IVSMixedAudioDeviceSourceConfiguration` no iOS


| Nome | Tipo | Descrição | 
| --- | --- | --- | 
| `gain` | Float | Ganho de áudio. Este é um multiplicador. Assim, qualquer valor acima de 1 aumenta o ganho e qualquer valor abaixo de 1, diminui. Valores válidos: 0 - 2. Padrão: 1.  | 

## Dispositivo de imagem de mixagem
<a name="broadcast-mixed-image-device"></a>

### Configuração
<a name="broadcast-mixed-image-device-configuration"></a>

`MixedImageDeviceConfiguration` no Android

`IVSMixedImageDeviceConfiguration` no iOS


| Nome | Tipo | Descrição | 
| --- | --- | --- | 
| `size` | Vec2 | Tamanho da tela de vídeo. | 
| `targetFramerate` | Inteiro | Número de quadros por segundo do destino para o dispositivo de mixagem. Em média, esse valor deve ser alcançado, mas o sistema pode abandonar quadros em determinadas circunstâncias (por exemplo, alta carga de CPU ou GPU). | 
| `transparencyEnabled` | Booleano | Isso permite a mistura usando a propriedade `alpha` nas configurações da fonte de imagens. Definir isso como `true` aumenta o consumo de memória e CPU. Padrão: `false`. | 

### Configuração de fonte
<a name="broadcast-mixed-image-device-source-configuration"></a>

`MixedImageDeviceSourceConfiguration` no Android

`IVSMixedImageDeviceSourceConfiguration` no iOS


| Nome | Tipo | Descrição | 
| --- | --- | --- | 
| `alpha` | Float | Alfa do slot. É um valor multiplicativo com quaisquer valores alfa na imagem. Valores válidos: 0 - 1. 0 é completamente transparente e 1 é completamente opaco. Padrão: 1. | 
| `aspect` | AspectMode | Modo de taxa de proporção para qualquer imagem renderizada no slot. Valores válidos: [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/pt_br/ivs/latest/RealTimeUserGuide/broadcast-mixed-devices.html) Padrão: `Fit`  | 
| `fillColor` | Vec4 | A cor de preenchimento a ser usada com `aspect Fit` quando as proporções do slot e da imagem não coincidem. O formato é (vermelho, verde, azul, alfa). Valor válido (para cada canal): 0 e 1. Padrão: (0, 0, 0, 0). | 
| `position` | Vec2 | Posição do slot (em pixels) em relação ao canto superior esquerdo da tela. A origem do slot também está no canto superior esquerdo. | 
| `size` | Vec2 | Tamanho do slot, em pixels. Definir esse valor também define `matchCanvasSize` como `false`. Padrão: (0, 0); porém, como `matchCanvasSize` assume o padrão `true`, o tamanho renderizado do slot é o tamanho da tela, não (0, 0). | 
| `zIndex` | Float | Ordenação relativa de slots. Slots com valores mais altos de `zIndex` são desenhados por cima dos slots com valores menores de `zIndex`. | 

## Criar e configurar um dispositivo de imagem de mixagem
<a name="broadcast-mixed-image-device-creating-configuring"></a>

![\[Configuração de uma sessão de transmissão para mixagem.\]](http://docs.aws.amazon.com/pt_br/ivs/latest/RealTimeUserGuide/images/Broadcast_SDK_Mixer_Configuring.png)


Aqui, criamos uma cena semelhante à do início deste guia, com três elementos na tela:
+ Slot inferior esquerdo para uma câmera.
+ Slot inferior direito para uma sobreposição de logo.
+ Slot superior direito para um filme.

Observe que a origem da tela é o canto superior esquerdo, e é o mesmo para os slots. Assim, posicionar um slot em (0, 0) o coloca no canto superior esquerdo com todo o slot visível.

### iOS
<a name="broadcast-mixed-image-device-creating-configuring-ios"></a>

```
let deviceDiscovery = IVSDeviceDiscovery()
let mixedImageConfig = IVSMixedImageDeviceConfiguration()
mixedImageConfig.size = CGSize(width: 1280, height: 720)
try mixedImageConfig.setTargetFramerate(60)
mixedImageConfig.isTransparencyEnabled = true
let mixedImageDevice = deviceDiscovery.createMixedImageDevice(with: mixedImageConfig)

// Bottom Left
let cameraConfig = IVSMixedImageDeviceSourceConfiguration()
cameraConfig.size = CGSize(width: 320, height: 180)
cameraConfig.position = CGPoint(x: 20, y: mixedImageConfig.size.height - cameraConfig.size.height - 20)
cameraConfig.zIndex = 2
let camera = deviceDiscovery.listLocalDevices().first(where: { $0 is IVSCamera }) as? IVSCamera
let cameraSource = IVSMixedImageDeviceSource(configuration: cameraConfig, device: camera)
mixedImageDevice.add(cameraSource)

// Top Right
let streamConfig = IVSMixedImageDeviceSourceConfiguration()
streamConfig.size = CGSize(width: 640, height: 320)
streamConfig.position = CGPoint(x: mixedImageConfig.size.width - streamConfig.size.width - 20, y: 20)
streamConfig.zIndex = 1
let streamDevice = deviceDiscovery.createImageSource(withName: "stream")
let streamSource = IVSMixedImageDeviceSource(configuration: streamConfig, device: streamDevice)
mixedImageDevice.add(streamSource)

// Bottom Right
let logoConfig = IVSMixedImageDeviceSourceConfiguration()
logoConfig.size = CGSize(width: 320, height: 180)
logoConfig.position = CGPoint(x: mixedImageConfig.size.width - logoConfig.size.width - 20,
                              y: mixedImageConfig.size.height - logoConfig.size.height - 20)
logoConfig.zIndex = 3
let logoDevice = deviceDiscovery.createImageSource(withName: "logo")
let logoSource = IVSMixedImageDeviceSource(configuration: logoConfig, device: logoDevice)
mixedImageDevice.add(logoSource)
```

### Android
<a name="broadcast-mixed-image-device-creating-configuring-android"></a>

```
val deviceDiscovery = DeviceDiscovery(this /* context */)
val mixedImageConfig = MixedImageDeviceConfiguration().apply {
    setSize(BroadcastConfiguration.Vec2(1280f, 720f))
    setTargetFramerate(60)
    setEnableTransparency(true)
}
val mixedImageDevice = deviceDiscovery.createMixedImageDevice(mixedImageConfig)

// Bottom Left
val cameraConfig = MixedImageDeviceSourceConfiguration().apply {
    setSize(BroadcastConfiguration.Vec2(320f, 180f))
    setPosition(BroadcastConfiguration.Vec2(20f, mixedImageConfig.size.y - size.y - 20))
    setZIndex(2)
}
val camera = deviceDiscovery.listLocalDevices().firstNotNullOf { it as? CameraSource }
val cameraSource = MixedImageDeviceSource(cameraConfig, camera)
mixedImageDevice.addSource(cameraSource)

// Top Right
val streamConfig = MixedImageDeviceSourceConfiguration().apply {
    setSize(BroadcastConfiguration.Vec2(640f, 320f))
    setPosition(BroadcastConfiguration.Vec2(mixedImageConfig.size.x - size.x - 20, 20f))
    setZIndex(1)
}
val streamDevice = deviceDiscovery.createImageInputSource(streamConfig.size)
val streamSource = MixedImageDeviceSource(streamConfig, streamDevice)
mixedImageDevice.addSource(streamSource)

// Bottom Right
val logoConfig = MixedImageDeviceSourceConfiguration().apply {
    setSize(BroadcastConfiguration.Vec2(320f, 180f))
    setPosition(BroadcastConfiguration.Vec2(mixedImageConfig.size.x - size.x - 20, mixedImageConfig.size.y - size.y - 20))
    setZIndex(1)
}
val logoDevice = deviceDiscovery.createImageInputSource(logoConfig.size)
val logoSource = MixedImageDeviceSource(logoConfig, logoDevice)
mixedImageDevice.addSource(logoSource)
```

## Removendo fontes
<a name="broadcast-mixed-devices-removing-sources"></a>

Para remover uma fonte, chame `MixedDevice.remove` com o objeto `Source` que você deseja remover.

## Animações com transições
<a name="broadcast-mixed-devices-animations-transitions"></a>

O método de transição substitui a configuração de uma fonte por uma nova configuração. Essa substituição pode ser animada ao longo do tempo, definindo uma duração superior a 0, em segundos. 

### Quais propriedades podem ser animadas?
<a name="broadcast-mixed-devices-animations-properties"></a>

Nem todas as propriedades na estrutura do slot podem ser animadas. Todas as propriedades baseadas em tipos Flutuantes podem ser animadas; outras propriedades produzem efeito no início ou no fim da animação.


| Nome | Pode ser animado? | Ponto de impacto | 
| --- | --- | --- | 
| `Audio.gain` | Sim | Interpolado(a) | 
| `Image.alpha` | Sim | Interpolado(a) | 
| `Image.aspect` | Não | Final | 
| `Image.fillColor` | Sim | Interpolado(a) | 
| `Image.position` | Sim | Interpolado(a) | 
| `Image.size` | Sim | Interpolado(a) | 
| `Image.zIndex` Observações: o `zIndex` move planos 2D através do espaço 3D. Assim, a transição acontece quando os dois planos se cruzam em algum ponto no meio da animação. Isso pode ser calculado, mas depende dos valores de início e fim do `zIndex`. Para obter uma transição mais suave, combine ele com a `alpha`.  | Sim | Desconhecido | 

### Exemplos simples
<a name="broadcast-mixed-devices-animations-examples"></a>

Os exemplos abaixo são do controle da câmera em tela cheia usando a configuração definida acima em [Criar e configurar um dispositivo de imagem de mixagem](#broadcast-mixed-image-device-creating-configuring). Isso fica animado por 0,5 segundo.

#### iOS
<a name="broadcast-mixed-devices-animations-examples-ios"></a>

```
// Continuing the example from above, modifying the existing cameraConfig object.
cameraConfig.size = CGSize(width: 1280, height: 720)
cameraConfig.position = CGPoint.zero
cameraSource.transition(to: cameraConfig, duration: 0.5) { completed in
    if completed {
        print("Animation completed")
    } else {
        print("Animation interrupted")
    }
}
```

#### Android
<a name="broadcast-mixed-devices-animations-examples-android"></a>

```
// Continuing the example from above, modifying the existing cameraConfig object.
cameraConfig.setSize(BroadcastConfiguration.Vec2(1280f, 720f))
cameraConfig.setPosition(BroadcastConfiguration.Vec2(0f, 0f))
cameraSource.transitionToConfiguration(cameraConfig, 500) { completed ->
    if (completed) {
        print("Animation completed")
    } else {
        print("Animation interrupted")
    }
}
```

## Espelhamento da transmissão
<a name="broadcast-mixed-devices-mirroring"></a>


| Para espelhar um dispositivo de imagem conectado na transmissão nessa direção... | Use um valor negativo para … | 
| --- | --- | 
| Horizontalmente | A largura do slot | 
| Verticalmente | A altura do slot | 
| Horizontalmento e verticalmente | Tanto a largura quanto a altura do slot | 

A posição precisará ser ajustada com base no mesmo valor para colocar o slot na posição correta quando espelhado.

Abaixo estão alguns exemplos para espelhar a transmissão horizontalmente e verticalmente.

### iOS
<a name="broadcast-mixed-devices-mirroring-ios"></a>

Espelhamento horizontal:

```
let cameraSource = IVSMixedImageDeviceSourceConfiguration()
cameraSource.size = CGSize(width: -320, height: 720)
// Add 320 to position x since our width is -320
cameraSource.position = CGPoint(x: 320, y: 0)
```

Espelhamento vertical:

```
let cameraSource = IVSMixedImageDeviceSourceConfiguration()
cameraSource.size = CGSize(width: 320, height: -720)
// Add 720 to position y since our height is -720
cameraSource.position = CGPoint(x: 0, y: 720)
```

### Android
<a name="broadcast-mixed-devices-mirroring-android"></a>

Espelhamento horizontal:

```
val cameraConfig = MixedImageDeviceSourceConfiguration().apply {
    setSize(BroadcastConfiguration.Vec2(-320f, 180f))
   // Add 320f to position x since our width is -320f
    setPosition(BroadcastConfiguration.Vec2(320f, 0f))
}
```

Espelhamento vertical:

```
val cameraConfig = MixedImageDeviceSourceConfiguration().apply {
    setSize(BroadcastConfiguration.Vec2(320f, -180f))
    // Add 180f to position y since our height is -180f
    setPosition(BroadcastConfiguration.Vec2(0f, 180f))
}
```

Observação: esse espelhamento é diferente do método `setMirrored` em `ImagePreviewView` (Android) e `IVSImagePreviewView` (iOS). Esse método afeta somente a visualização local no dispositivo e não afeta a transmissão.

# SDK de transmissão do IVS: troca de tokens \$1 streaming em tempo real
<a name="broadcast-mobile-token-exchange"></a>

A troca de tokens permite que você faça upgrade ou downgrade dos recursos do token do participante e atualize os atributos do token no SDK de transmissão móvel, sem exigir que os participantes se reconectem. Isso é útil em cenários como co-hospedagem, em que os participantes podem começar com recursos somente para assinantes e, posteriormente, precisarem de recursos de publicação.

Limitações:
+ A troca de tokens só funciona com tokens criados em seu servidor usando um [par de chaves](https://docs.aws.amazon.com//ivs/latest/RealTimeUserGuide/getting-started-distribute-tokens.html#getting-started-distribute-tokens-self-signed). Ela não funciona com tokens criados por meio da API [CreateParticipantToken](https://docs.aws.amazon.com/ivs/latest/RealTimeAPIReference/API_CreateParticipantToken.html). 
+ Se você usar a troca de tokens para alterar os atributos que impulsionam os layouts de composição do servidor (como featuredParticipantAttribute e participantOrderAttribute), o layout de uma composição ativa não será atualizado até que o participante se reconecte. 

## Troca de tokens
<a name="broadcast-mobile-token-exchange-exchanging-tokens"></a>

A troca de tokens é simples: chame a API `exchangeToken` no objeto `Stage`/`IVSStage` e forneça o novo token. Se os `capabilities` do novo token forem diferentes dos do token anterior, os recursos do novo token serão avaliados imediatamente. Por exemplo, se o token anterior não tiver o recurso `publish` e o novo token tiver, as funções de estratégia de estágio para publicação serão invocadas, permitindo que a aplicação de host decida se deseja publicar imediatamente com o novo recurso ou esperar. O mesmo vale para os recursos removidos: se o token anterior tinha a capacidade `publish` e o novo token não, o participante imediatamente cancela a publicação sem invocar as funções de estratégia de palco para publicação.

Ao trocar um token, o token anterior e o novo devem ter os mesmos valores para os seguintes campos de carga útil: 
+ `topic`
+ `resource`
+ `jti`
+ `whip_url`
+ `events_url`

Esses campos são imutáveis. A troca de um token que modifica um campo imutável faz com que o SDK rejeite imediatamente a troca.

Os campos restantes podem ser alterados, incluindo:
+ `attributes`
+ `capabilities`
+ `user`
+ `_id`
+ `iat`
+ `exp`

### iOS
<a name="broadcast-mobile-token-exchange-exchanging-tokens-ios"></a>



```
let stage = try IVSStage(token: originalToken, strategy: self)
stage.join()
stage.exchangeToken(newToken)
```

### Android
<a name="broadcast-mobile-token-exchange-exchanging-tokens-android"></a>



```
val stage = Stage(context, originalToken, strategy)
stage.join()
stage.exchangeToken(newToken)
```

## Receber atualizações
<a name="broadcast-mobile-token-exchange-receiving-updates"></a>

Uma função em `StageRenderer`/`IVSStageRenderer` recebe atualizações sobre participantes remotos já publicados que trocam seus tokens para atualizar seu `userId` ou `attributes`. Participantes remotos que ainda não estão publicando terão seus `userId` e `attributes` atualizados expostos por meio das funções de renderizador `onParticipantJoined`/`participantDidJoin`existentes se eventualmente publicarem.

### iOS
<a name="broadcast-mobile-token-exchange-receiving-updates-ios"></a>



```
class MyStageRenderer: NSObject, IVSStageRenderer {
    func stage(_ stage: IVSStage, participantMetadataDidUpdate participant: IVSParticipantInfo) {
        // participant will be a new IVSParticipantInfo instance with updated properties.
    }
}
```

### Android
<a name="broadcast-mobile-token-exchange-receiving-updates-android"></a>



```
private val stageRenderer = object : StageRenderer {
    override fun onParticipantMetadataUpdated(stage: Stage, participantInfo: ParticipantInfo) {
        // participantInfo will be a new ParticipantInfo instance with updated properties.
    }
}
```

## Visibilidade das atualizações
<a name="broadcast-mobile-token-exchange-visibility"></a>

Quando um participante troca um token para atualizar seu `userId` ou `attributes`, a visibilidade dessas alterações depende do estado atual de publicação: 
+ **Se o participante *não* estiver publicando:** a atualização será processada silenciosamente. Se eles acabarem sendo publicados, todos os SDKs receberão a atualização `userId` e `attributes` como parte do evento de publicação inicial.
+ **Se o participante *já* estiver publicando:** a atualização será transmitida imediatamente. No entanto, apenas os SDKs para dispositivos móveis versão 1.37.0 ou mais recente recebem a notificação. Os participantes do SDK da web, dos SDKs móveis mais antigos e da composição do servidor não veem a alteração até que o participante cancele a publicação e republique.

Esta tabela esclarece a matriz de suporte:


| Estado do participante | Observador: SDK móvel 1.37.0 ou mais recente | Observador: SDKs móveis mais antigos, SDK da web, composição do servidor | 
| --- | --- | --- | 
| Não está publicando (depois começa) | ✅ Visível (na publicação por meio do evento participante que ingressou) | ✅ Visível (na publicação por meio do evento participante que ingressou) | 
| Já está publicando (nunca republica) | ✅ Visível (imediatamente por meio do evento atualizado de metadados do participante) | ❌ Não visível | 
| Já está publicando (cancela e republica) | ✅ Visível (imediatamente por meio do evento atualizado de metadados do participante) | ⚠️ Com o tempo, visível (após republicação por meio do evento em que o participante ingressou) | 

# SDK de Transmissão do IVS: Fontes de imagens personalizadas \$1 Streaming em tempo real
<a name="broadcast-custom-image-sources"></a>

As fontes de entrada de imagem personalizadas permitem que uma aplicação forneça a própria entrada de imagem para o SDK de transmissão, em vez de se limitar às câmeras definidas previamente. Uma fonte de imagem personalizada pode ser algo tão simples quanto uma marca d’água semitransparente ou uma cena estática de “volto logo”, ou pode permitir que a aplicação realize um processamento personalizado adicional, como a adição de filtros de beleza à câmera.

Quando você usa uma fonte de entrada de imagem personalizada para o controle personalizado da câmera (p. ex., o uso de bibliotecas de filtro de beleza que exigem acesso à câmera), o SDK de Transmissão deixa de ser o responsável pelo gerenciamento da câmera. Em vez disso, a aplicação fica responsável por processar corretamente o ciclo de vida da câmera. Consulte a documentação oficial da plataforma sobre como sua aplicação deve gerenciar a câmera.

## Android
<a name="custom-image-sources-android"></a>

Após criar uma sessão `DeviceDiscovery`, crie uma fonte de entrada de imagem:

```
CustomImageSource imageSource = deviceDiscovery.createImageInputSource(new BroadcastConfiguration.Vec2(1280, 720));
```

Esse método retorna uma `CustomImageSource`, que é uma fonte de imagem corroborada por uma classe padrão [Surface](https://developer.android.com/reference/android/view/Surface) do Android. A subclasse `SurfaceSource` pode ser redimensionada e girada. Você também pode criar uma `ImagePreviewView` para exibir uma prévia de seu conteúdo.

Para recuperar a subjacente `Surface`:

```
Surface surface = surfaceSource.getInputSurface();
```

Essa `Surface` pode ser usada como o buffer de saída para produtores de imagens como Camera2, OpenGL ES e outras bibliotecas. O caso de uso mais simples é desenhar diretamente um bitmap estático ou uma cor na tela da Surface. No entanto, muitas bibliotecas (como bibliotecas de filtro de beleza) fornecem um método que permite que uma aplicação especifique uma `Surface` externa para renderização. Você pode usar tal método a fim de passar essa `Surface` para a biblioteca de filtros, permitindo que a biblioteca produza quadros processados para a sessão de transmissão transmitir.

Esta `CustomImageSource` pode ser agrupada em uma `LocalStageStream` e retornada pela `StageStrategy` para publicar em um `Stage`.

## iOS
<a name="custom-image-sources-ios"></a>

Após criar uma sessão `DeviceDiscovery`, crie uma fonte de entrada de imagem:

```
let customSource = broadcastSession.createImageSource(withName: "customSourceName")
```

Esse método retorna uma `IVSCustomImageSource`, que é uma fonte de imagem que permite que a aplicação envie `CMSampleBuffers`manualmente. Para obter os formatos de pixel compatíveis, consulte a Referência do iOS Broadcast SDK; um link para a versão mais atual está disponível nas [Notas de lançamento do Amazon IVS](release-notes.md) para a versão mais recente do Broadcast SDK.

As amostras enviadas para a fonte personalizada serão transmitidas para o Palco:

```
customSource.onSampleBuffer(sampleBuffer)
```

Para transmissão de vídeo, use esse método em um retorno de chamada. Por exemplo, se estiver usando a câmera, sempre que um novo buffer de amostra for recebido de uma `AVCaptureSession`, a aplicação pode encaminhar o buffer de amostra para a fonte de imagem personalizada. Se desejar, a aplicação pode aplicar processamento adicional (como um filtro de beleza) antes de enviar a amostra para a fonte de imagem personalizada.

A `IVSCustomImageSource` pode ser agrupada em uma `IVSLocalStageStream` e retornada pela `IVSStageStrategy` para publicar em um `Stage`.

# SDK de transmissão do IVS: fontes de imagens personalizadas \$1 streaming em tempo real
<a name="broadcast-custom-audio-sources"></a>

**Nota:** este guia aplica-se apenas ao SDK de transmissão de streaming em tempo real do IVS para Android. As informações sobre os SDKs do iOS e da web serão publicadas no futuro.

As fontes de entrada personalizada de áudio permitem que uma aplicação forneça a própria entrada de áudio para o SDK de transmissão, em vez de ficar limitado ao microfone embutido do dispositivo. Uma fonte de áudio personalizada permite que as aplicações transmitam áudio processado com efeitos, mixem vários fluxos de áudio ou se integrem a bibliotecas de processamento de áudio de terceiros.

Ao usar uma fonte de entrada de áudio personalizada, o SDK de transmissão deixa de ser responsável pelo gerenciamento direto do microfone. Em vez disso, sua aplicação é responsável por capturar, processar e enviar dados de áudio para a fonte personalizada.

O fluxo de trabalho da fonte de áudio personalizada segue estas etapas:

1. Entrada de áudio: crie uma fonte de áudio personalizada com formato de áudio especificado (taxa de amostragem, canais, formato). 

1. Seu processamento: capture ou gere dados de áudio do seu pipeline de processamento de áudio.

1. Fonte de áudio personalizada: envie buffers de áudio para a fonte personalizada usando `appendBuffer()`.

1. Palco: encapsulamento no `LocalStageStream` e publicação no palco por meio de seu `StageStrategy`. 

1. Participantes: os participantes do palco recebem o áudio processado em tempo real.

## Android
<a name="custom-audio-sources-android"></a>

### Criar uma fonte de áudio personalizada
<a name="custom-audio-sources-android-creating-a-custom-audio-source"></a>

Após criar uma sessão de `DeviceDiscovery`, crie uma fonte de entrada de áudio personalizada:

```
DeviceDiscovery deviceDiscovery = new DeviceDiscovery(context); 
 
// Create custom audio source with specific format 
CustomAudioSource customAudioSource = deviceDiscovery.createAudioInputSource( 
   2,  // Number of channels (1 = mono, 2 = stereo) 
   BroadcastConfiguration.AudioSampleRate.RATE_48000,  // Sample rate 
   AudioDevice.Format.INT16  // Audio format (16-bit PCM) 
);
```

Esse método retorna um `CustomAudioSource`, que aceita dados brutos de áudio PCM. A fonte de áudio personalizada deve ser configurada com o mesmo formato de áudio que seu pipeline de processamento de áudio produz.

#### Formatos de áudio compatíveis
<a name="custom-audio-sources-android-submitting-audio-data-supportedi-audio-formats"></a>


| Parâmetro | Opções | Descrição | 
| --- | --- | --- | 
| Canais | 1 (mono), 2 (estéreo) | O número de canais de áudio. | 
| Taxa de amostra | RATE\$116000, RATE\$144100, RATE\$148000 | Taxa de amostragem de áudio em Hz. 48kHz recomendados para alta qualidade. | 
| Formato | INT16, FLOAT32 | Formato de amostra de áudio. INT16 é PCM de ponto fixo de 16 bits, FLOAT32 é PCM de ponto flutuante de 32 bits. Os formatos intercalado e plano estão disponíveis. | 

### Enviar dados de áudio
<a name="custom-audio-sources-android-submitting-audio-data"></a>

Para enviar dados de áudio para a fonte personalizada, use o método `appendBuffer()`:

```
// Prepare audio data in a ByteBuffer 
ByteBuffer audioBuffer = ByteBuffer.allocateDirect(bufferSize); 
audioBuffer.put(pcmAudioData);  // Your processed audio data 
 
// Calculate the number of bytes 
long byteCount = pcmAudioData.length; 
 
// Submit audio to the custom source 
// presentationTimeUs should be generated by and come from your audio source
int samplesProcessed = customAudioSource.appendBuffer( 
   audioBuffer, 
   byteCount, 
   presentationTimeUs 
); 
 
if (samplesProcessed > 0) { 
   Log.d(TAG, "Successfully submitted " + samplesProcessed + " samples"); 
} else { 
   Log.w(TAG, "Failed to submit audio samples"); 
} 
 
// Clear buffer for reuse 
audioBuffer.clear();
```

**Considerações importantes:**
+ Os dados de áudio devem estar no formato especificado ao criar a fonte personalizada.
+ Os carimbos de data/hora devem aumentar monotonicamente e serem fornecidos pela fonte de áudio para uma reprodução suave do áudio.
+ Envie áudio regularmente para evitar lacunas na transmissão.
+ O método retorna o número de amostras processadas (0 indica falha). 

### Publicar em um palco
<a name="custom-audio-sources-android-publishing-to-a-stage"></a>

Encapsule o `CustomAudioSource` em um `AudioLocalStageStream` e retorne-o ao seu `StageStrategy`:

```
// Create the audio stream from custom source 
AudioLocalStageStream audioStream = new AudioLocalStageStream(customAudioSource); 
 
// Define your stage strategy 
Strategy stageStrategy = new Strategy() { 
   @NonNull 
   @Override 
   public List<LocalStageStream> stageStreamsToPublishForParticipant( 
         @NonNull Stage stage, 
         @NonNull ParticipantInfo participantInfo) { 
      List<LocalStageStream> streams = new ArrayList<>(); 
      streams.add(audioStream);  // Publish custom audio 
      return streams; 
   } 
 
   @Override 
   public boolean shouldPublishFromParticipant( 
         @NonNull Stage stage, 
         @NonNull ParticipantInfo participantInfo) { 
      return true;  // Control when to publish 
   } 
 
   @Override 
   public Stage.SubscribeType shouldSubscribeToParticipant( 
         @NonNull Stage stage, 
         @NonNull ParticipantInfo participantInfo) { 
      return Stage.SubscribeType.AUDIO_VIDEO; 
   } 
}; 
 
// Create and join the stage 
Stage stage = new Stage(context, stageToken, stageStrategy);
```

### Exemplo completo: integração de processamento de áudio
<a name="custom-audio-sources-android-complete-example"></a>

Aqui está um exemplo completo que mostra a integração com um SDK de processamento de áudio:

```
public class AudioStreamingActivity extends AppCompatActivity { 
   private DeviceDiscovery deviceDiscovery; 
   private CustomAudioSource customAudioSource; 
   private AudioLocalStageStream audioStream; 
   private Stage stage; 
 
   @Override 
   protected void onCreate(Bundle savedInstanceState) { 
      super.onCreate(savedInstanceState); 
 
      // Configure audio manager 
      StageAudioManager.getInstance(this) 
         .setPreset(StageAudioManager.UseCasePreset.VIDEO_CHAT); 
 
      // Initialize IVS components 
      initializeIVSStage(); 
 
      // Initialize your audio processing SDK 
      initializeAudioProcessing(); 
   } 
 
   private void initializeIVSStage() { 
      deviceDiscovery = new DeviceDiscovery(this); 
 
      // Create custom audio source (48kHz stereo, 16-bit) 
      customAudioSource = deviceDiscovery.createAudioInputSource( 
         2,  // Stereo 
         BroadcastConfiguration.AudioSampleRate.RATE_48000, 
         AudioDevice.Format.INT16 
      ); 
 
      // Create audio stream 
      audioStream = new AudioLocalStageStream(customAudioSource); 
 
      // Create stage with strategy 
      Strategy strategy = new Strategy() { 
         @NonNull 
         @Override 
         public List<LocalStageStream> stageStreamsToPublishForParticipant( 
               @NonNull Stage stage, 
               @NonNull ParticipantInfo participantInfo) { 
            return Collections.singletonList(audioStream); 
         } 
 
         @Override 
         public boolean shouldPublishFromParticipant( 
               @NonNull Stage stage, 
               @NonNull ParticipantInfo participantInfo) { 
            return true; 
         } 
 
         @Override 
         public Stage.SubscribeType shouldSubscribeToParticipant( 
               @NonNull Stage stage, 
               @NonNull ParticipantInfo participantInfo) { 
            return Stage.SubscribeType.AUDIO_VIDEO; 
         } 
      }; 
 
      stage = new Stage(this, getStageToken(), strategy); 
   } 
 
   private void initializeAudioProcessing() { 
      // Initialize your audio processing SDK 
      // Set up callback to receive processed audio 
      yourAudioSDK.setAudioCallback(new AudioCallback() { 
         @Override 
         public void onProcessedAudio(byte[] audioData, int sampleRate, 
                                     int channels, long timestamp) { 
            // Submit processed audio to IVS Stage 
            submitAudioToStage(audioData, timestamp); 
         } 
      }); 
   } 
 
   // The timestamp is required to come from your audio source and you  
   // should not be generating one on your own, unless your audio source 
   // does not provide one. If that is the case, create your own epoch  
   // timestamp and manually calculate the duration between each sample  
   // using the number of frames and frame size. 

   private void submitAudioToStage(byte[] audioData, long timestamp) { 
      try { 
         // Allocate direct buffer 
         ByteBuffer buffer = ByteBuffer.allocateDirect(audioData.length); 
         buffer.put(audioData); 
 
         // Submit to custom audio source 
         int samplesProcessed = customAudioSource.appendBuffer( 
            buffer, 
            audioData.length, 
            timestamp > 0 ? timestamp : System.nanoTime() / 1000 
         ); 
 
         if (samplesProcessed <= 0) { 
            Log.w(TAG, "Failed to submit audio samples"); 
         } 
 
         buffer.clear(); 
      } catch (Exception e) { 
         Log.e(TAG, "Error submitting audio: " + e.getMessage(), e); 
      } 
   } 
 
   @Override 
   protected void onDestroy() { 
      super.onDestroy(); 
      if (stage != null) { 
          stage.release(); 
      } 
   } 
}
```

### Práticas recomendadas
<a name="custom-audio-sources-android-best-practices"></a>

#### Consistência do formato de áudio
<a name="custom-audio-sources-android-best-practices-audio-format-consistency"></a>

Certifique-se de que o formato de áudio enviado corresponda ao formato especificado ao criar a fonte personalizada:

```
// If you create with 48kHz stereo INT16 
customAudioSource = deviceDiscovery.createAudioInputSource( 
   2, RATE_48000, INT16 
); 
 
// Your audio data must be: 
// - 2 channels (stereo) 
// - 48000 Hz sample rate 
// - 16-bit interleaved PCM format
```

#### Gerenciamento de buffer
<a name="custom-audio-sources-android-best-practices-buffer-managemetn"></a>

Use diretamente o `ByteBuffers` e reutilize-o para minimizar a coleta de resíduos: 

```
// Allocate once 
private ByteBuffer audioBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE); 
 
// Reuse in callback 
public void onAudioData(byte[] data) { 
   audioBuffer.clear(); 
   audioBuffer.put(data); 
   customAudioSource.appendBuffer(audioBuffer, data.length, getTimestamp()); 
   audioBuffer.clear(); 
}
```

#### Cronometragem e sincronização
<a name="custom-audio-sources-android-best-practices-timing-and-synchronization"></a>

Você deve usar os carimbos de data/hora fornecidos pela fonte de áudio para uma reprodução suave do áudio. Se sua fonte de áudio não fornecer seu próprio carimbo de data/hora, crie seu próprio carimbo de data/hora de época e calcule manualmente a duração entre cada amostra usando o número de quadros e o tamanho do quadro. 

```
// "audioFrameTimestamp" should be generated by your audio source
// Consult your audio source’s documentation for information on how to get this 
long timestamp = audioFrameTimestamp;
```

#### Como tratar erros
<a name="custom-audio-sources-android-best-practices-error-handling"></a>

Sempre verifique o valor de retorno de `appendBuffer()`: 

```
int samplesProcessed = customAudioSource.appendBuffer(buffer, count, timestamp); 
 
if (samplesProcessed <= 0) { 
   Log.w(TAG, "Audio submission failed - buffer may be full or format mismatch"); 
   // Handle error: check format, reduce submission rate, etc. 
}
```

# SDK de Transmissão do IVS: Filtros de câmera de terceiros \$1 Streaming em tempo real
<a name="broadcast-3p-camera-filters"></a>

Este guia pressupõe que você já esteja familiarizado com fontes de [imagem personalizada](broadcast-custom-image-sources.md), bem como com a integração do [SDK de Transmissão de streaming em tempo real do IVS](broadcast.md) com sua aplicação.

Os filtros de câmera permitem que os criadores de transmissões ao vivo aumentem ou alterem sua aparência facial ou de plano de fundo. Isso pode aumentar potencialmente o engajamento do espectador, atrair espectadores e aprimorar a experiência de transmissão ao vivo.

# Integração de filtros de câmera de terceiros
<a name="broadcast-3p-camera-filters-integrating"></a>

Você pode integrar SDKs de filtro de câmera de terceiros ao SDK de Transmissão do IVS alimentando a saída do SDK do filtro para uma [fonte de entrada de imagem personalizada](broadcast-custom-image-sources.md). Uma fonte de entrada de imagem personalizada permite que uma aplicação forneça a própria entrada de imagem para o SDK de Transmissão. O SDK de um provedor de filtro terceirizado pode gerenciar o ciclo de vida da câmera para processar imagens da câmera, aplicar um efeito de filtro e exibi-las em um formato que possa ser transmitido para uma fonte de imagem personalizada.

![\[A integração de SDKs de filtro de câmera de terceiros ao SDK de Transmissão do IVS é feita alimentando a saída do SDK do filtro para uma fonte de entrada de imagem personalizada.\]](http://docs.aws.amazon.com/pt_br/ivs/latest/RealTimeUserGuide/images/3P_Camera_Filters_Integrating.png)


Consulte a documentação do seu fornecedor de filtro terceirizado para conhecer os métodos integrados de conversão de um quadro de câmera, com o efeito de filtro, aplicada a um formato que possa ser transmitido para uma [fonte de entrada de imagem personalizada](broadcast-custom-image-sources.md). O processo varia de acordo com a versão do SDK de Transmissão do IVS em uso:
+ **Web**: o provedor do filtro deve ser capaz de renderizar sua saída em um elemento de tela. Assim, será possível usar o método [captureStream](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/captureStream) para retornar um MediaStream do conteúdo da tela. Em seguida, o MediaStream poderá ser convertido em uma instância de um [LocalStageStream](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/classes/LocalStageStream) e publicado em um palco.
+ **Android**: o SDK do provedor de filtro pode renderizar um quadro em um Android `Surface` fornecido pelo SDK do Transmissor do IVS ou converter o quadro em um bitmap. Se estiver usando um bitmap, ele poderá ser renderizado no `Surface` subjacente fornecido pela fonte de imagem personalizada ao desbloquear e gravar em uma tela.
+ **iOS**: o SDK de um provedor de filtro terceirizado deve fornecer um quadro de câmera com um efeito de filtro aplicado como um `CMSampleBuffer`. Consulte a documentação do SDK do seu fornecedor de filtro terceirizado para obter informações sobre como fazer a `CMSampleBuffer` ser a saída final após o processamento da imagem da câmera.

# Usar o BytePlus com o SDK de Transmissão do IVS
<a name="broadcast-3p-camera-filters-integrating-byteplus"></a>

Este documento explica como usar o SDK de Efeitos do BytePlus com o SDK de Transmissão do IVS.

## Android
<a name="integrating-byteplus-android"></a>

### Instalar e configurar o SDK do BytePlus Effects
<a name="integrating-byteplus-android-install-effects-sdk"></a>

Consulte o [Guia de acesso do BytePlus para Android](https://docs.byteplus.com/en/effects/docs/android-v4101-access-guide) para obter detalhes sobre como instalar, inicializar e configurar o SDK do BytePlus Effects.

### Configurar a fonte de imagem personalizada
<a name="integrating-byteplus-android-setup-image-source"></a>

Após inicializar o SDK, alimente os quadros de câmera processados com um efeito de filtro aplicado a uma fonte de entrada de imagem personalizada. Para fazer isso, crie uma instância de um objeto `DeviceDiscovery` e crie uma fonte de imagem personalizada. Observe que quando você usa uma fonte de entrada de imagem personalizada para o controle personalizado da câmera, o SDK de Transmissão deixa de ser o responsável pelo gerenciamento da câmera. Em vez disso, a aplicação fica responsável por processar corretamente o ciclo de vida da câmera.

#### Java
<a name="integrating-byteplus-android-setup-image-source-code"></a>

```
var deviceDiscovery = DeviceDiscovery(applicationContext)
var customSource = deviceDiscovery.createImageInputSource( BroadcastConfiguration.Vec2(
720F, 1280F
))
var surface: Surface = customSource.inputSurface
var filterStream = ImageLocalStageStream(customSource)
```

### Converter saída em bitmap e alimentar em fonte de entrada de imagem personalizada
<a name="integrating-byteplus-android-convert-to-bitmap"></a>

Para permitir que os quadros da câmera com um efeito de filtro aplicado do SDK do BytePlus Effect sejam encaminhados diretamente para o SDK de Transmissão do IVS, converta a saída de uma textura do SDK do BytePlus Effects em um bitmap. Quando uma imagem for processada, o método `onDrawFrame()` será invocado pelo SDK. O método `onDrawFrame()` é um método público da interface [GLSurfaceView.Renderer](https://developer.android.com/reference/android/opengl/GLSurfaceView.Renderer) do Android. No aplicativo de amostra para Android fornecido pelo BytePlus, esse método é chamado em cada quadro da câmera e gera uma textura. Simultaneamente, você pode complementar o método `onDrawFrame()` com a lógica para converter essa textura em um bitmap e alimentá-la em uma fonte de entrada de imagem personalizada. Conforme apresentado no exemplo de código a seguir, use o método `transferTextureToBitmap` fornecido pelo SDK do BytePlus para fazer essa conversão. Esse método é fornecido pela biblioteca [com.bytedance.labcv.core.util.ImageUtil](https://docs.byteplus.com/en/effects/docs/android-v4101-access-guide#Appendix:%20convert%20input%20texture%20to%202D%20texture%20with%20upright%20face) do SDK do BytePlus Effects, conforme apresentado na amostra de código a seguir. Em seguida, será possível renderizar para o Android `Surface` subjacente de um `CustomImageSource` gravando o bitmap resultante na tela do Surface. Muitas invocações sucessivas de `onDrawFrame()` resultarão em uma sequência de bitmaps e, quando combinadas, criarão uma transmissão de vídeo.

#### Java
<a name="integrating-byteplus-android-convert-to-bitmap-code"></a>

```
import com.bytedance.labcv.core.util.ImageUtil;
...
protected ImageUtil imageUtility;
...


@Override
public void onDrawFrame(GL10 gl10) {
  ...	
  // Convert BytePlus output to a Bitmap
  Bitmap outputBt = imageUtility.transferTextureToBitmap(output.getTexture(),ByteEffect     
  Constants.TextureFormat.Texture2D,output.getWidth(), output.getHeight());

  canvas = surface.lockCanvas(null);
  canvas.drawBitmap(outputBt, 0f, 0f, null);
  surface.unlockCanvasAndPost(canvas);
```

# Usar o DeepAR com o SDK de Transmissão do IVS
<a name="broadcast-3p-camera-filters-integrating-deepar"></a>

Este documento explica como usar o SDK do DeepAR com o SDK de Transmissão do IVS.

## Android
<a name="integrating-deepar-android"></a>

Consulte o [Guia de integração com Android do DeepAR](https://docs.deepar.ai/deepar-sdk/integrations/video-calling/amazon-ivs/android/) para obter detalhes sobre como integrar o SDK do DeepAR com o SDK de Transmissão do IVS para Android.

## iOS
<a name="integrating-deepar-ios"></a>

Consulte o [Guia de integração com iOS do DeepAR](https://docs.deepar.ai/deepar-sdk/integrations/video-calling/amazon-ivs/ios/) para obter detalhes sobre como integrar o SDK do DeepAR com o SDK de Transmissão do IVS para iOS.

# Usar o Snap com o SDK de Transmissão do IVS
<a name="broadcast-3p-camera-filters-integrating-snap"></a>

Este documento explica como usar o SDK de Kit de Câmera do Snap com o SDK de Transmissão do IVS.

## Web
<a name="integrating-snap-web"></a>

Esta seção pressupõe que você já esteja familiarizado com a [publicação e a inscrição em vídeos usando o SDK de Transmissão na Web](getting-started-pub-sub-web.md).

Para integrar o SDK Camera Kit do Snap com o SDK de Transmissão na Web de streaming em tempo real do IVS, você precisa:

1. Instalar o SDK Camera Kit e o Webpack. (Nosso exemplo usa o Webpack como empacotador, mas você pode usar qualquer empacotador de sua escolha.)

1. Create `index.html`.

1. Adicionar elementos de configuração.

1. Create `index.css`.

1. Exibir e configurar os participantes.

1. Exibir câmeras e microfones conectados.

1. Criar uma sessão do Camera Kit.

1. Obtenha as lentes e preencha o seletor de lentes.

1. Renderizar a saída de uma sessão do Camera Kit em uma tela.

1. Crie uma função para preencher o menu suspenso Lentes.

1. Fornecer uma fonte de mídia para o Camera Kit renderizar e publicar um `LocalStageStream`.

1. Create `package.json`.

1. Criar um arquivo de configuração do Webpack.

1. Configure um servidor HTTPS e teste.

Cada uma dessas etapas está descrita abaixo.

### Instalar o SDK Camera Kit e o Webpack
<a name="integrating-snap-web-install-camera-kit"></a>

Neste exemplo, usamos o Webpack como nosso empacotador; no entanto, você pode usar qualquer empacotador.

```
npm i @snap/camera-kit webpack webpack-cli
```

### Crie index.html
<a name="integrating-snap-web-create-index"></a>

Em seguida, crie o padrão em HTML e importe o SDK de Transmissão da Web como uma tag de script. No código a seguir, certifique-se de substituir `<SDK version>` pela versão do SDK de Transmissão que você estiver usando.

#### HTML
<a name="integrating-snap-web-create-index-code"></a>

```
<!--
/*! Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */
-->
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />

  <title>Amazon IVS Real-Time Streaming Web Sample (HTML and JavaScript)</title>

  <!-- Fonts and Styling -->
  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic" />
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.css" />
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/milligram/1.4.1/milligram.css" />
  <link rel="stylesheet" href="./index.css" />

  <!-- Stages in Broadcast SDK -->
  <script src="https://web-broadcast.live-video.net/<SDK version>/amazon-ivs-web-broadcast.js"></script>
</head>

<body>
  <!-- Introduction -->
  <header>
    <h1>Amazon IVS Real-Time Streaming Web Sample (HTML and JavaScript)</h1>

    <p>This sample is used to demonstrate basic HTML / JS usage. <b><a href="https://docs.aws.amazon.com/ivs/latest/LowLatencyUserGuide/multiple-hosts.html">Use the AWS CLI</a></b> to create a <b>Stage</b> and a corresponding <b>ParticipantToken</b>. Multiple participants can load this page and put in their own tokens. You can <b><a href="https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-guides/stages#glossary" target="_blank">read more about stages in our public docs.</a></b></p>
  </header>
  <hr />
  
  <!-- Setup Controls -->
 
  <!-- Display Local Participants -->
  
  <!-- Lens Selector -->

  <!-- Display Remote Participants -->

  <!-- Load All Desired Scripts -->
```

### Adicionar elementos de configuração
<a name="integrating-snap-web-add-setup-elements"></a>

Crie o HTML para selecionar uma câmera, microfone e lentes, e para especificar um token de participante:

#### HTML
<a name="integrating-snap-web-setup-controls-code"></a>

```
<!-- Setup Controls -->
  <div class="row">
    <div class="column">
      <label for="video-devices">Select Camera</label>
      <select disabled id="video-devices">
        <option selected disabled>Choose Option</option>
      </select>
    </div>
    <div class="column">
      <label for="audio-devices">Select Microphone</label>
      <select disabled id="audio-devices">
        <option selected disabled>Choose Option</option>
      </select>
    </div>
    <div class="column">
      <label for="token">Participant Token</label>
      <input type="text" id="token" name="token" />
    </div>
    <div class="column" style="display: flex; margin-top: 1.5rem">
      <button class="button" style="margin: auto; width: 100%" id="join-button">Join Stage</button>
    </div>
    <div class="column" style="display: flex; margin-top: 1.5rem">
      <button class="button" style="margin: auto; width: 100%" id="leave-button">Leave Stage</button>
    </div>
  </div>
```

Acrescente HTML adicional abaixo para exibir os feeds da câmera de participantes locais e remotos:

#### HTML
<a name="integrating-snap-web-local-remote-participants-code"></a>

```
 <!-- Local Participant -->
<div class="row local-container">
    <canvas id="canvas"></canvas>

    <div class="column" id="local-media"></div>
    <div class="static-controls hidden" id="local-controls">
      <button class="button" id="mic-control">Mute Mic</button>
      <button class="button" id="camera-control">Mute Camera</button>
    </div>
  </div>

  
  <hr style="margin-top: 5rem"/>
  
  <!-- Remote Participants -->
  <div class="row">
    <div id="remote-media"></div>
  </div>
```

Carregue lógica adicional, incluindo métodos auxiliares para configurar a câmera e o arquivo JavaScript incluído. (Posteriormente nesta seção, você criará esses arquivos JavaScript e os agrupará em um único arquivo, para poder importar o Camera Kit como um módulo. O arquivo JavaScript incluído conterá a lógica para configurar o Camera Kit, aplicar uma Lente e publicar o feed da câmera com uma Lente aplicada a um palco.) Adicione tags de fechamento para os elementos `body` e `html` para concluir a criação do `index.html`.

#### HTML
<a name="integrating-snap-web-load-all-scripts-code"></a>

```
<!-- Load all Desired Scripts -->
  <script src="./helpers.js"></script>
  <script src="./media-devices.js"></script>
  <!-- <script type="module" src="./stages-simple.js"></script> -->
  <script src="./dist/bundle.js"></script>
</body>
</html>
```

### Crie o index.css
<a name="integrating-snap-web-create-index-css"></a>

Crie um arquivo de origem de CSS para estilizar a página. Não examinaremos esse código para focar na lógica de gerenciamento de um palco e na integração com o SDK do Camera Kit do Snap.

#### CSS
<a name="integrating-snap-web-create-index-css-code"></a>

```
/*! Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */

html,
body {
  margin: 2rem;
  box-sizing: border-box;
  height: 100vh;
  max-height: 100vh;
  display: flex;
  flex-direction: column;
}

hr {
  margin: 1rem 0;
}

table {
  display: table;
}

canvas {
  margin-bottom: 1rem;
  background: green;
}

video {
  margin-bottom: 1rem;
  background: black;
  max-width: 100%;
  max-height: 150px;
}

.log {
  flex: none;
  height: 300px;
}

.content {
  flex: 1 0 auto;
}

.button {
  display: block;
  margin: 0 auto;
}

.local-container {
  position: relative;
}

.static-controls {
  position: absolute;
  margin-left: auto;
  margin-right: auto;
  left: 0;
  right: 0;
  bottom: -4rem;
  text-align: center;
}

.static-controls button {
  display: inline-block;
}

.hidden {
  display: none;
}

.participant-container {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  margin: 1rem;
}

video {
  border: 0.5rem solid #555;
  border-radius: 0.5rem;
}
.placeholder {
  background-color: #333333;
  display: flex;
  text-align: center;
  margin-bottom: 1rem;
}
.placeholder span {
  margin: auto;
  color: white;
}
#local-media {
  display: inline-block;
  width: 100vw;
}

#local-media video {
  max-height: 300px;
}

#remote-media {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: row;
  width: 100%;
}

#lens-selector {
  width: 100%;
  margin-bottom: 1rem;
}
```

### Exibir e configurar os participantes
<a name="integrating-snap-web-setup-participants"></a>

Em seguida, crie `helpers.js`, que contém métodos auxiliares que você usará para exibir e configurar os participantes:

#### JavaScript
<a name="integrating-snap-web-setup-participants-code"></a>

```
/*! Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */

function setupParticipant({ isLocal, id }) {
  const groupId = isLocal ? 'local-media' : 'remote-media';
  const groupContainer = document.getElementById(groupId);

  const participantContainerId = isLocal ? 'local' : id;
  const participantContainer = createContainer(participantContainerId);
  const videoEl = createVideoEl(participantContainerId);

  participantContainer.appendChild(videoEl);
  groupContainer.appendChild(participantContainer);

  return videoEl;
}

function teardownParticipant({ isLocal, id }) {
  const groupId = isLocal ? 'local-media' : 'remote-media';
  const groupContainer = document.getElementById(groupId);
  const participantContainerId = isLocal ? 'local' : id;

  const participantDiv = document.getElementById(
    participantContainerId + '-container'
  );
  if (!participantDiv) {
    return;
  }
  groupContainer.removeChild(participantDiv);
}

function createVideoEl(id) {
  const videoEl = document.createElement('video');
  videoEl.id = id;
  videoEl.autoplay = true;
  videoEl.playsInline = true;
  videoEl.srcObject = new MediaStream();
  return videoEl;
}

function createContainer(id) {
  const participantContainer = document.createElement('div');
  participantContainer.classList = 'participant-container';
  participantContainer.id = id + '-container';

  return participantContainer;
}
```

### Exibir câmeras e microfones conectados
<a name="integrating-snap-web-display-cameras-microphones"></a>

Em seguida, crie `media-devices.js`, que contém métodos auxiliares para exibir câmeras e microfones conectados ao seu dispositivo:

#### JavaScript
<a name="integrating-snap-web-display-cameras-microphones-code"></a>

```
/*! Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */

/**
 * Returns an initial list of devices populated on the page selects
 */
async function initializeDeviceSelect() {
  const videoSelectEl = document.getElementById('video-devices');
  videoSelectEl.disabled = false;

  const { videoDevices, audioDevices } = await getDevices();
  videoDevices.forEach((device, index) => {
    videoSelectEl.options[index] = new Option(device.label, device.deviceId);
  });

  const audioSelectEl = document.getElementById('audio-devices');

  audioSelectEl.disabled = false;
  audioDevices.forEach((device, index) => {
    audioSelectEl.options[index] = new Option(device.label, device.deviceId);
  });
}

/**
 * Returns all devices available on the current device
 */
async function getDevices() {
  // Prevents issues on Safari/FF so devices are not blank
  await navigator.mediaDevices.getUserMedia({ video: true, audio: true });

  const devices = await navigator.mediaDevices.enumerateDevices();
  // Get all video devices
  const videoDevices = devices.filter((d) => d.kind === 'videoinput');
  if (!videoDevices.length) {
    console.error('No video devices found.');
  }

  // Get all audio devices
  const audioDevices = devices.filter((d) => d.kind === 'audioinput');
  if (!audioDevices.length) {
    console.error('No audio devices found.');
  }

  return { videoDevices, audioDevices };
}

async function getCamera(deviceId) {
  // Use Max Width and Height
  return navigator.mediaDevices.getUserMedia({
    video: {
      deviceId: deviceId ? { exact: deviceId } : null,
    },
    audio: false,
  });
}

async function getMic(deviceId) {
  return navigator.mediaDevices.getUserMedia({
    video: false,
    audio: {
      deviceId: deviceId ? { exact: deviceId } : null,
    },
  });
}
```

### Criar uma sessão do Camera Kit
<a name="integrating-snap-web-camera-kit-session"></a>

Crie `stages.js`, que contém a lógica para aplicar uma Lente ao feed da câmera e publicar o feed em um palco. Recomendamos copiar e colar o bloco de código a seguir em `stages.js`. Em seguida, você pode revisar o código, peça por peça, para entender o que está acontecendo nas seções a seguir.

#### JavaScript
<a name="integrating-snap-web-camera-kit-session-code"></a>

```
/*! Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */

const {
  Stage,
  LocalStageStream,
  SubscribeType,
  StageEvents,
  ConnectionState,
  StreamType,
} = IVSBroadcastClient;

import {
  bootstrapCameraKit,
  createMediaStreamSource,
  Transform2D,
} from '@snap/camera-kit';

let cameraButton = document.getElementById('camera-control');
let micButton = document.getElementById('mic-control');
let joinButton = document.getElementById('join-button');
let leaveButton = document.getElementById('leave-button');

let controls = document.getElementById('local-controls');
let videoDevicesList = document.getElementById('video-devices');
let audioDevicesList = document.getElementById('audio-devices');

let lensSelector = document.getElementById('lens-selector');
let session;
let availableLenses = [];

// Stage management
let stage;
let joining = false;
let connected = false;
let localCamera;
let localMic;
let cameraStageStream;
let micStageStream;

const liveRenderTarget = document.getElementById('canvas');

const init = async () => {
  await initializeDeviceSelect();

  const cameraKit = await bootstrapCameraKit({
    apiToken: 'INSERT_YOUR_API_TOKEN_HERE',
  });

  session = await cameraKit.createSession({ liveRenderTarget });
  const { lenses } = await cameraKit.lensRepository.loadLensGroups([
    'INSERT_YOUR_LENS_GROUP_ID_HERE',
  ]);

  availableLenses = lenses;
  populateLensSelector(lenses);

  const snapStream = liveRenderTarget.captureStream();

  lensSelector.addEventListener('change', handleLensChange);
  lensSelector.disabled = true;
  cameraButton.addEventListener('click', () => {
    const isMuted = !cameraStageStream.isMuted;
    cameraStageStream.setMuted(isMuted);
    cameraButton.innerText = isMuted ? 'Show Camera' : 'Hide Camera';
  });

  micButton.addEventListener('click', () => {
    const isMuted = !micStageStream.isMuted;
    micStageStream.setMuted(isMuted);
    micButton.innerText = isMuted ? 'Unmute Mic' : 'Mute Mic';
  });

  joinButton.addEventListener('click', () => {
    joinStage(session, snapStream);
  });

  leaveButton.addEventListener('click', () => {
    leaveStage();
  });
};

async function setCameraKitSource(session, mediaStream) {
  const source = createMediaStreamSource(mediaStream);
  await session.setSource(source);
  source.setTransform(Transform2D.MirrorX);
  session.play();
}

const populateLensSelector = (lenses) => {
  lensSelector.innerHTML = '<option selected disabled>Choose Lens</option>';

  lenses.forEach((lens, index) => {
    const option = document.createElement('option');
    option.value = index;
    option.text = lens.name || `Lens ${index + 1}`;
    lensSelector.appendChild(option);
  });
};

const handleLensChange = (event) => {
  const selectedIndex = parseInt(event.target.value);
  if (session && availableLenses[selectedIndex]) {
    session.applyLens(availableLenses[selectedIndex]);
  }
};

const joinStage = async (session, snapStream) => {
  if (connected || joining) {
    return;
  }
  joining = true;

  const token = document.getElementById('token').value;

  if (!token) {
    window.alert('Please enter a participant token');
    joining = false;
    return;
  }

  // Retrieve the User Media currently set on the page
  localCamera = await getCamera(videoDevicesList.value);
  localMic = await getMic(audioDevicesList.value);
  await setCameraKitSource(session, localCamera);

  // Create StageStreams for Audio and Video
  cameraStageStream = new LocalStageStream(snapStream.getVideoTracks()[0]);
  micStageStream = new LocalStageStream(localMic.getAudioTracks()[0]);

  const strategy = {
    stageStreamsToPublish() {
      return [cameraStageStream, micStageStream];
    },
    shouldPublishParticipant() {
      return true;
    },
    shouldSubscribeToParticipant() {
      return SubscribeType.AUDIO_VIDEO;
    },
  };

  stage = new Stage(token, strategy);

  // Other available events:
  // https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-guides/stages#events
  stage.on(StageEvents.STAGE_CONNECTION_STATE_CHANGED, (state) => {
    connected = state === ConnectionState.CONNECTED;

    if (connected) {
      joining = false;
      controls.classList.remove('hidden');
      lensSelector.disabled = false;
    } else {
      controls.classList.add('hidden');
      lensSelector.disabled = true;
    }
  });

  stage.on(StageEvents.STAGE_PARTICIPANT_JOINED, (participant) => {
    console.log('Participant Joined:', participant);
  });

  stage.on(
    StageEvents.STAGE_PARTICIPANT_STREAMS_ADDED,
    (participant, streams) => {
      console.log('Participant Media Added: ', participant, streams);

      let streamsToDisplay = streams;

      if (participant.isLocal) {
        // Ensure to exclude local audio streams, otherwise echo will occur
        streamsToDisplay = streams.filter(
          (stream) => stream.streamType === StreamType.VIDEO
        );
      }

      const videoEl = setupParticipant(participant);
      streamsToDisplay.forEach((stream) =>
        videoEl.srcObject.addTrack(stream.mediaStreamTrack)
      );
    }
  );

  stage.on(StageEvents.STAGE_PARTICIPANT_LEFT, (participant) => {
    console.log('Participant Left: ', participant);
    teardownParticipant(participant);
  });

  try {
    await stage.join();
  } catch (err) {
    joining = false;
    connected = false;
    console.error(err.message);
  }
};

const leaveStage = async () => {
  stage.leave();

  joining = false;
  connected = false;

  cameraButton.innerText = 'Hide Camera';
  micButton.innerText = 'Mute Mic';
  controls.classList.add('hidden');
};

init();
```

Na primeira parte desse arquivo, importamos o SDK de Transmissão e o SDK do Camera Kit Web e inicializamos as variáveis que usaremos com cada SDK. Criaremos uma sessão do Camera Kit chamando `createSession` após [fazer o bootstrap do SDK do Camera Kit Web](https://kit.snapchat.com/reference/CameraKit/web/0.7.0/index.html#bootstrapping-the-sdk). Observe que um objeto de elemento de tela é transmitido para uma sessão e isso faz com que o Camera Kit seja renderizado nessa tela.

#### JavaScript
<a name="integrating-snap-web-camera-kit-session-code-2"></a>

```
/*! Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */

const {
  Stage,
  LocalStageStream,
  SubscribeType,
  StageEvents,
  ConnectionState,
  StreamType,
} = IVSBroadcastClient;

import {
  bootstrapCameraKit,
  createMediaStreamSource,
  Transform2D,
} from '@snap/camera-kit';

let cameraButton = document.getElementById('camera-control');
let micButton = document.getElementById('mic-control');
let joinButton = document.getElementById('join-button');
let leaveButton = document.getElementById('leave-button');

let controls = document.getElementById('local-controls');
let videoDevicesList = document.getElementById('video-devices');
let audioDevicesList = document.getElementById('audio-devices');

let lensSelector = document.getElementById('lens-selector');
let session;
let availableLenses = [];

// Stage management
let stage;
let joining = false;
let connected = false;
let localCamera;
let localMic;
let cameraStageStream;
let micStageStream;

const liveRenderTarget = document.getElementById('canvas');

const init = async () => {
  await initializeDeviceSelect();

  const cameraKit = await bootstrapCameraKit({
    apiToken: 'INSERT_YOUR_API_TOKEN_HERE',
  });

  session = await cameraKit.createSession({ liveRenderTarget });
```

### Obtenha as lentes e preencha o seletor de lentes
<a name="integrating-snap-web-fetch-apply-lens"></a>

Para buscar suas Lentes, substitua o espaço reservado para o ID de grupo de Lentes pelo seu próprio, que está disponível no [Portal do desenvolvedor do Camera Kit](https://camera-kit.snapchat.com/). Preencha a lista suspensa de seleção de lentes usando a função `populateLensSelector()` que criaremos posteriormente.

#### JavaScript
<a name="integrating-snap-web-fetch-apply-lens-code"></a>

```
session = await cameraKit.createSession({ liveRenderTarget });
  const { lenses } = await cameraKit.lensRepository.loadLensGroups([
    'INSERT_YOUR_LENS_GROUP_ID_HERE',
  ]);

  availableLenses = lenses;
  populateLensSelector(lenses);
```

### Renderizar a saída de uma sessão do Camera Kit em uma tela
<a name="integrating-snap-web-render-output-to-canvas"></a>

Use o método [captureStream](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/captureStream) para retornar um `MediaStream` do conteúdo da tela. A tela conterá uma transmissão de vídeo do feed da câmera com uma Lente aplicada. Além disso, adicione receptores de eventos para os botões de silenciar a câmera e o microfone, bem como receptores de eventos para entrar e sair de um palco. Caso um receptor entre em um palco, passaremos uma sessão do Camera Kit e o `MediaStream` da tela para que ela possa ser publicada em um palco.

#### JavaScript
<a name="integrating-snap-web-render-output-to-canvas-code"></a>

```
const snapStream = liveRenderTarget.captureStream();

  lensSelector.addEventListener('change', handleLensChange);
  lensSelector.disabled = true;
  cameraButton.addEventListener('click', () => {
    const isMuted = !cameraStageStream.isMuted;
    cameraStageStream.setMuted(isMuted);
    cameraButton.innerText = isMuted ? 'Show Camera' : 'Hide Camera';
  });

  micButton.addEventListener('click', () => {
    const isMuted = !micStageStream.isMuted;
    micStageStream.setMuted(isMuted);
    micButton.innerText = isMuted ? 'Unmute Mic' : 'Mute Mic';
  });

  joinButton.addEventListener('click', () => {
    joinStage(session, snapStream);
  });

  leaveButton.addEventListener('click', () => {
    leaveStage();
  });
};
```

### Crie uma função para preencher a lista suspensa de lentes
<a name="integrating-snap-web-populate-lens-dropdown"></a>

Crie a função a seguir para preencher o seletor **Lentes** com as lentes obtidas anteriormente. O seletor **Lentes** é um elemento de interface de usuário na página que permite selecionar uma lista de lentes para aplicar ao feed da câmera. Além disso, crie a função de retorno de chamada `handleLensChange` para aplicar a lente especificada quando ela for selecionada no menu suspenso **Lentes**.

#### JavaScript
<a name="integrating-snap-web-populate-lens-dropdown-code"></a>

```
const populateLensSelector = (lenses) => {
  lensSelector.innerHTML = '<option selected disabled>Choose Lens</option>';

  lenses.forEach((lens, index) => {
    const option = document.createElement('option');
    option.value = index;
    option.text = lens.name || `Lens ${index + 1}`;
    lensSelector.appendChild(option);
  });
};

const handleLensChange = (event) => {
  const selectedIndex = parseInt(event.target.value);
  if (session && availableLenses[selectedIndex]) {
    session.applyLens(availableLenses[selectedIndex]);
  }
};
```

### Fornecer o Camera com uma fonte de mídia para renderização e publicar um LocalStageStream
<a name="integrating-snap-web-publish-localstagestream"></a>

Para publicar uma transmissão de vídeo com uma Lente aplicada, crie uma função chamada `setCameraKitSource` para transmitir o `MediaStream` que foi capturado da tela anteriormente. O `MediaStream` da tela não estará fazendo nada no momento porque ainda não incorporamos nosso feed de câmera local. Podemos incorporar nosso feed de câmera local chamando o método auxiliar `getCamera` e atribuindo-o a `localCamera`. Em seguida, podemos transmitir nosso feed de câmera local (via `localCamera`) e o objeto da sessão para `setCameraKitSource`. A função `setCameraKitSource` converte nosso feed de câmera local em uma [fonte de mídia para o CameraKit](https://docs.snap.com/camera-kit/integrate-sdk/web/web-configuration#creating-a-camerakitsource) chamando `createMediaStreamSource`. Em seguida, a fonte de mídia para `CameraKit` é [transformada](https://docs.snap.com/camera-kit/integrate-sdk/web/web-configuration#2d-transforms) para espelhar a câmera frontal. O efeito da Lente é aplicado à fonte de mídia e renderizado na tela de saída chamando `session.play()`.

Agora, com a Lente aplicada ao `MediaStream` capturado da tela, poderemos publicá-lo em um palco. Fazemos isso criando um `LocalStageStream` com as faixas de vídeo do `MediaStream`. Em seguida, é possível transmitir uma instância de `LocalStageStream` para um `StageStrategy` para publicação.

#### JavaScript
<a name="integrating-snap-web-publish-localstagestream-code"></a>

```
async function setCameraKitSource(session, mediaStream) {
  const source = createMediaStreamSource(mediaStream);
  await session.setSource(source);
  source.setTransform(Transform2D.MirrorX);
  session.play();
}

const joinStage = async (session, snapStream) => {
  if (connected || joining) {
    return;
  }
  joining = true;

  const token = document.getElementById('token').value;

  if (!token) {
    window.alert('Please enter a participant token');
    joining = false;
    return;
  }

  // Retrieve the User Media currently set on the page
  localCamera = await getCamera(videoDevicesList.value);
  localMic = await getMic(audioDevicesList.value);
  await setCameraKitSource(session, localCamera);
  // Create StageStreams for Audio and Video
  // cameraStageStream = new LocalStageStream(localCamera.getVideoTracks()[0]);
  cameraStageStream = new LocalStageStream(snapStream.getVideoTracks()[0]);
  micStageStream = new LocalStageStream(localMic.getAudioTracks()[0]);

  const strategy = {
    stageStreamsToPublish() {
      return [cameraStageStream, micStageStream];
    },
    shouldPublishParticipant() {
      return true;
    },
    shouldSubscribeToParticipant() {
      return SubscribeType.AUDIO_VIDEO;
    },
  };
```

O código restante abaixo serve para criar e gerenciar nosso palco:

#### JavaScript
<a name="integrating-snap-web-create-manage-stage-code"></a>

```
stage = new Stage(token, strategy);

  // Other available events:
  // https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-guides/stages#events

  stage.on(StageEvents.STAGE_CONNECTION_STATE_CHANGED, (state) => {
    connected = state === ConnectionState.CONNECTED;

    if (connected) {
      joining = false;
      controls.classList.remove('hidden');
    } else {
      controls.classList.add('hidden');
    }
  });

  stage.on(StageEvents.STAGE_PARTICIPANT_JOINED, (participant) => {
    console.log('Participant Joined:', participant);
  });

  stage.on(
    StageEvents.STAGE_PARTICIPANT_STREAMS_ADDED,
    (participant, streams) => {
      console.log('Participant Media Added: ', participant, streams);

      let streamsToDisplay = streams;

      if (participant.isLocal) {
        // Ensure to exclude local audio streams, otherwise echo will occur
        streamsToDisplay = streams.filter(
          (stream) => stream.streamType === StreamType.VIDEO
        );
      }

      const videoEl = setupParticipant(participant);
      streamsToDisplay.forEach((stream) =>
        videoEl.srcObject.addTrack(stream.mediaStreamTrack)
      );
    }
  );

  stage.on(StageEvents.STAGE_PARTICIPANT_LEFT, (participant) => {
    console.log('Participant Left: ', participant);
    teardownParticipant(participant);
  });

  try {
    await stage.join();
  } catch (err) {
    joining = false;
    connected = false;
    console.error(err.message);
  }
};

const leaveStage = async () => {
  stage.leave();

  joining = false;
  connected = false;

  cameraButton.innerText = 'Hide Camera';
  micButton.innerText = 'Mute Mic';
  controls.classList.add('hidden');
};

init();
```

### Crie o package.json
<a name="integrating-snap-web-package-json"></a>

Crie `package.json` e adicione a configuração JSON a seguir. Esse arquivo define nossas dependências e inclui um comando de script para empacotar nosso código.

#### Configuração de JSON
<a name="integrating-snap-web-package-json-code"></a>

```
{
  "dependencies": {
    "@snap/camera-kit": "^0.10.0"
  },
  "name": "ivs-stages-with-snap-camerakit",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "build": "webpack"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "devDependencies": {
    "webpack": "^5.95.0",
    "webpack-cli": "^5.1.4"
  }
}
```

### Criar um arquivo de configuração do Webpack
<a name="integrating-snap-web-webpack-config"></a>

Crie `webpack.config.js` e adicione o código a seguir. Isso empacota o código que criamos até aqui para que você possa usar a instrução de importar para usar o Camera Kit.

#### JavaScript
<a name="integrating-snap-web-webpack-config-code"></a>

```
const path = require('path');
module.exports = {
  entry: ['./stage.js'],
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
};
```

Por fim, execute `npm run build` para empacotar seu JavaScript conforme definido no arquivo de configuração do Webpack. Para fins de teste, será possível servir HTML e JavaScript a partir de seu computador local. Neste exemplo, usamos o módulo `http.server` do Python. 

### Configure um servidor HTTPS e teste
<a name="integrating-snap-web-https-server-test"></a>

Para testar nosso código, é necessário configurar um servidor HTTPS. O uso de um servidor HTTPS para desenvolvimento local e testes da integração da sua aplicação da Web com o SDK do Snap Camera Kit ajudará a evitar problemas de CORS (compartilhamento de recursos de origem cruzada).

Abra um terminal e navegue até o diretório em que você criou todo o código até esse ponto. Use o comando a seguir para gerar um certificado SSL/TLS autoassinado e uma chave privada:

```
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes
```

Isso cria dois arquivos: `key.pem` (a chave privada) e `cert.pem` (o certificado autoassinado). Crie um novo arquivo de Python chamado `https_server.py` e adicione o seguinte código:

#### Python
<a name="integrating-snap-web-https-server-test-code"></a>

```
import http.server
import ssl

# Set the directory to serve files from
DIRECTORY = '.'

# Create the HTTPS server
server_address = ('', 4443)
httpd = http.server.HTTPServer(
    server_address, http.server.SimpleHTTPRequestHandler)

# Wrap the socket with SSL/TLS
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.load_cert_chain('cert.pem', 'key.pem')
httpd.socket = context.wrap_socket(httpd.socket, server_side=True)

print(f'Starting HTTPS server on https://localhost:4443, serving {DIRECTORY}')
httpd.serve_forever()
```

Abra um terminal, navegue até o diretório em que você criou o arquivo `https_server.py` e execute o comando a seguir:

```
python3 https_server.py
```

Isso inicia o servidor de HTTPS em https://localhost:4443, servindo arquivos do diretório atual. Certifique-se de que os arquivos `cert.pem` e `key.pem` estejam no mesmo diretório do arquivo `https_server.py`.

Abra seu navegador e navegue até https://localhost:4443. Como esse é um certificado SSL/TLS autoassinado, seu navegador não confiará nele, então você receberá um aviso. Como isso é apenas para fins de teste, você pode ignorar o aviso. Você então deverá ver o efeito de AR da lente do Snap que especificou anteriormente aplicado ao feed da câmera na tela.

Observe que essa configuração usando os módulos integrados `http.server` e `ssl` do Python é adequada para fins locais de desenvolvimento e teste, mas não é recomendada para um ambiente de produção. O certificado SSL/TLS autoassinado usado nessa configuração não é confiável para navegadores da Web e outros clientes, o que significa que os usuários receberão avisos de segurança ao acessar o servidor. Além disso, embora usemos os módulos http.server e ssl integrados do Python neste exemplo, você pode optar por usar outra solução de servidor de HTTPS.

## Android
<a name="integrating-snap-android"></a>

Para integrar o SDK do Camera Kit do Snap com o SDK de Transmissão do IVS para Android, você deverá instalar o SDK do Camera Kit, inicializar uma sessão do Camera Kit, aplicar uma Lente e alimentar a saída da sessão do Camera Kit para a fonte de entrada de imagem personalizada.

Para instalar o SDK do Camera Kit, adicione o seguinte ao arquivo `build.gradle` do seu módulo. Substitua `$cameraKitVersion` pela [versão mais recente do SDK do Camera Kit](https://docs.snap.com/camera-kit/integrate-sdk/mobile/changelog-mobile).

### Java
<a name="integrating-snap-android-install-camerakit-sdk-code"></a>

```
implementation "com.snap.camerakit:camerakit:$cameraKitVersion"
```

Inicialize e obtenha um `cameraKitSession`. O Camera Kit também conta com um pacote prático para as APIs [CameraX](https://developer.android.com/media/camera/camerax) do Android, de modo que você não precise escrever uma lógica complicada para usar o CameraX com o Camera Kit. Você pode usar o objeto `CameraXImageProcessorSource` como uma [fonte](https://snapchat.github.io/camera-kit-reference/api/android/latest/-camera-kit/com.snap.camerakit/-source/index.html) para [ImageProcessor](https://snapchat.github.io/camera-kit-reference/api/android/latest/-camera-kit/com.snap.camerakit/-image-processor/index.html), o que permite iniciar quadros de streaming de pré-visualização da câmera.

### Java
<a name="integrating-snap-android-initialize-camerakitsession-code"></a>

```
 protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        // Camera Kit support implementation of ImageProcessor that is backed by CameraX library:
        // https://developer.android.com/training/camerax
        CameraXImageProcessorSource imageProcessorSource = new CameraXImageProcessorSource( 
            this /*context*/, this /*lifecycleOwner*/
        );
        imageProcessorSource.startPreview(true /*cameraFacingFront*/);

        cameraKitSession = Sessions.newBuilder(this)
                .imageProcessorSource(imageProcessorSource)
                .attachTo(findViewById(R.id.camerakit_stub))
                .build();
    }
```

### Buscar e aplicar Lentes
<a name="integrating-snap-android-fetch-apply-lenses"></a>

Você pode configurar as Lentes e sua ordem no carrossel no [Portal do Desenvolvedor do Camera Kit](https://camera-kit.snapchat.com/):

#### Java
<a name="integrating-snap-android-configure-lenses-code"></a>

```
// Fetch lenses from repository and apply them
 // Replace LENS_GROUP_ID with Lens Group ID from https://camera-kit.snapchat.com
cameraKitSession.getLenses().getRepository().get(new Available(LENS_GROUP_ID), available -> {
     Log.d(TAG, "Available lenses: " + available);
     Lenses.whenHasFirst(available, lens -> cameraKitSession.getLenses().getProcessor().apply(lens, result -> {
          Log.d(TAG,  "Apply lens [" + lens + "] success: " + result);
      }));
});
```

Para transmitir, envie quadros processados para o `Surface` subjacente de uma fonte de imagem personalizada. Use um objeto `DeviceDiscovery` e crie um `CustomImageSource` para retornar um `SurfaceSource`. Em seguida, será possível renderizar a saída de uma sessão `CameraKit` para o `Surface` subjacente fornecido pelo `SurfaceSource`.

#### Java
<a name="integrating-snap-android-broadcast-code"></a>

```
val publishStreams = ArrayList<LocalStageStream>()

val deviceDiscovery = DeviceDiscovery(applicationContext)
val customSource = deviceDiscovery.createImageInputSource(BroadcastConfiguration.Vec2(720f, 1280f))

cameraKitSession.processor.connectOutput(outputFrom(customSource.inputSurface))
val customStream = ImageLocalStageStream(customSource)

// After rendering the output from a Camera Kit session to the Surface, you can 
// then return it as a LocalStageStream to be published by the Broadcast SDK
val customStream: ImageLocalStageStream = ImageLocalStageStream(surfaceSource)
publishStreams.add(customStream)

@Override
fun stageStreamsToPublishForParticipant(stage: Stage, participantInfo: ParticipantInfo): List<LocalStageStream> = publishStreams
```

# Usar substituição em segundo plano com o SDK de Transmissão do IVS
<a name="broadcast-3p-camera-filters-background-replacement"></a>

A substituição do fundo é um tipo de filtro de câmera que permite que criadores de transmissões ao vivo alterem seus planos de plano de fundo. Conforme exibido no diagrama a seguir, a substituição do plano de fundo envolve:

1. Obter uma imagem da câmera com base no feed da câmera ao vivo.

1. Segmentá-la em componentes de primeiro e segundo plano usando o Google ML Kit.

1. Combinar a máscara de segmentação resultante com uma imagem de plano de fundo personalizada.

1. Transmiti-la para uma fonte de imagem personalizada para transmissão.

![\[Fluxo de trabalho para implementar a substituição do plano de fundo.\]](http://docs.aws.amazon.com/pt_br/ivs/latest/RealTimeUserGuide/images/3P_Camera_Filters_Background_Replacement.png)


## Web
<a name="background-replacement-web"></a>

Esta seção pressupõe que você já esteja familiarizado com a [publicação e a inscrição em vídeos usando o SDK de Transmissão na Web](https://docs.aws.amazon.com//ivs/latest/RealTimeUserGuide/getting-started-pub-sub-web.html).

Para substituir o plano de fundo de uma transmissão ao vivo por uma imagem personalizada, use o [modelo de segmentação de selfies](https://developers.google.com/mediapipe/solutions/vision/image_segmenter#selfie-model) com o [MediaPipe Image Segmenter](https://developers.google.com/mediapipe/solutions/vision/image_segmenter). Esse é um modelo de machine-learning que identifica quais pixels no quadro do vídeo estão em primeiro ou segundo plano. Em seguida, será possível usar os resultados do modelo para substituir o fundo de uma transmissão ao vivo, copiando os pixels do primeiro plano do feed de vídeo para uma imagem personalizada representando o novo plano de fundo.

Para integrar a substituição em segundo plano com o SDK de Transmissão na Web de streaming em tempo real do IVS, você precisará:

1. Instalar o MediaPipe e o Webpack. (Nosso exemplo usa o Webpack como empacotador, mas você pode usar qualquer empacotador de sua escolha.)

1. Create `index.html`.

1. Adiciona elementos de mídia.

1. Adicionar uma tag de script.

1. Create `app.js`.

1. Carregar uma imagem de plano de fundo personalizada.

1. Crie uma instância de `ImageSegmenter`.

1. Renderizar o feed de vídeo em uma tela.

1. Criar uma lógica de substituição em segundo plano.

1. Criar um arquivo de configuração do Webpack.

1. Empacotar seu arquivo JavaScript.

### Instalar o MediaPipe e o Webpack
<a name="background-replacement-web-install-mediapipe-webpack"></a>

Para começar, instale os pacotes npm `@mediapipe/tasks-vision` e `webpack`. O exemplo abaixo usa o Webpack como um empacotador de JavaScript, mas você pode usar um empacotador diferente se quiser.

#### JavaScript
<a name="background-replacement-web-install-mediapipe-webpack-code"></a>

```
npm i @mediapipe/tasks-vision webpack webpack-cli
```

Certifique-se também de atualizar seu `package.json` para especificar `webpack` como seu script de compilação:

#### JavaScript
<a name="background-replacement-web-update-package-json-code"></a>

```
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack"
  },
```

### Crie index.html
<a name="background-replacement-web-create-index"></a>

Em seguida, crie o padrão em HTML e importe o SDK de Transmissão da Web como uma tag de script. No código a seguir, certifique-se de substituir `<SDK version>` pela versão do SDK de Transmissão que você estiver usando.

#### JavaScript
<a name="background-replacement-web-create-index-code"></a>

```
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />

  <!-- Import the SDK -->
  <script src="https://web-broadcast.live-video.net/<SDK version>/amazon-ivs-web-broadcast.js"></script>
</head>

<body>

</body>
</html>
```

### Adicionar elementos de mídia
<a name="background-replacement-web-add-media-elements"></a>

Em seguida, adicione um elemento de vídeo e dois elementos de tela na tag do corpo. O elemento de vídeo conterá o feed da câmera ao vivo e será usado como entrada para o MediaPipe Image Segmenter. O primeiro elemento de tela será usado para renderizar uma prévia do feed que será transmitido. O segundo elemento de tela será usado para renderizar a imagem personalizada que será usada como plano de fundo. Como a segunda tela com a imagem personalizada é usada somente como fonte para copiar programaticamente pixels dela para a tela final, ela ficará oculta.

#### JavaScript
<a name="background-replacement-web-add-media-elements-code"></a>

```
<div class="row local-container">
      <video id="webcam" autoplay style="display: none"></video>
    </div>
    <div class="row local-container">
      <canvas id="canvas" width="640px" height="480px"></canvas>

      <div class="column" id="local-media"></div>
      <div class="static-controls hidden" id="local-controls">
        <button class="button" id="mic-control">Mute Mic</button>
        <button class="button" id="camera-control">Mute Camera</button>
      </div>
    </div>
    <div class="row local-container">
      <canvas id="background" width="640px" height="480px" style="display: none"></canvas>
    </div>
```

### Adicionar uma tag de script
<a name="background-replacement-web-add-script-tag"></a>

Adicione uma tag de script para carregar um arquivo JavaScript incluído que conterá o código para fazer a substituição em segundo plano e publicá-lo em um palco:

```
<script src="./dist/bundle.js"></script>
```

### Criar app.js
<a name="background-replacement-web-create-appjs"></a>

Em seguida, crie um arquivo JavaScript para obter os objetos dos elementos da tela e do vídeo que foram criados na página HTML. Importe os módulos `ImageSegmenter` e `FilesetResolver`. O módulo `ImageSegmenter` será usado para realizar a tarefa de segmentação.

#### JavaScript
<a name="create-appjs-import-imagesegmenter-fileresolver-code"></a>

```
const canvasElement = document.getElementById("canvas");
const background = document.getElementById("background");
const canvasCtx = canvasElement.getContext("2d");
const backgroundCtx = background.getContext("2d");
const video = document.getElementById("webcam");

import { ImageSegmenter, FilesetResolver } from "@mediapipe/tasks-vision";
```

Em seguida, crie uma função chamada `init()` para recuperar o MediaStream da câmera do usuário e invoque uma função de retorno de chamada sempre que o quadro da câmera terminar de carregar. Adicione receptores de eventos para os botões de entrar e sair de um palco.

Observe que, ao entrar em um palco, transmitimos uma variável chamada `segmentationStream`. Trata-se de uma transmissão de vídeo capturado de um elemento de tela, contendo uma imagem de primeiro plano sobreposta à imagem personalizada que representa o plano de fundo. Posteriormente, essa transmissão personalizada será usada para criar uma instância de um `LocalStageStream`, que poderá ser publicada em um palco.

#### JavaScript
<a name="create-appjs-create-init-code"></a>

```
const init = async () => {
  await initializeDeviceSelect();

  cameraButton.addEventListener("click", () => {
    const isMuted = !cameraStageStream.isMuted;
    cameraStageStream.setMuted(isMuted);
    cameraButton.innerText = isMuted ? "Show Camera" : "Hide Camera";
  });

  micButton.addEventListener("click", () => {
    const isMuted = !micStageStream.isMuted;
    micStageStream.setMuted(isMuted);
    micButton.innerText = isMuted ? "Unmute Mic" : "Mute Mic";
  });

  localCamera = await getCamera(videoDevicesList.value);
  const segmentationStream = canvasElement.captureStream();

  joinButton.addEventListener("click", () => {
    joinStage(segmentationStream);
  });

  leaveButton.addEventListener("click", () => {
    leaveStage();
  });
};
```

### Carregar uma imagem de plano de fundo personalizada
<a name="background-replacement-web-background-image"></a>

Na parte inferior da função `init`, adicione código para chamar uma função `initBackgroundCanvas`, que carrega uma imagem personalizada de um arquivo local e a renderiza em uma tela. Definiremos essa função na próxima etapa. Atribua o `MediaStream` recuperado da câmera do usuário ao objeto de vídeo. Posteriormente, esse objeto de vídeo será passado para o Image Segmenter. Além disso, defina uma função chamada `renderVideoToCanvas` como a função de retorno de chamada a ser invocada sempre que um quadro de vídeo terminar o carregamento. Definiremos essa função em uma etapa posterior.

#### JavaScript
<a name="background-replacement-web-load-background-image-code"></a>

```
initBackgroundCanvas();

  video.srcObject = localCamera;
  video.addEventListener("loadeddata", renderVideoToCanvas);
```

Vamos implementar a função `initBackgroundCanvas`, que carrega uma imagem de um arquivo local. Neste exemplo, usamos a imagem de uma praia como plano de fundo personalizado. A tela contendo a imagem personalizada ficará oculta da exibição, pois ela será combinada com os pixels do primeiro plano do elemento de tela que contém o feed da câmera.

#### JavaScript
<a name="background-replacement-web-implement-initBackgroundCanvas-code"></a>

```
const initBackgroundCanvas = () => {
  let img = new Image();
  img.src = "beach.jpg";

  img.onload = () => {
    backgroundCtx.clearRect(0, 0, canvas.width, canvas.height);
    backgroundCtx.drawImage(img, 0, 0);
  };
};
```

### Criar uma instância do ImageSegmenter
<a name="background-replacement-web-imagesegmenter"></a>

Em seguida, crie uma instância de `ImageSegmenter`, que segmentará a imagem e retornará o resultado como uma máscara. Ao criar uma instância de um `ImageSegmenter`, você usará o [modelo de segmentação de selfies](https://developers.google.com/mediapipe/solutions/vision/image_segmenter#selfie-model).

#### JavaScript
<a name="background-replacement-web-imagesegmenter-code"></a>

```
const createImageSegmenter = async () => {
  const audio = await FilesetResolver.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.2/wasm");

  imageSegmenter = await ImageSegmenter.createFromOptions(audio, {
    baseOptions: {
      modelAssetPath: "https://storage.googleapis.com/mediapipe-models/image_segmenter/selfie_segmenter/float16/latest/selfie_segmenter.tflite",
      delegate: "GPU",
    },
    runningMode: "VIDEO",
    outputCategoryMask: true,
  });
};
```

### Renderizar o feed de vídeo em uma tela
<a name="background-replacement-web-render-video-to-canvas"></a>

Em seguida, crie a função que renderiza o feed de vídeo para o outro elemento da tela. Precisamos renderizar o feed de vídeo em uma tela para que possamos extrair os pixels do primeiro plano usando a API Canvas 2D. Ao fazer isso, também transmitiremos um quadro de vídeo para nossa instância de `ImageSegmenter`, usando o método [segmentforVideo](https://developers.google.com/mediapipe/api/solutions/js/tasks-vision.imagesegmenter#imagesegmentersegmentforvideo) para segmentar o primeiro plano em relação ao de fundo no quadro do vídeo. Quando o método [segmentforVideo](https://developers.google.com/mediapipe/api/solutions/js/tasks-vision.imagesegmenter#imagesegmentersegmentforvideo) retornar, ele invocará nossa função personalizada de retorno de chamada, `replaceBackground`, para fazer a substituição em segundo plano.

#### JavaScript
<a name="background-replacement-web-render-video-to-canvas-code"></a>

```
const renderVideoToCanvas = async () => {
  if (video.currentTime === lastWebcamTime) {
    window.requestAnimationFrame(renderVideoToCanvas);
    return;
  }
  lastWebcamTime = video.currentTime;
  canvasCtx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);

  if (imageSegmenter === undefined) {
    return;
  }

  let startTimeMs = performance.now();

  imageSegmenter.segmentForVideo(video, startTimeMs, replaceBackground);
};
```

### Criar uma lógica de substituição em segundo plano
<a name="background-replacement-web-logic"></a>

Crie a função `replaceBackground`, que mescla a imagem de plano de fundo personalizada com o primeiro plano do feed da câmera para substituir o plano de fundo. Primeiro, a função recupera os dados de pixel subjacentes da imagem de plano de fundo personalizada e do feed de vídeo dos dois elementos de tela criados anteriormente. Em seguida, ela fará uma iteração pela máscara fornecida por `ImageSegmenter`, que indica quais pixels estão em primeiro plano. Conforme percorre a máscara, ela copiará seletivamente os pixels que contenham o feed da câmera do usuário para os dados de pixels de plano de fundo correspondentes. Depois disso, ela converterá os dados finais de pixel com o primeiro plano copiado para o plano de fundo e os desenhará em uma tela.

#### JavaScript
<a name="background-replacement-web-logic-create-replacebackground-code"></a>

```
function replaceBackground(result) {
  let imageData = canvasCtx.getImageData(0, 0, video.videoWidth, video.videoHeight).data;
  let backgroundData = backgroundCtx.getImageData(0, 0, video.videoWidth, video.videoHeight).data;
  const mask = result.categoryMask.getAsFloat32Array();
  let j = 0;

  for (let i = 0; i < mask.length; ++i) {
    const maskVal = Math.round(mask[i] * 255.0);

    j += 4;
  // Only copy pixels on to the background image if the mask indicates they are in the foreground
    if (maskVal < 255) {
      backgroundData[j] = imageData[j];
      backgroundData[j + 1] = imageData[j + 1];
      backgroundData[j + 2] = imageData[j + 2];
      backgroundData[j + 3] = imageData[j + 3];
    }
  }

 // Convert the pixel data to a format suitable to be drawn to a canvas
  const uint8Array = new Uint8ClampedArray(backgroundData.buffer);
  const dataNew = new ImageData(uint8Array, video.videoWidth, video.videoHeight);
  canvasCtx.putImageData(dataNew, 0, 0);
  window.requestAnimationFrame(renderVideoToCanvas);
}
```

Para referência, aqui está o arquivo `app.js` completo contendo toda a lógica acima:

#### JavaScript
<a name="background-replacement-web-logic-app-js-code"></a>

```
/*! Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */

// All helpers are expose on 'media-devices.js' and 'dom.js'
const { setupParticipant } = window;

const { Stage, LocalStageStream, SubscribeType, StageEvents, ConnectionState, StreamType } = IVSBroadcastClient;
const canvasElement = document.getElementById("canvas");
const background = document.getElementById("background");
const canvasCtx = canvasElement.getContext("2d");
const backgroundCtx = background.getContext("2d");
const video = document.getElementById("webcam");

import { ImageSegmenter, FilesetResolver } from "@mediapipe/tasks-vision";

let cameraButton = document.getElementById("camera-control");
let micButton = document.getElementById("mic-control");
let joinButton = document.getElementById("join-button");
let leaveButton = document.getElementById("leave-button");

let controls = document.getElementById("local-controls");
let audioDevicesList = document.getElementById("audio-devices");
let videoDevicesList = document.getElementById("video-devices");

// Stage management
let stage;
let joining = false;
let connected = false;
let localCamera;
let localMic;
let cameraStageStream;
let micStageStream;
let imageSegmenter;
let lastWebcamTime = -1;

const init = async () => {
  await initializeDeviceSelect();

  cameraButton.addEventListener("click", () => {
    const isMuted = !cameraStageStream.isMuted;
    cameraStageStream.setMuted(isMuted);
    cameraButton.innerText = isMuted ? "Show Camera" : "Hide Camera";
  });

  micButton.addEventListener("click", () => {
    const isMuted = !micStageStream.isMuted;
    micStageStream.setMuted(isMuted);
    micButton.innerText = isMuted ? "Unmute Mic" : "Mute Mic";
  });

  localCamera = await getCamera(videoDevicesList.value);
  const segmentationStream = canvasElement.captureStream();

  joinButton.addEventListener("click", () => {
    joinStage(segmentationStream);
  });

  leaveButton.addEventListener("click", () => {
    leaveStage();
  });

  initBackgroundCanvas();

  video.srcObject = localCamera;
  video.addEventListener("loadeddata", renderVideoToCanvas);
};

const joinStage = async (segmentationStream) => {
  if (connected || joining) {
    return;
  }
  joining = true;

  const token = document.getElementById("token").value;

  if (!token) {
    window.alert("Please enter a participant token");
    joining = false;
    return;
  }

  // Retrieve the User Media currently set on the page
  localMic = await getMic(audioDevicesList.value);

  cameraStageStream = new LocalStageStream(segmentationStream.getVideoTracks()[0]);
  micStageStream = new LocalStageStream(localMic.getAudioTracks()[0]);

  const strategy = {
    stageStreamsToPublish() {
      return [cameraStageStream, micStageStream];
    },
    shouldPublishParticipant() {
      return true;
    },
    shouldSubscribeToParticipant() {
      return SubscribeType.AUDIO_VIDEO;
    },
  };

  stage = new Stage(token, strategy);

  // Other available events:
  // https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-guides/stages#events
  stage.on(StageEvents.STAGE_CONNECTION_STATE_CHANGED, (state) => {
    connected = state === ConnectionState.CONNECTED;

    if (connected) {
      joining = false;
      controls.classList.remove("hidden");
    } else {
      controls.classList.add("hidden");
    }
  });

  stage.on(StageEvents.STAGE_PARTICIPANT_JOINED, (participant) => {
    console.log("Participant Joined:", participant);
  });

  stage.on(StageEvents.STAGE_PARTICIPANT_STREAMS_ADDED, (participant, streams) => {
    console.log("Participant Media Added: ", participant, streams);

    let streamsToDisplay = streams;

    if (participant.isLocal) {
      // Ensure to exclude local audio streams, otherwise echo will occur
      streamsToDisplay = streams.filter((stream) => stream.streamType === StreamType.VIDEO);
    }

    const videoEl = setupParticipant(participant);
    streamsToDisplay.forEach((stream) => videoEl.srcObject.addTrack(stream.mediaStreamTrack));
  });

  stage.on(StageEvents.STAGE_PARTICIPANT_LEFT, (participant) => {
    console.log("Participant Left: ", participant);
    teardownParticipant(participant);
  });

  try {
    await stage.join();
  } catch (err) {
    joining = false;
    connected = false;
    console.error(err.message);
  }
};

const leaveStage = async () => {
  stage.leave();

  joining = false;
  connected = false;

  cameraButton.innerText = "Hide Camera";
  micButton.innerText = "Mute Mic";
  controls.classList.add("hidden");
};

function replaceBackground(result) {
  let imageData = canvasCtx.getImageData(0, 0, video.videoWidth, video.videoHeight).data;
  let backgroundData = backgroundCtx.getImageData(0, 0, video.videoWidth, video.videoHeight).data;
  const mask = result.categoryMask.getAsFloat32Array();
  let j = 0;

  for (let i = 0; i < mask.length; ++i) {
    const maskVal = Math.round(mask[i] * 255.0);

    j += 4;
    if (maskVal < 255) {
      backgroundData[j] = imageData[j];
      backgroundData[j + 1] = imageData[j + 1];
      backgroundData[j + 2] = imageData[j + 2];
      backgroundData[j + 3] = imageData[j + 3];
    }
  }
  const uint8Array = new Uint8ClampedArray(backgroundData.buffer);
  const dataNew = new ImageData(uint8Array, video.videoWidth, video.videoHeight);
  canvasCtx.putImageData(dataNew, 0, 0);
  window.requestAnimationFrame(renderVideoToCanvas);
}

const createImageSegmenter = async () => {
  const audio = await FilesetResolver.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.2/wasm");

  imageSegmenter = await ImageSegmenter.createFromOptions(audio, {
    baseOptions: {
      modelAssetPath: "https://storage.googleapis.com/mediapipe-models/image_segmenter/selfie_segmenter/float16/latest/selfie_segmenter.tflite",
      delegate: "GPU",
    },
    runningMode: "VIDEO",
    outputCategoryMask: true,
  });
};

const renderVideoToCanvas = async () => {
  if (video.currentTime === lastWebcamTime) {
    window.requestAnimationFrame(renderVideoToCanvas);
    return;
  }
  lastWebcamTime = video.currentTime;
  canvasCtx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);

  if (imageSegmenter === undefined) {
    return;
  }

  let startTimeMs = performance.now();

  imageSegmenter.segmentForVideo(video, startTimeMs, replaceBackground);
};

const initBackgroundCanvas = () => {
  let img = new Image();
  img.src = "beach.jpg";

  img.onload = () => {
    backgroundCtx.clearRect(0, 0, canvas.width, canvas.height);
    backgroundCtx.drawImage(img, 0, 0);
  };
};

createImageSegmenter();
init();
```

### Criar um arquivo de configuração do Webpack
<a name="background-replacement-web-webpack-config"></a>

Adicione essa configuração ao arquivo de configuração do Webpack para empacotar `app.js`, de modo que as chamadas de importação funcionem:

#### JavaScript
<a name="background-replacement-web-webpack-config-code"></a>

```
const path = require("path");
module.exports = {
  entry: ["./app.js"],
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "dist"),
  },
};
```

### Empacotar seus arquivos JavaScript
<a name="background-replacement-web-bundle-javascript"></a>

```
npm run build
```

Inicie um servidor HTTP simples no diretório que contém `index.html` e abra `localhost:8000` para ver o resultado:

```
python3 -m http.server -d ./
```

## Android
<a name="background-replacement-android"></a>

Para substituir o plano de fundo em sua transmissão ao vivo, você pode usar a API de segmentação de selfies do [Google ML Kit](https://developers.google.com/ml-kit/vision/selfie-segmentation). A API de segmentação de selfies aceita uma imagem da câmera como entrada e retorna uma máscara que fornece uma pontuação de confiança para cada pixel da imagem, indicando se ela estava em primeiro plano ou em segundo plano. Com base na pontuação de confiança, será possível recuperar a cor de pixel correspondente da imagem de plano de fundo ou da imagem de primeiro plano. Esse processo continua até que todas as pontuações de confiança na máscara tenham sido examinadas. O resultado será uma nova matriz de cores de pixels contendo pixels em primeiro plano combinados com pixels da imagem de plano de fundo.

Para integrar a substituição em segundo plano com o SDK de Transmissão para Android de streaming em tempo real do IVS, você precisará:

1. Instalar as bibliotecas CameraX e o Google ML Kit.

1. Inicializar variáveis clichê.

1. Criar uma fonte de imagens personalizada.

1. Gerenciar os quadros da câmera.

1. Transmitir as molduras da câmera para o Google ML Kit.

1. Sobrepor o primeiro plano da moldura da câmera ao seu plano de fundo personalizado.

1. Alimentar a nova imagem para uma fonte de imagem personalizada.

### Instalar as bibliotecas CameraX e o Google ML Kit
<a name="background-replacement-android-install-camerax-googleml"></a>

Para extrair imagens do feed da câmera ao vivo, use a biblioteca CameraX do Android. Para instalar a biblioteca CameraX e o Gooogle ML Kit, adicione o seguinte ao arquivo `build.gradle` do seu módulo. Substitua `${camerax_version}` e `${google_ml_kit_version}` pela versão mais recente das bibliotecas [CameraX](https://developer.android.com/jetpack/androidx/releases/camera) e [Google ML Kit](https://developers.google.com/ml-kit/vision/selfie-segmentation/android), respectivamente. 

#### Java
<a name="background-replacement-android-install-camerax-googleml-code"></a>

```
implementation "com.google.mlkit:segmentation-selfie:${google_ml_kit_version}"
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-lifecycle:${camerax_version}"
```

Importe as seguintes bibliotecas:

#### Java
<a name="background-replacement-android-import-libraries-code"></a>

```
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageProxy
import androidx.camera.lifecycle.ProcessCameraProvider
import com.google.mlkit.vision.segmentation.selfie.SelfieSegmenterOptions
```

### Inicializar variáveis clichê
<a name="background-replacement-android-initialize-variables"></a>

Inicialize uma instância de `ImageAnalysis` e uma instância de `ExecutorService`:

#### Java
<a name="background-replacement-android-initialize-imageanalysis-executorservice-code"></a>

```
private lateinit var binding: ActivityMainBinding
private lateinit var cameraExecutor: ExecutorService
private var analysisUseCase: ImageAnalysis? = null
```

Inicialize uma instância do Segmenter em [STREAM\$1MODE](https://developers.google.com/ml-kit/vision/selfie-segmentation/android#detector_mode):

#### Java
<a name="background-replacement-android-initialize-segmenter-code"></a>

```
private val options =
        SelfieSegmenterOptions.Builder()
            .setDetectorMode(SelfieSegmenterOptions.STREAM_MODE)
            .build()

private val segmenter = Segmentation.getClient(options)
```

### Criar uma fonte de imagens personalizada
<a name="background-replacement-android-create-image-source"></a>

No método `onCreate` da sua atividade, crie uma instância de um objeto `DeviceDiscovery` e crie uma fonte de imagem personalizada. O `Surface` fornecido pela Fonte de imagem personalizada receberá a imagem final, com o primeiro plano sobreposto a uma imagem de plano de fundo personalizada. Em seguida, você criará uma instância de um `ImageLocalStageStream` usando a fonte de imagem personalizada. Em seguida, a instância de um `ImageLocalStageStream` (nomeada `filterStream`, neste exemplo) poderá ser publicada em um palco. Consulte o [Guia do SDK de Transmissão do IVS para Android](broadcast-android.md) para obter instruções sobre como configurar um palco. Por fim, crie também um tópico que será usado para gerenciar a câmera.

#### Java
<a name="background-replacement-android-create-image-source-code"></a>

```
var deviceDiscovery = DeviceDiscovery(applicationContext)
var customSource = deviceDiscovery.createImageInputSource( BroadcastConfiguration.Vec2(
720F, 1280F
))
var surface: Surface = customSource.inputSurface
var filterStream = ImageLocalStageStream(customSource)

cameraExecutor = Executors.newSingleThreadExecutor()
```

### Gerenciar os quadros da câmera
<a name="background-replacement-android-camera-frames"></a>

Em seguida, crie uma função para inicializar a câmera. Essa função usa a biblioteca CameraX para extrair imagens do feed da câmera ao vivo. Primeiro, você cria uma instância de um `ProcessCameraProvider` chamado `cameraProviderFuture`. Esse objeto representa um resultado futuro da obtenção de um fornecedor de câmeras. Em seguida, você carrega uma imagem do seu projeto como um bitmap. Este exemplo usa a imagem de uma praia como plano de fundo, mas pode ser qualquer imagem que você quiser.

Em seguida, você adiciona um receptor a `cameraProviderFuture`. Esse receptor será notificado quando a câmera ficar disponível ou se ocorrer um erro durante o processo de obtenção de um receptor de câmeras.

#### Java
<a name="background-replacement-android-initialize-camera-code"></a>

```
private fun startCamera(surface: Surface) {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
        val imageResource = R.drawable.beach
        val bgBitmap: Bitmap = BitmapFactory.decodeResource(resources, imageResource)
        var resultBitmap: Bitmap;


        cameraProviderFuture.addListener({
            val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

            
                if (mediaImage != null) {
                    val inputImage =
                        InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)

                            resultBitmap = overlayForeground(mask, maskWidth, maskHeight, inputBitmap, backgroundPixels)
                            canvas = surface.lockCanvas(null);
                            canvas.drawBitmap(resultBitmap, 0f, 0f, null)

                            surface.unlockCanvasAndPost(canvas);

                        }
                        .addOnFailureListener { exception ->
                            Log.d("App", exception.message!!)
                        }
                        .addOnCompleteListener {
                            imageProxy.close()
                        }

                }
            };

            val cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA

            try {
                // Unbind use cases before rebinding
                cameraProvider.unbindAll()

                // Bind use cases to camera
                cameraProvider.bindToLifecycle(this, cameraSelector, analysisUseCase)

            } catch(exc: Exception) {
                Log.e(TAG, "Use case binding failed", exc)
            }

        }, ContextCompat.getMainExecutor(this))
    }
```

No receptor , crie `ImageAnalysis.Builder` para acessar cada quadro individual do feed da câmera ao vivo. Defina a estratégia de contrapressão como `STRATEGY_KEEP_ONLY_LATEST`. Isso garante que apenas um quadro de câmera seja entregue para processamento por vez. Converta cada quadro individual da câmera em um bitmap. Assim, será possível extrair seus pixels e depois combiná-los com a imagem de plano de fundo personalizada.

#### Java
<a name="background-replacement-android-create-imageanalysisbuilder-code"></a>

```
val imageAnalyzer = ImageAnalysis.Builder()
analysisUseCase = imageAnalyzer
    .setTargetResolution(Size(360, 640))
    .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
    .build()

analysisUseCase?.setAnalyzer(cameraExecutor) { imageProxy: ImageProxy ->
    val mediaImage = imageProxy.image
    val tempBitmap = imageProxy.toBitmap();
    val inputBitmap = tempBitmap.rotate(imageProxy.imageInfo.rotationDegrees.toFloat())
```

### Transmitir as molduras da câmera para o Google ML Kit
<a name="background-replacement-android-frames-to-mlkit"></a>

Em seguida, crie um `InputImage` e passe-o para a instância do Segmenter para processamento. Um `InputImage` pode ser criado com base em um `ImageProxy` fornecido pela instância de `ImageAnalysis`. Após o fornecer um `InputImage` ao Segmenter, ele retornará uma máscara com pontuações de confiança indicando a probabilidade de um pixel estar em primeiro plano ou em segundo plano. Essa máscara também fornece propriedades de largura e altura, que você usará para criar uma nova matriz contendo os pixels de plano de fundo da imagem de plano de fundo personalizada carregada anteriormente.

#### Java
<a name="background-replacement-android-frames-to-mlkit-code"></a>

```
if (mediaImage != null) {
        val inputImage =
            InputImage.fromMediaImag


segmenter.process(inputImage)
    .addOnSuccessListener { segmentationMask ->
        val mask = segmentationMask.buffer
        val maskWidth = segmentationMask.width
        val maskHeight = segmentationMask.height
        val backgroundPixels = IntArray(maskWidth * maskHeight)
        bgBitmap.getPixels(backgroundPixels, 0, maskWidth, 0, 0, maskWidth, maskHeight)
```

### Sobrepor o primeiro plano da moldura da câmera ao seu plano de fundo personalizado
<a name="background-replacement-android-overlay-frame-foreground"></a>

Com a máscara contendo as pontuações de confiança, a moldura da câmera como um bitmap e os pixels coloridos da imagem de plano de fundo personalizada, você tem tudo o que precisa para sobrepor o primeiro plano ao plano de fundo personalizado. Em seguida, a função `overlayForeground` será chamada com os seguintes parâmetros:

#### Java
<a name="background-replacement-android-call-overlayforeground-code"></a>

```
resultBitmap = overlayForeground(mask, maskWidth, maskHeight, inputBitmap, backgroundPixels)
```

Essa função percorre a máscara e verifica os valores de confiança para determinar se deseja obter a cor de pixel correspondente da imagem de plano de fundo ou da moldura da câmera. Se o valor de confiança indicar a probabilidade de que um pixel na máscara está em segundo plano, ele obterá a cor de pixel correspondente da imagem de plano de fundo. Caso contrário, ele obterá a cor de pixel correspondente da moldura da câmera para criar o primeiro plano. Quando a função terminar a iteração pela máscara, um novo bitmap será criado usando a nova matriz de pixels coloridos e retornado. Esse novo bitmap vai conter o primeiro plano sobreposto ao plano de fundo personalizado.

#### Java
<a name="background-replacement-android-run-overlayforeground-code"></a>

```
private fun overlayForeground(
        byteBuffer: ByteBuffer,
        maskWidth: Int,
        maskHeight: Int,
        cameraBitmap: Bitmap,
        backgroundPixels: IntArray
    ): Bitmap {
        @ColorInt val colors = IntArray(maskWidth * maskHeight)
        val cameraPixels = IntArray(maskWidth * maskHeight)

        cameraBitmap.getPixels(cameraPixels, 0, maskWidth, 0, 0, maskWidth, maskHeight)

        for (i in 0 until maskWidth * maskHeight) {
            val backgroundLikelihood: Float = 1 - byteBuffer.getFloat()

            // Apply the virtual background to the color if it's not part of the foreground
            if (backgroundLikelihood > 0.9) {
                // Get the corresponding pixel color from the background image
                // Set the color in the mask based on the background image pixel color
                colors[i] = backgroundPixels.get(i)
            } else {
                // Get the corresponding pixel color from the camera frame
                // Set the color in the mask based on the camera image pixel color
                colors[i] = cameraPixels.get(i)
            }
        }

        return Bitmap.createBitmap(
            colors, maskWidth, maskHeight, Bitmap.Config.ARGB_8888
        )
    }
```

### Alimentar a nova imagem para uma fonte de imagem personalizada
<a name="background-replacement-android-custom-image-source"></a>

Em seguida, será possível gravar o novo bitmap no `Surface` fornecido por uma fonte de imagem personalizada. Isso o transmitirá para o seu palco.

#### Java
<a name="background-replacement-android-custom-image-source-code"></a>

```
resultBitmap = overlayForeground(mask, inputBitmap, mutableBitmap, bgBitmap)
canvas = surface.lockCanvas(null);
canvas.drawBitmap(resultBitmap, 0f, 0f, null)
```

Aqui está a função completa para obter os quadros da câmera, transmiti-los para o Segmenter e sobrepô-los ao plano de fundo:

#### Java
<a name="background-replacement-android-custom-image-source-startcamera-code"></a>

```
@androidx.annotation.OptIn(androidx.camera.core.ExperimentalGetImage::class)
    private fun startCamera(surface: Surface) {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
        val imageResource = R.drawable.clouds
        val bgBitmap: Bitmap = BitmapFactory.decodeResource(resources, imageResource)
        var resultBitmap: Bitmap;

        cameraProviderFuture.addListener({
            // Used to bind the lifecycle of cameras to the lifecycle owner
            val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

            val imageAnalyzer = ImageAnalysis.Builder()
            analysisUseCase = imageAnalyzer
                .setTargetResolution(Size(720, 1280))
                .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
                .build()

            analysisUseCase!!.setAnalyzer(cameraExecutor) { imageProxy: ImageProxy ->
                val mediaImage = imageProxy.image
                val tempBitmap = imageProxy.toBitmap();
                val inputBitmap = tempBitmap.rotate(imageProxy.imageInfo.rotationDegrees.toFloat())

                if (mediaImage != null) {
                    val inputImage =
                        InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)

                    segmenter.process(inputImage)
                        .addOnSuccessListener { segmentationMask ->
                            val mask = segmentationMask.buffer
                            val maskWidth = segmentationMask.width
                            val maskHeight = segmentationMask.height
                            val backgroundPixels = IntArray(maskWidth * maskHeight)
                            bgBitmap.getPixels(backgroundPixels, 0, maskWidth, 0, 0, maskWidth, maskHeight)

                            resultBitmap = overlayForeground(mask, maskWidth, maskHeight, inputBitmap, backgroundPixels)
                            canvas = surface.lockCanvas(null);
                            canvas.drawBitmap(resultBitmap, 0f, 0f, null)

                            surface.unlockCanvasAndPost(canvas);

                        }
                        .addOnFailureListener { exception ->
                            Log.d("App", exception.message!!)
                        }
                        .addOnCompleteListener {
                            imageProxy.close()
                        }

                }
            };

            val cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA

            try {
                // Unbind use cases before rebinding
                cameraProvider.unbindAll()

                // Bind use cases to camera
                cameraProvider.bindToLifecycle(this, cameraSelector, analysisUseCase)

            } catch(exc: Exception) {
                Log.e(TAG, "Use case binding failed", exc)
            }

        }, ContextCompat.getMainExecutor(this))
    }
```

# SDK de Transmissão do IVS: Modos de áudio para dispositivos móveis \$1 Streaming em tempo real
<a name="broadcast-mobile-audio-modes"></a>

A qualidade do áudio é uma parte importante de qualquer experiência de mídia em tempo real, e não há uma configuração de áudio única que funcione melhor para cada caso de uso. Para garantir que seus usuários tenham a melhor experiência ao ouvir uma transmissão em tempo real do IVS, nossos SDKs móveis oferecem várias configurações de áudio predefinidas, bem como personalizações mais poderosas, conforme necessário.

## Introdução
<a name="broadcast-mobile-audio-modes-introduction"></a>

Os SDKs de transmissão móvel do IVS oferecem uma classe `StageAudioManager`. Essa classe foi projetada para ser o único ponto de contato para controlar os modos de áudio subjacentes em ambas as plataformas. No Android, isso controla o [AudioManager](https://developer.android.com/reference/android/media/AudioManager), incluindo o modo de áudio, a fonte do áudio, o tipo de conteúdo, o uso e os dispositivos de comunicação. No iOS, o elemento controla o a aplicação [AVAudioSession](https://developer.apple.com/documentation/avfaudio/avaudiosession), bem como se o [voiceProcessing](https://developer.apple.com/documentation/avfaudio/avaudioionode/3152101-voiceprocessingenabled?language=objc) está habilitado.

**Importante**: não interaja com `AVAudioSession` ou `AudioManager` diretamente enquanto o SDK de Transmissão em tempo real do IVS estiver ativo. Isso poderá resultar na perda de áudio ou na gravação ou reprodução do áudio no dispositivo errado.

Antes de criar seu primeiro objeto `DeviceDiscovery` ou `Stage`, é necessário configurar classe `StageAudioManager`.

------
#### [ Android (Kotlin) ]

```
StageAudioManager.getInstance(context).setPreset(StageAudioManager.UseCasePreset.VIDEO_CHAT) // The default value

val deviceDiscovery = DeviceDiscovery(context)
val stage = Stage(context, token, this)

// Other Stage implementation code
```

------
#### [ iOS (Swift) ]

```
IVSStageAudioManager.sharedInstance().setPreset(.videoChat) // The default value

let deviceDiscovery = IVSDeviceDiscovery()
let stage = try? IVSStage(token: token, strategy: self)

// Other Stage implementation code
```

------

Se nada for definido em `StageAudioManager` antes da inicialização de uma instância `DeviceDiscovery` ou `Stage`, a predefinição `VideoChat` será aplicada automaticamente.

## Predefinições do modo de áudio
<a name="broadcast-mobile-audio-modes-presets"></a>

O SDK de Transmissão em tempo real fornece três predefinições, cada uma personalizada para casos de uso comuns, conforme descrito abaixo. Para cada predefinição, abordamos cinco categorias principais que diferenciam as predefinições umas das outras.

A categoria **Volume Rocker** refere-se ao tipo de volume (volume de mídia ou volume de chamadas) que é usado ou alterado por meio dos controles de volume físicos no dispositivo. Observe que isso afeta o volume ao alternar os modos de áudio. Por exemplo, suponha que o volume do dispositivo esteja definido para o valor máximo ao usar a predefinição Video Chat. Alternar para a predefinição Subscribe Only gera um nível de volume diferente do sistema operacional, o que pode levar a uma alteração significativa no volume do dispositivo.

### Bate-papo por vídeo
<a name="audio-modes-presets-video-chat"></a>

Essa é a predefinição padrão, projetada para quando o dispositivo local tiver uma conversa em tempo real com outros participantes.

**Problema conhecido no iOS**: usar essa predefinição e não conectar um microfone faz com que o áudio seja reproduzido pelo fone de ouvido em vez de pelo alto-falante do dispositivo. Use essa predefinição somente em combinação com um microfone.


| Categoria | Android | iOS | 
| --- | --- | --- | 
| Cancelamento de eco | Habilitado | Habilitado | 
| Alternador de volume | Volume da chamada | Volume da chamada | 
| Seleção de microfone | Limitado com base no sistema operacional. Os microfones USB podem não estar disponíveis. | Limitado com base no sistema operacional. Os microfones USB e Bluetooth podem não estar disponíveis. Os fones de ouvido Bluetooth que processam entrada e saída juntas devem funcionar (por exemplo, AirPods). | 
| Saída de áudio | Qualquer dispositivo de saída deve funcionar. | Limitado com base no sistema operacional. Fones de ouvido com fio podem não estar disponíveis. | 
| Qualidade de áudio | Média/baixa. Soará como um telefonema, não como uma reprodução de mídia. | Média/baixa. Soará como um telefonema, não como uma reprodução de mídia. | 

### Somente inscrição
<a name="audio-modes-presets-subscribe-only"></a>

Essa predefinição foi criada para quando você planeja se inscrever em outros participantes da publicação, mas não publicar a si mesmo. Ela se concentra na qualidade do áudio e na compatibilidade com todos os dispositivos de saída disponíveis.


| Categoria | Android | iOS | 
| --- | --- | --- | 
| Cancelamento de eco | Desabilitado | Desabilitado | 
| Alternador de volume | Volume de mídia | Volume de mídia | 
| Seleção de microfone | N/D, essa predefinição não foi projetada para publicação. | N/D, essa predefinição não foi projetada para publicação. | 
| Saída de áudio | Qualquer dispositivo de saída deve funcionar. | Qualquer dispositivo de saída deve funcionar. | 
| Qualidade de áudio | Alta. Qualquer tipo de mídia deve ser transmitido com clareza, inclusive música. | Alta. Qualquer tipo de mídia deve ser transmitido com clareza, inclusive música. | 

### Studio
<a name="audio-modes-presets-studio"></a>

Essa predefinição foi projetada para inscrições de alta qualidade, mantendo a capacidade de publicação. É necessário que o hardware de gravação e reprodução forneça o cancelamento de eco. Um caso de uso aqui seria usar um microfone USB e um fone de ouvido com fio. O SDK manterá a mais alta qualidade de áudio e, ao mesmo tempo, dependerá da separação física desses dispositivos contra a ocorrência de eco.


| Categoria | Android | iOS | 
| --- | --- | --- | 
| Cancelamento de eco | O cancelamento de eco da plataforma está desativado, mas o cancelamento de eco por software ainda pode ocorrer se `StageAudioConfiguration.enableEchoCancellation` for verdadeiro. | Desabilitado | 
| Alternador de volume | Volume de mídia na maioria dos casos. Volume da chamada quando um microfone Bluetooth estiver conectado.  | Volume de mídia | 
| Seleção de microfone | Qualquer microfone deve funcionar. | Qualquer microfone deve funcionar. | 
| Saída de áudio | Qualquer dispositivo de saída deve funcionar. | Qualquer dispositivo de saída deve funcionar. | 
| Qualidade de áudio | Alta. Ambos os lados devem ser capazes de enviar música e ouvi-la claramente do outro lado. Quando um fone de ouvido Bluetooth for conectado, a qualidade do áudio diminuirá devido à ativação do modo SCO do Bluetooth. | Alta. Ambos os lados devem ser capazes de enviar música e ouvi-la claramente do outro lado. Quando um fone de ouvido Bluetooth for conectado, a qualidade do áudio poderá diminuir devido à ativação do modo SCO do Bluetooth, dependendo do fone de ouvido.  | 

## Casos de uso avançados
<a name="broadcast-mobile-audio-modes-advanced-use-cases"></a>

Além das predefinições, os SDKs de Transmissão de streaming em tempo real para iOS e Android permitem configurar os modos de áudio da plataforma subjacente:
+ No Android, defina [AudioSource](https://developer.android.com/reference/android/media/MediaRecorder.AudioSource), [Usage](https://developer.android.com/reference/android/media/AudioAttributes#USAGE_ALARM) e [ContentType](https://developer.android.com/reference/android/media/AudioAttributes#CONTENT_TYPE_MOVIE).
+ No iOS, use [AVAudioSession.Category](https://developer.apple.com/documentation/avfaudio/avaudiosession/category), [AVAudioSession.CategoryOptions](https://developer.apple.com/documentation/avfaudio/avaudiosession/categoryoptions), [AVAudioSession.Mode](https://developer.apple.com/documentation/avfaudio/avaudiosession/mode) e a capacidade de alternar se o [processamento de voz](https://developer.apple.com/documentation/avfaudio/avaudioionode/3152101-voiceprocessingenabled?language=objc) está habilitado ou não durante a publicação.

Observação: ao usar esses métodos de SDK de áudio, é possível configurar incorretamente a sessão de áudio subjacente. Por exemplo, usar a opção `.allowBluetooth` no iOS em combinação com a categoria `.playback` cria uma configuração de áudio inválida e o SDK não pode gravar ou reproduzir áudio. Esses métodos são projetados para serem usados somente quando uma aplicação tiver requisitos específicos de sessão de áudio que foram validados.

------
#### [ Android (Kotlin) ]

```
// This would act similar to the Subscribe Only preset, but it uses a different ContentType.
StageAudioManager.getInstance(context)
    .setConfiguration(StageAudioManager.Source.GENERIC,
                      StageAudioManager.ContentType.MOVIE,
                      StageAudioManager.Usage.MEDIA);

val stage = Stage(context, token, this)

// Other Stage implementation code
```

------
#### [ iOS (Swift) ]

```
// This would act similar to the Subscribe Only preset, but it uses a different mode and options.
IVSStageAudioManager.sharedInstance()
    .setCategory(.playback,
                 options: [.duckOthers, .mixWithOthers],
                 mode: .default)

let stage = try? IVSStage(token: token, strategy: self)

// Other Stage implementation code
```

------

### Cancelamento do Echo no iOS
<a name="advanced-use-cases-ios_echo_cancellation"></a>

O cancelamento do Echo no iOS também pode ser controlado via `IVSStageAudioManager` de forma independente usando o método `echoCancellationEnabled`. Esse método controla se o [processamento de voz](https://developer.apple.com/documentation/avfaudio/avaudioionode/3152101-voiceprocessingenabled?language=objc) está habilitado nos nós de entrada e saída do `AVAudioEngine` subjacente usado pelo SDK. É importante entender o efeito de alterar essa propriedade manualmente:
+ A propriedade `AVAudioEngine` será reconhecida somente se o microfone do SDK estiver ativo. Isso é necessário devido à exigência do iOS de que o processamento de voz seja habilitado simultaneamente nos nós de entrada e saída. Normalmente, isso é feito usando o microfone retornado pelo `IVSDeviceDiscovery` para criar um `IVSLocalStageStream` a ser publicado. Como alternativa, o microfone pode ser habilitado, sem ser usado para publicar, anexando uma `IVSAudioDeviceStatsCallback` ao próprio microfone. Essa abordagem alternativa será útil se o cancelamento do Echo for necessário ao usar um microfone personalizado baseado em fonte de áudio em vez do microfone do SDK do IVS.
+ A habilitação da propriedade `AVAudioEngine` requer um modo de `.videoChat` ou `.voiceChat`. Solicitar um modo diferente faz com que o framework de áudio subjacente do iOS resista ao SDK, causando perda de áudio.
+ Habilitar `AVAudioEngine` automaticamente habilita a opção `.allowBluetooth `.

Os comportamentos podem ser diferentes dependendo do dispositivo e da versão do iOS.

### Fontes de áudio personalizadas para iOS
<a name="advanced-use-cases-ios_custom_audio_sources"></a>

Fontes de áudio personalizadas podem ser usadas com o SDK usando `IVSDeviceDiscovery.createAudioSource`. Ao se conectar a um palco, o SDK de transmissão de streaming em tempo real do IVS ainda gerencia uma instância `AVAudioEngine` interna para reprodução de áudio, mesmo que o microfone do SDK não seja usado. Como resultado, os valores fornecidos para `IVSStageAudioManager` devem ser compatíveis com o áudio fornecido pela fonte de áudio personalizada.

Se a fonte de áudio personalizada usada para publicar estiver gravando do microfone, mas for gerenciada pela aplicação host, o SDK de cancelamento do Echo acima não funcionará, a menos que o microfone gerenciado pelo SDK seja ativado. Para contornar esse requisito, consulte [Cancelamento do Echo no iOS](#advanced-use-cases-ios_echo_cancellation).

### Publicação com Bluetooth no Android
<a name="advanced-use-cases-bluetooth-android"></a>

O SDK reverterá automaticamente para a predefinição `VIDEO_CHAT` no Android quando as condições a seguir forem atendidas:
+ A configuração atribuída não usar o valor de uso `VOICE_COMMUNICATION`.
+ Houver um microfone Bluetooth conectado ao dispositivo.
+ O participante local estiver publicando em um palco.

Essa é uma limitação do sistema operacional Android em relação à forma como os fones de ouvido Bluetooth são usados para gravar áudio.

## Integração com outros SDKs
<a name="broadcast-mobile-audio-modes-integrating-other-sdks"></a>

Como o iOS e o Android são compatíveis apenas com um modo de áudio ativo por aplicação, é comum entrar em conflito se o aplicativo usar vários SDKs que exijam controle do modo de áudio. Veja abaixo algumas estratégias comuns de resolução para tentar solucionar esses conflitos.

### Combinar os valores do modo de áudio
<a name="integrating-other-sdks-match-values"></a>

Usando as opções avançadas de configuração de áudio do SDK do IVS ou a funcionalidade de outro SDK, faça com que os dois SDKs se alinhem aos valores subjacentes.

### Agora
<a name="integrating-other-sdks-agora"></a>

#### iOS
<a name="integrating-other-sdks-agora-ios"></a>

No iOS, pedir que o SDK do Agora que mantenha o `AVAudioSession` ativo impedirá que ele seja desativado enquanto o SDK de Transmissão de streaming em tempo real do IVS estiver fazendo uso dele.

```
myRtcEngine.SetParameters("{\"che.audio.keep.audiosession\":true}");
```

#### Android
<a name="integrating-other-sdks-agora-android"></a>

Evite chamar `setEnableSpeakerphone` em `RtcEngine` e chamar `enableLocalAudio(false)` enquanto publica com o SDK de Transmissão de streaming em tempo real do IVS. Você pode chamar `enableLocalAudio(true)` novamente quando o SDK do IVS não estiver sendo publicado.