

# Conceitos básicos do Streaming em tempo real do IVS
<a name="getting-started"></a>

Este documento descreve as etapas envolvidas na integração do Streaming em tempo real do Amazon IVS com a sua aplicação.

**Topics**
+ [Introdução ao streaming em tempo real do IVS](getting-started-introduction.md)
+ [Etapa 1: Configurar permissões do IAM](getting-started-iam-permissions.md)
+ [Etapa 2: criar um palco com gravação opcional de participantes](getting-started-create-stage.md)
+ [Etapa 3: Distribuir tokens de participante](getting-started-distribute-tokens.md)
+ [Etapa 4: Integrar o SDK de Transmissão do IVS](getting-started-broadcast-sdk.md)
+ [Etapa 5: Publicar e assinar o vídeo](getting-started-pub-sub.md)

# Introdução ao streaming em tempo real do IVS
<a name="getting-started-introduction"></a>

Esta seção lista os pré-requisitos para uso do streaming em tempo real e apresenta a principal terminologia.

## Pré-requisitos
<a name="getting-started-introduction-prereq"></a>

Antes de usar o streaming em tempo real pela primeira vez, conclua as tarefas a seguir. Para obter instruções, consulte [Conceitos básicos do streaming de baixa latência do IVS](https://docs.aws.amazon.com/ivs/latest/LowLatencyUserGuide/getting-started.html).
+ Criar uma conta da AWS
+ Configurar os usuários raiz e administrativo

## Outras referências
<a name="getting-started-introduction-extref"></a>
+ [Referência do SDK de Transmissão do IVS para Web](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference)
+ [Referência do SDK de Transmissão do IVS para Android](https://aws.github.io/amazon-ivs-broadcast-docs/latest/android/)
+ [Referência do SDK de Transmissão do IVS para iOS](https://aws.github.io/amazon-ivs-broadcast-docs/latest/ios/)
+ [Referência de API do Streaming em tempo real do IVS](https://docs.aws.amazon.com/ivs/latest/RealTimeAPIReference/Welcome.html)

## Terminologia do streaming em tempo real
<a name="getting-started-introduction-terminology"></a>


| Prazo | Descrição | 
| --- | --- | 
| Estágio | Um espaço virtual no qual os participantes podem trocar vídeos em tempo real. | 
| Host | Um participante que envia vídeos locais para o palco. | 
| Visualizador | Um participante que recebe vídeos dos hosts. | 
| Participante | Um usuário conectado ao palco como host ou espectador. | 
| Token de participante | Um token que autentica um participante quando ele ingressa em um palco. | 
| SDK de transmissão | Uma biblioteca do cliente que possibilita aos participantes enviar e receber vídeos. | 

## Visão geral das etapas
<a name="getting-started-introduction-steps"></a>

1. [Configurar permissões do IAM](getting-started-iam-permissions.md): crie uma política do AWS Identity and Access Management (IAM) que conceda aos usuários um conjunto básico de permissões e atribua essa política aos usuários.

1. [Criar um palco](getting-started-create-stage.md): crie um espaço virtual no qual os participantes possam trocar vídeos em tempo real.

1. [Distribuir tokens de participantes](getting-started-distribute-tokens.md): envie tokens aos participantes para que eles possam ingressar no seu palco.

1. [Integrar o SDK de Transmissão do IVS](getting-started-broadcast-sdk.md): adicione o SDK de transmissão à sua aplicação para possibilitar que os participantes enviem e recebam vídeos: [Web](getting-started-broadcast-sdk.md#getting-started-broadcast-sdk-web), [Android](getting-started-broadcast-sdk.md#getting-started-broadcast-sdk-android) e [iOS](getting-started-broadcast-sdk.md#getting-started-broadcast-sdk-ios).

1. [Publicar e inscrever-se em vídeos](getting-started-pub-sub.md): envie seu vídeo para o palco e receba vídeos de outros hosts: [console do IVS](getting-started-pub-sub.md#getting-started-pub-sub-console), [Publicar e assinar com o SDK de Transmissão na Web do IVS](getting-started-pub-sub-web.md), [Publicar e assinar com o SDK de Transmissão para Android do IVS](getting-started-pub-sub-android.md) e [Publicar e assinar com o SDK de Transmissão para iOS do IVS](getting-started-pub-sub-ios.md).

# Etapa 1: Configurar permissões do IAM
<a name="getting-started-iam-permissions"></a>

Em seguida, você deverá criar uma política do AWS Identity and Access Management (IAM) que conceda aos usuários um conjunto básico de permissões (por exemplo, para criar um palco do Amazon IVS e criar tokens de participante) e atribuir essa política a usuários. É possível atribuir as permissões ao criar um [novo usuário](#iam-permissions-new-user) ou adicionar as permissões a um [usuário existente](#iam-permissions-existing-user). Os dois procedimentos são apresentados abaixo.

Para obter mais informações (por exemplo, para aprender sobre usuários e políticas do IAM, como anexar uma política a um usuário e como restringir o que os usuários podem fazer com o Amazon IVS), consulte:
+ [Como criar um usuário do IAM](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users_create.html#Using_CreateUser_console) no *Guia do usuário do IAM*
+ As informações em [Segurança do Amazon IVS](https://docs.aws.amazon.com/ivs/latest/LowLatencyUserGuide/security.html) sobre IAM e “Managed Policies for IVS”. 
+ As informações sobre o IAM apresentadas em [Amazon IVS Security](https://docs.aws.amazon.com/ivs/latest/LowLatencyUserGuide/security.html)

Você pode usar uma política gerenciada pela AWS existente para o Amazon IVS ou criar uma política que personalize as permissões que você quer conceder a um conjunto de usuários, grupos ou perfis. Ambas as abordagens são descritas a seguir.

## Usar uma política existente para permissões do IVS
<a name="iam-permissions-existing-policy"></a>

Na maioria dos casos, você vai querer usar uma política gerenciada pela AWS para o Amazon IVS. Elas são descritas totalmente na seção [Managed Policies for IVS](https://docs.aws.amazon.com/ivs/latest/LowLatencyUserGuide/security-iam-awsmanpol.html) da *Segurança do IVS*.
+ Use a política gerenciada pela AWS `IVSReadOnlyAccess` para dar aos desenvolvedores de aplicações acesso a todas as operações das APIs Get e List do IVS (para streaming de baixa latência e em tempo real).
+ Use a política gerenciada pela AWS `IVSFullAccess` para dar aos desenvolvedores de aplicações acesso a todas as operações das APIs do IVS (para streaming de baixa latência e em tempo real).

## Opcional: criar uma política personalizada para permissões do Amazon IVS
<a name="iam-permissions-new-policy"></a>

Siga estas etapas:

1. Faça login no Console de Gerenciamento da AWS e abra o console do IAM em [https://console.aws.amazon.com/iam/](https://console.aws.amazon.com/iam/).

1. No painel de navegação, selecione **Políticas** e, em seguida, **Criar política**. Uma janela **Especificar permissões** é aberta.

1. Na janela **Especificar permissões**, escolha a guia **JSON**, e copie e cole a política do IVS a seguir na área de texto do **Editor de políticas**. A política não inclui todas as ações do Amazon IVS. Você pode adicionar/excluir (Permitir/Negar) permissões de acesso ao endpoint, conforme necessário. Consulte [IVS Real-Time Streaming API Reference](https://docs.aws.amazon.com//ivs/latest/RealTimeAPIReference/Welcome.html) para obter detalhes sobre endpoints do IVS.

------
#### [ JSON ]

****  

   ```
   {
      "Version":"2012-10-17",		 	 	 
      "Statement": [
         {
            "Effect": "Allow",
            "Action": [
               "ivs:CreateStage",
               "ivs:CreateParticipantToken",
               "ivs:GetStage",
               "ivs:GetStageSession",
               "ivs:ListStages",
               "ivs:ListStageSessions",
               "ivs:CreateEncoderConfiguration",
               "ivs:GetEncoderConfiguration",
               "ivs:ListEncoderConfigurations",
               "ivs:GetComposition",
               "ivs:ListCompositions",
               "ivs:StartComposition",
               "ivs:StopComposition"
             ],
             "Resource": "*"
         },
         {
            "Effect": "Allow",
            "Action": [
               "cloudwatch:DescribeAlarms",
               "cloudwatch:GetMetricData",
               "s3:DeleteBucketPolicy",
               "s3:GetBucketLocation",
               "s3:GetBucketPolicy",
               "s3:PutBucketPolicy",
               "servicequotas:ListAWSDefaultServiceQuotas",
               "servicequotas:ListRequestedServiceQuotaChangeHistoryByQuota",
               "servicequotas:ListServiceQuotas",
               "servicequotas:ListServices",
               "servicequotas:ListTagsForResource"
            ],
            "Resource": "*"
         }
      ]
   }
   ```

------

1. Ainda na janela **Especificar permissões**, escolha **Avançar** (role até a parte inferior da janela para ver isso). Uma janela **Revisar e criar** é aberta. 

1. Na janela **Revisar e criar**, insira um **Nome da política** e, opcionalmente, adicione uma **Descrição**. Anote o nome da política, pois ele será necessário ao criar usuários (abaixo). Escolha **Create policy** (Criar política) na parte inferior da janela.

1. Você será levado de volta para a janela do console do IAM, onde deverá ver um banner confirmando que sua nova política foi criada.

## Criar usuários e adicionar permissões
<a name="iam-permissions-new-user"></a>

### Chaves de acesso do usuário do IAM
<a name="iam-permissions-new-user-access-keys"></a>

As chaves de acesso do IAM consistem em um ID de chave de acesso e em uma chave de acesso secreta. Elas são usadas para assinar as solicitações programáticas que você faz à AWS. Se não tiver chaves de acesso, será possível criá-las a partir do Console de Gerenciamento da AWS. Como prática recomendada, não crie chaves de acesso do usuário raiz.

*A única vez que é possível exibir ou baixar uma chave de acesso secreta é quando você cria chaves de acesso. Não será possível recuperá-las posteriormente.* Contudo, é possível criar novas chaves de acesso a qualquer momento, caso tenha as permissões para realizar as ações do IAM necessárias.

Sempre armazene as chaves de acesso com segurança. Nunca as compartilhe com terceiros (mesmo que uma consulta pareça vir da Amazon). Para obter mais informações, consulte [Como gerenciar chaves de acesso para usuários do IAM](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html) no *Manual do usuário do IAM*.

### Procedimento
<a name="iam-permissions-new-user-procedure"></a>

Siga estas etapas:

1. No painel de navegação, selecione **Usuários** e depois **Criar usuário**. Uma janela **Especificar detalhes do usuário** é aberta. 

1. Na janela **Especificar detalhes do usuário**:

   1. Em **Detalhes do usuário**, digite o novo **Nome de usuário** a ser criado.

   1. Marque **Fornecer ao usuário acesso ao Console de Gerenciamento da AWS)**.

   1. Em **Senha do console**, selecione **Senha gerada automaticamente**.

   1. Marque **Usuários devem criar uma nova senha no próximo login**.

   1. Escolha **Próximo**. Uma janela **Definir permissões** é aberta.

1. Em **Definir permissões**, selecione **Anexar políticas diretamente**. Uma janela **Políticas de permissões** é aberta.

1. Na caixa de pesquisa, insira o nome de uma política do IVS (uma política gerenciada pela AWS ou sua política personalizada criada anteriormente). Quando ela for localizada, marque a caixa para selecionar a política.

1. Escolha **Avançar** (na parte inferior da janela). Uma janela **Revisar e criar** é aberta.

1. Na janela **Revisar e criar**, confirme que todos os detalhes do usuário estão corretos e escolha **Criar usuário** (na parte inferior da janela).

1. A janela **Recuperar senha** é aberta, contendo seus **Detalhes de login no console**. *Salve essas informações em segurança para referência futura*. Quando terminar, escolha **Voltar à lista de usuários**.

## Adicionar permissões para um usuário existente
<a name="iam-permissions-existing-user"></a>

Siga estas etapas:

1. Faça login no Console de Gerenciamento da AWS e abra o console do IAM em [https://console.aws.amazon.com/iam/](https://console.aws.amazon.com/iam/).

1. No painel de navegação, escolha **Users** (Usuários) e escolha um nome de usuário existente a ser atualizado. (Escolha o nome clicando nele; não marque a caixa de seleção.)

1. Na página **Resumo**, na guia **Permissões**, escolha **Adicionar permissões**. Uma janela **Adicionar permissões** é aberta.

1. Selecione **Attach existing policies directly (Anexar políticas existentes diretamente)**. Uma janela **Políticas de permissões** é aberta.

1. Na caixa de pesquisa, insira o nome de uma política do IVS (uma política gerenciada pela AWS ou sua política personalizada criada anteriormente). Quando a política for localizada, marque a caixa para selecionar a política.

1. Escolha **Avançar** (na parte inferior da janela). Uma janela **Revisar** é aberta.

1. Na janela **Revisar**, selecione **Adicionar permissões** (na parte inferior da janela).

1. Na página **Summary** (Resumo), confirme se a política do IVS foi adicionada.

# Etapa 2: criar um palco com gravação opcional de participantes
<a name="getting-started-create-stage"></a>

Um palco corresponde a um espaço virtual no qual os participantes podem trocar vídeos em tempo real. Ele é o recurso fundamental da API de streaming em tempo real. É possível criar um estágio usando o console ou a operação CreateStage.

Recomendamos que, sempre que possível, você crie um novo palco para cada sessão lógica e exclua-o quando terminar, em vez de manter os palcos antigos para possível reutilização. Se os recursos obsoletos (palcos antigos, que não devem ser reutilizados) não forem limpos, é provável que você atinja o limite do número máximo de palcos mais rapidamente.

Você pode criar um palco (com ou sem gravação de participante individual) no console do Amazon IVS ou na AWS CLI. A criação e a gravação de palco são discutidas abaixo.

## Gravação individual de participante
<a name="getting-started-create-stage-ipr-overview"></a>

Você tem a opção de habilitar a gravação de participantes individuais para um palco. Se o recurso de gravação de participantes individuais no S3 estiver habilitado, todas as transmissões de participantes individuais para o palco serão gravadas e salvas em um bucket de armazenamento do Amazon S3 de sua propriedade. Posteriormente, a gravação fica disponível para reprodução sob demanda.

*Configurar isso é uma opção avançada.* Por padrão, a gravação é desabilitada quando um palco é criado.

Antes que possa configurar um palco para gravação, é necessário criar uma *configuração de armazenamento*. Trata-se de um recurso que especifica um local do Amazon S3 no qual as transmissões gravadas para o canal são armazenadas. É possível criar e gerenciar configurações de armazenamento usando o console ou a CLI; os dois procedimentos são apresentados abaixo. Após criar a configuração de armazenamento, basta associá-la a um palco ao criar o palco (conforme descrito abaixo) ou posteriormente, atualizando um palco existente. (Na API, consulte [CreateStage](https://docs.aws.amazon.com//ivs/latest/RealTimeAPIReference/API_CreateStage.html) e [UpdateStage](https://docs.aws.amazon.com//ivs/latest/RealTimeAPIReference/API_UpdateStage.html).) É possível associar vários palcos à mesma configuração de armazenamento. É possível excluir uma configuração de armazenamento que não esteja mais associada a nenhum palco.

Lembre-se das seguintes restrições:
+ É necessário ser proprietário do bucket do S3. Ou seja, a conta que configura um palco para gravação precisa ser proprietária do bucket do S3 no qual as gravações serão armazenadas.
+ O palco, a configuração de armazenamento e o local do S3 precisam estar na mesma região da AWS. Se você criar palcos em outras regiões e quiser gravá-los, também será necessário definir as configurações de armazenamento e os buckets do S3 nessas regiões.

Para gravar em seu bucket do S3, é necessária autorização com suas credenciais da AWS. Para conceder o acesso necessário ao IVS, um [perfil vinculado a serviço](https://docs.aws.amazon.com/IAM/latest/UserGuide/using-service-linked-roles.html) (SLR) do AWS IAM é criado automaticamente quando a configuração de gravação é criada: o SLR é limitado a conceder permissão de gravação ao IVS somente no bucket específico.

Observe que problemas de rede entre o local de streaming e a AWS ou na AWS podem resultar em alguma perda de dados durante a gravação do seu fluxo. Em casos como este, o Amazon IVS prioriza o stream ao vivo em relação à gravação. Para obter redundância, grave de forma local por meio da sua ferramenta de streaming.

Para obter mais informações (inclusive como configurar o pós-processamento ou a reprodução de VOD em seus arquivos gravados), consulte [Gravação de participante individual](rt-individual-participant-recording.md).

### Como desabilitar a gravação
<a name="getting-started-disable-recording"></a>

Para desabilitar a gravação do Amazon S3 em um palco existente:
+ Console: na página de detalhes do palco relevante, na seção **Gravar fluxos de participantes individuais**, desative a opção **Habilitar gravação automática** em **Gravação automática no S3** e escolha **Salvar alterações**. Isso removerá a associação da configuração de armazenamento do palco; os fluxos nesse palco não serão mais gravados.
+ Na CLI: execute o comando `update-stage` e o submeta no ARN de configuração de gravação como uma string vazia:

  ```
  aws ivs-realtime update-stage --arn arn:aws:ivs:us-west-2:123456789012:stage/abcdABCDefgh --auto-participant-recording-configuration storageConfigurationArn=""
  ```

  Isso retorna um objeto de palco com uma string vazia para `storageConfigurationArn`, indicando que a gravação está desabilitada.

## Instruções do console para a criação de um palco do IVS
<a name="getting-started-create-stage-console"></a>

1. Abra o [console do Amazon IVS](https://console.aws.amazon.com/ivs).

   (Você também pode acessar o console do Amazon IVS via [Console de Gerenciamento da AWS ](https://console.aws.amazon.com/).)

1. No painel de navegação esquerdo, selecione **Palcos** e, em seguida, selecione **Criar palco**. A janela **Criar palco** é exibida.  
![\[Use a janela Criar palco para criar um novo palco e um token de participante para ele.\]](http://docs.aws.amazon.com/pt_br/ivs/latest/RealTimeUserGuide/images/Create_Stage_Console_IPR.png)

1. Opcionalmente, insira um **Nome de palco**.

1. Se você quiser habilitar a gravação individual de participantes, conclua as etapas em [Configurar a gravação automática de participantes individuais no Amazon S3 (opcional)](#getting-started-create-stage-ipr) abaixo.

1. Selecione **Criar palco** para criar o palco. A página de detalhes do palco é exibida para o novo palco.

### Configurar a gravação automática de participantes individuais no Amazon S3 (opcional)
<a name="getting-started-create-stage-ipr"></a>

Siga estas etapas para habilitar a gravação de participantes individuais durante a criação de um palco:

1. Na página **Criar palco**, em **Gravar participantes individuais**, ative **Habilitar gravação automática**. O sistema exibirá campos adicionais para a escolha dos **Tipos de mídia gravada**, de uma **Configuração de armazenamento** existente ou para criar uma nova e escolher se deseja gravar miniaturas em um intervalo.  
![\[Use a caixa de diálogo Gravar participantes individuais para configurar a gravação de participantes individuais para um palco.\]](http://docs.aws.amazon.com/pt_br/ivs/latest/RealTimeUserGuide/images/Create_Stage_Console_enable_IPR.png)

1. Escolha quais tipos de mídia gravar.

1. Escolha **Criar configuração de armazenamento**. Uma nova janela se abrirá com opções que permitem criar um bucket do Amazon S3 e anexá-lo à nova configuração de gravação.  
![\[Use a janela Criar configuração de armazenamento para criar uma nova configuração de armazenamento para um palco.\]](http://docs.aws.amazon.com/pt_br/ivs/latest/RealTimeUserGuide/images/Create_Storage_Configuration_IPR.png)

1. Preencha os campos:

   1. Opcionalmente, insira um **Nome de configuração de gravação**.

   1. Insira um **Bucket name (Nome do bucket)**.

1. Escolha **Criar configuração de armazenamento** para criar um novo recurso de configuração de armazenamento com um ARN exclusivo. Normalmente, a criação da configuração de gravação leva alguns segundos, mas pode demorar até 20 segundos. Quando a configuração de armazenamento for criada, você retornará para a janela **Criar palco**. Nela, a área **Gravar participantes individuais** mostrará sua nova **Configuração de armazenamento** e o bucket do S3 (**Armazenamento**) que você criou.  
![\[Crie um palco usando o console do IVS: nova configuração de armazenamento criada.\]](http://docs.aws.amazon.com/pt_br/ivs/latest/RealTimeUserGuide/images/Create_Stage_Console_Storage_Configuration.png)

1. Opcionalmente, você pode habilitar outras opções não padrão, como gravar réplicas de participantes, mesclar gravações de participantes individuais e gravação de miniaturas.  
![\[Crie um palco usando o console do IVS: habilite opções avançadas, como gravação de miniaturas e combinação de IPR.\]](http://docs.aws.amazon.com/pt_br/ivs/latest/RealTimeUserGuide/images/Create_Stage_Console_IPR_Stitching.png)

## Instruções da CLI para a criação de um palco do IVS
<a name="getting-started-create-stage-cli"></a>

Para instalar a AWS CLI, consulte [Install or update to the latest version of the AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html).

Agora, você pode usar a CLI para criar e gerenciar recursos seguindo um dos dois procedimentos abaixo, dependendo se deseja criar um palco com ou sem a gravação de participantes individuais habilitada.

### Criar um palco sem gravação de participantes individuais
<a name="getting-started-create-stage-cli-without-ipr"></a>

A API do palco está sob o namespace ivs-realtime. Por exemplo, para criar um palco:

```
aws ivs-realtime create-stage --name "test-stage"
```

A resposta é:

```
{
   "stage": {
      "arn": "arn:aws:ivs:us-west-2:376666121854:stage/VSWjvX5XOkU3",
      "name": "test-stage"
   }
}
```

### Criar um palco com gravação de participantes individuais
<a name="getting-started-create-stage-cli-with-ipr"></a>

Para criar um palco com a gravação de participantes individuais habilitada:

```
aws ivs-realtime create-stage --name "test-stage-participant-recording" --auto-participant-recording-configuration storageConfigurationArn=arn:aws:ivs:us-west-2:123456789012:storage-configuration/LKZ6QR7r55c2,mediaTypes=AUDIO_VIDEO
```

Como opção, você pode transmitir o parâmetro `thumbnailConfiguration` para definir manualmente o modo de armazenamento e gravação em miniatura e o intervalo de miniaturas em segundos:

```
aws ivs-realtime create-stage --name "test-stage-participant-recording" --auto-participant-recording-configuration storageConfigurationArn=arn:aws:ivs:us-west-2:123456789012:storage-configuration/LKZ6QR7r55c2,mediaTypes=AUDIO_VIDEO,thumbnailConfiguration="{targetIntervalSeconds=10,storage=[SEQUENTIAL,LATEST],recordingMode=INTERVAL}"
```

Como opção, transmita o parâmetro `recordingReconnectWindowSeconds` para habilitar a mesclagem de gravações fragmentadas de participantes individuais:

```
aws ivs-realtime create-stage --name "test-stage-participant-recording" --auto-participant-recording-configuration "storageConfigurationArn=arn:aws:ivs:us-west-2:123456789012:storage-configuration/LKZ6QR7r55c2,mediaTypes=AUDIO_VIDEO,thumbnailConfiguration="{targetIntervalSeconds=10,storage=[SEQUENTIAL,LATEST],recordingMode=INTERVAL}",recordingReconnectWindowSeconds=60"
```

A resposta é:

```
{
   "stage": {
      "arn": "arn:aws:ivs:us-west-2:123456789012:stage/VSWjvX5XOkU3",
      "autoParticipantRecordingConfiguration": {
         "hlsConfiguration": {
             "targetSegmentDurationSeconds": 6
         },
         "mediaTypes": [
            "AUDIO_VIDEO"
         ],
         "recordingReconnectWindowSeconds": 60,
         "recordParticipantReplicas": true,
         "storageConfigurationArn": "arn:aws:ivs:us-west-2:123456789012:storage-configuration/LKZ6QR7r55c2",
         "thumbnailConfiguration": {
            "recordingMode": "INTERVAL",
            "storage": [
               "SEQUENTIAL",
               "LATEST"
            ],
            "targetIntervalSeconds": 10
         }
      },
      "endpoints": {
         "events": "<events-endpoint>",
         "rtmp": "<rtmp-endpoint>",
         "rtmps": "<rtmps-endpoint>",
         "whip": "<whip-endpoint>"
      },
      "name": "test-stage-participant-recording"
   }
}
```

# Etapa 3: Distribuir tokens de participante
<a name="getting-started-distribute-tokens"></a>

Agora que você tem um palco, é necessário criar e distribuir tokens aos participantes para possibilitar que eles ingressem no palco e comecem a enviar e receber vídeos. Existem duas abordagens para gerar tokens:
+ [Crie](#getting-started-distribute-tokens-self-signed) tokens com um par de chaves.
+ [Crie tokens com a API de streaming em tempo real do IVS](#getting-started-distribute-tokens-api).

Ambas as abordagens são descritas a seguir.

## Criar tokens com um par de chaves
<a name="getting-started-distribute-tokens-self-signed"></a>

Você pode criar tokens na aplicação do servidor e distribuí-los aos participantes para participarem de um palco. Você precisa gerar um par de chaves públicas e privadas do ECDSA para assinar os JWTs e importar a chave pública para IVS. Então, o IVS pode verificar os tokens ao ingressar no palco. 

O IVS não oferece expiração de chave. Se a chave privada estiver comprometida, você deverá excluir a chave pública antiga.

### Criar um novo par de chaves
<a name="getting-started-distribute-tokens-self-signed-create-key-pair"></a>

Existem vários métodos para criar um par de chaves. Abaixo, damos dois exemplos.

Para criar um par de chaves no console, siga estas etapas:

1. [Abra o console do Amazon IVS](https://console.aws.amazon.com/ivs). Escolha a região do palco se já não estiver nela.

1. No menu de navegação à esquerda, escolha **Streaming em tempo real > Chaves públicas**.

1. Selecione **Create public key** (Criar chave pública). A caixa de diálogo **Criar chave pública** é exibida.

1. Escolha **Create** (Criar) e siga as solicitações.

1. O Amazon IVS gera um novo par de chaves. A chave pública é importada como um recurso de chave pública, e a chave privada é imediatamente disponibilizada para download. A chave pública também pode ser baixada posteriormente, se necessário.

   O Amazon IVS gera a chave no lado do cliente e não armazena a chave privada. ***Certifique-se de salvar a chave, pois você não poderá recuperá-la mais tarde.***

Para criar um novo par de chaves EC P384 com OpenSSL (talvez seja necessário instalar o [OpenSSL](https://www.openssl.org/source/) primeiro), siga estas etapas. Este processo permite acessar as chaves privadas e públicas. Você precisará da chave pública somente se quiser testar a verificação de seus tokens.

```
openssl ecparam -name secp384r1 -genkey -noout -out priv.pem
openssl ec -in priv.pem -pubout -out public.pem
```

Agora importe sua nova chave pública usando as instruções abaixo.

### Importar a chave pública
<a name="getting-started-distribute-tokens-import-public-key"></a>

Caso já tenha um par de chaves, você poderá importar a chave pública para o IVS. A chave privada não é necessária para nosso sistema, mas é usada por você para assinar tokens.

Para importar uma chave pública existente com o console:

1. [Abra o console do Amazon IVS](https://console.aws.amazon.com/ivs). Escolha a região do palco se já não estiver nela.

1. No menu de navegação à esquerda, escolha **Streaming em tempo real > Chaves públicas**.

1. Escolha **Importar**. Uma caixa de diálogo **Importar chave pública** é exibida.

1. Selecione **Import** (Importar) e siga as solicitações.

1. O Amazon IVS importa a chave pública e gera um recurso de chave pública.

Para importar uma chave pública existente com a CLI:

```
aws ivs-realtime import-public-key --public-key-material "`cat public.pem`" --region <aws-region>
```

É possível omitir `--region <aws-region>` se a região estiver em seu arquivo de configuração local da AWS.

Este é um exemplo de resposta:

```
{
    "publicKey": {
        "arn": "arn:aws:ivs:us-west-2:123456789012:public-key/f99cde61-c2b0-4df3-8941-ca7d38acca1a",
        "fingerprint": "98:0d:1a:a0:19:96:1e:ea:0a:0a:2c:9a:42:19:2b:e7",
        "publicKeyMaterial": "-----BEGIN PUBLIC KEY-----\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEVjYMV+P4ML6xemanCrtse/FDwsNnpYmS\nS6vRV9Wx37mjwi02hObKuCJqpj7x0lpz0bHm5v1JBvdZYAd/r2LR5aChK+/GM2Wj\nl8MG9NJIVFaw1u3bvjEjzTASSfS1BDX1\n-----END PUBLIC KEY-----\n",
        "tags": {}
    }
}
```

### Solicitações de API
<a name="getting-started-distribute-tokens-create-api"></a>

```
POST /ImportPublicKey HTTP/1.1
{
  "publicKeyMaterial": "<pem file contents>"
}
```

### Gerar e assinar o token
<a name="getting-started-distribute-tokens-self-signed-generate-sign"></a>

Para obter detalhes sobre como trabalhar com JWTs e as bibliotecas suportadas para assinar tokens, visite [jwt.io](https://jwt.io/). Na interface do jwt.io, é necessário inserir sua chave privada para assinar os tokens. A chave pública será necessária somente se você quiser verificar os tokens.

Todos os JWTs têm três campos: cabeçalho, carga útil e assinatura.

Os esquemas JSON para o cabeçalho e a carga útil do JWT estão descritos abaixo. Como alternativa, você pode copiar um exemplo de JSON do console do IVS. Para obter a carga útil e o cabeçalho JSON do console do IVS:

1. [Abra o console do Amazon IVS](https://console.aws.amazon.com/ivs). Escolha a região do palco se já não estiver nela.

1. No painel de navegação à esquerda, escolha **Streaming em tempo real > Palcos**.

1. Selecione o palco que você deseja usar. Selecione **Exibir detalhes**.

1. Na seção **Tokens de participantes**, selecione o menu suspenso ao lado de **Criar token**.

1. Selecione **Criar cabeçalho e carga útil de token**.

1. Preencha o formulário e copie a carga útil e o cabeçalho JWT mostrados na parte inferior do pop-up.

#### Esquema de token: cabeçalho
<a name="getting-started-distribute-tokens-self-signed-generate-sign-header"></a>

O cabeçalho especifica:
+ `alg` é o algoritmo de assinatura. Este é o ES384, um algoritmo de assinatura ECDSA que usa o algoritmo de hash SHA-384.
+ `typ` é o tipo de token, JWT.
+ `kid` é o ARN da chave pública usada para assinar o token. Ele deve ser o mesmo ARN retornado da solicitação da API [GetPublicKey](https://docs.aws.amazon.com//ivs/latest/RealTimeAPIReference/API_GetPublicKey.html).

```
{
  "alg": "ES384",
  "typ": "JWT"
  “kid”: “arn:aws:ivs:123456789012:us-east-1:public-key/abcdefg12345”
}
```

#### Esquema de token: carga útil
<a name="getting-started-distribute-tokens-self-signed-generate-sign-payload"></a>

A carga útil contém dados específicos do IVS. Todos os campos, exceto `user_id`, são obrigatórios.
+ `RegisteredClaims` na especificação de JWT são declarações reservadas que precisam ser fornecidas para que o token do palco seja válido: 
  + `exp` (tempo de expiração) é um carimbo de data e hora UTC do Unix para quando o token expirar. (Observe que um carimbo de data e hora do Unix é um valor numérico que representa o número de segundos de 1970-01-01T00:00:00Z UTC até a data e hora UTC especificada, ignorando os segundos de salto.) O token é validado quando o participante entra em um palco. O IVS fornece tokens com um TTL padrão de 12 horas, o que recomendamos. Isso pode ser estendido até um máximo de 14 dias a partir do momento da emissão (iat). Esse valor deve ser um número inteiro.
  + `iat` (emitido no momento) é um carimbo de data e hora UTC do Unix para quando o JWT foi emitido. (Veja a observação de `exp` sobre carimbos de data e hora do Unix.) Esse valor deve ser um número inteiro.
  + `jti` (ID do JWT) é o ID do participante usado para rastrear e fazer referência ao participante a quem o token foi concedido. Cada token deve ter um ID de participante exclusivo. Deve ser uma string com distinção de maiúsculas e minúsculas, com até 64 caracteres, contendo somente caracteres alfanuméricos, hífen (-) e sublinhado (\$1). Nenhum outro caractere especial é permitido. 
+ `user_id` é um nome opcional atribuído pelo cliente para ajudar a identificar o token. Ele pode ser usado para vincular um participante a um usuário nos sistemas do próprio cliente. Isso deve corresponder ao campo `userId` na solicitação da API [CreateParticipantToken](https://docs.aws.amazon.com/ivs/latest/RealTimeAPIReference/API_CreateParticipantToken.html). Ele pode ser qualquer texto codificado em UTF-8 e é uma string de até 128 caracteres. *Este campo fica exposto para todos os participantes do palco e não deve ser usado ​​para identificação pessoal, informações confidenciais ou sigilosas.*
+ `resource` é o ARN do palco; por exemplo, `arn:aws:ivs:us-east-1:123456789012:stage/oRmLNwuCeMlQ`.
+ `topic` é o ID do palco, que pode ser extraído do ARN do palco. Por exemplo, se o ARN do palco for `arn:aws:ivs:us-east-1:123456789012:stage/oRmLNwuCeMlQ`, o ID do palco será `oRmLNwuCeMlQ`.
+ `events_url` deve ser o endpoint de eventos retornado da operação CreateStage ou GetStage. Recomendamos que você armazene esse valor em cache no momento da criação do palco. O valor pode ser armazenado em cache por até 14 dias. Um exemplo de valor é `wss://global.events.live-video.net`.
+ `whip_url` deve ser o endpoint do WHIP retornado da operação CreateStage ou GetStage. Recomendamos que você armazene esse valor em cache no momento da criação do palco. O valor pode ser armazenado em cache por até 14 dias. Um exemplo de valor é `https://453fdfd2ad24df.global-bm.whip.live-video.net`.
+ `capabilities` especifica os recursos do token. Os valores válidos são `allow_publish` e `allow_subscribe`. Para tokens somente para assinantes, defina somente `allow_subscribe` como `true`.
+ `attributes` é um campo opcional em que você pode especificar atributos fornecidos pela aplicação para codificar no token e anexar a um palco. Chaves e valores de mapa podem conter texto codificado em UTF-8. Este campo não pode ultrapassar o total de 1 KB. *Este campo fica exposto para todos os participantes do palco e não deve ser usado ​​para identificação pessoal, informações confidenciais ou sigilosas.*
+ `version` deve ser `1.0`.

  ```
  {
    "exp": 1697322063,
    "iat": 1697149263,
    "jti": "Mx6clRRHODPy",
    "user_id": "<optional_customer_assigned_name>",
    "resource": "<stage_arn>",
    "topic": "<stage_id>",
    "events_url": "wss://global.events.live-video.net",
    "whip_url": "https://114ddfabadaf.global-bm.whip.live-video.net",
    "capabilities": {
      "allow_publish": true,
      "allow_subscribe": true
    },
    "attributes": {
      "optional_field_1": "abcd1234",
      "optional_field_2": "false"
    },
    "version": "1.0"
  }
  ```

#### Esquema de token: assinatura
<a name="getting-started-distribute-tokens-self-signed-generate-sign-signature"></a>

Para criar a assinatura, use a chave privada com o algoritmo especificado no cabeçalho (ES384) para assinar o cabeçalho codificado e a carga útil codificada.

```
ECDSASHA384(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  <private-key>
)
```

#### Instruções
<a name="getting-started-distribute-tokens-self-signed-generate-sign-instructions"></a>

1. Gere a assinatura do token com um algoritmo de assinatura ES384 e uma chave privada associada à chave pública fornecida ao IVS.

1. Monte o token.

   ```
   base64UrlEncode(header) + "." +
   base64UrlEncode(payload) + "." +
   base64UrlEncode(signature)
   ```

## Criar tokens com a API de streaming em tempo real do IVS
<a name="getting-started-distribute-tokens-api"></a>

![\[Distribuir tokens de participantes: fluxo de trabalho de token do palco\]](http://docs.aws.amazon.com/pt_br/ivs/latest/RealTimeUserGuide/images/Distribute_Participant_Tokens.png)


Conforme mostrado acima, uma aplicação do cliente solicita um token à sua aplicação de servidor, e a aplicação de servidor chama CreateParticipantToken usando um AWS SDK ou uma solicitação assinada SigV4. Como as credenciais da AWS são usadas para chamar a API, o token deve ser gerado em uma aplicação segura do lado do servidor, não na aplicação do lado do cliente.

Ao criar um token de participante, você pode opcionalmente especificar atributos e recursos:
+ Você pode especificar atributos fornecidos pela aplicação para codificar no token e anexar a um palco. Chaves e valores de mapa podem conter texto codificado em UTF-8. Este campo não pode ultrapassar o total de 1 KB. *Este campo fica exposto para todos os participantes do palco e não deve ser usado ​​para identificação pessoal, informações confidenciais ou sigilosas.*
+ Você pode especificar os recursos habilitados pelo token. O padrão é `PUBLISH` e `SUBSCRIBE`, que permite ao participante enviar e receber áudio e vídeo, mas você pode emitir tokens com um subconjunto de funcionalidades. Por exemplo, é possível emitir um token somente com a funcionalidade `SUBSCRIBE` para moderadores. Nesse caso, os moderadores podem visualizar os participantes que estão enviando vídeos, mas não podem enviar seus próprios vídeos.

Para obter detalhes, consulte [CreateParticipantToken](https://docs.aws.amazon.com//ivs/latest/RealTimeAPIReference/API_CreateParticipantToken.html).

É possível criar tokens de participantes usando o console ou a CLI para testes e desenvolvimento, mas provavelmente você desejará criá-los com o AWS SDK em seu ambiente de produção.

Você precisará de uma maneira de distribuir os tokens do seu servidor para cada cliente (por exemplo, por meio de uma solicitação de API). Não fornecemos essa funcionalidade. Para este guia, você pode simplesmente copiar e colar os tokens no código do cliente nas etapas a seguir.

**Importante**: trate os tokens como opacos; ou seja, não desenvolva funcionalidades com base no conteúdo do token. O formato dos tokens poderá mudar no futuro.

### Instruções do console
<a name="getting-started-distribute-tokens-console"></a>

1. Navegue até o palco criado na etapa anterior.

1. Selecione **Criar token**. A janela **Criar token** é exibida.

1. Insira um ID de usuário a ser associado ao token. Isso pode ser qualquer texto codificado em UTF-8. 

1. Escolha **Criar**.

1. Copie o token. *Importante: certifique-se de salvar o token. O IVS não o armazena, e você não poderá recuperá-lo posteriormente*.

### Instruções da CLI
<a name="getting-started-distribute-tokens-cli"></a>

A criação de um token com a AWS CLI requer que você faça o download e configure a CLI em sua máquina primeiro. Para obter mais detalhes, consulte o [Guia do usuário da Interface de Linhas de Comando da AWS](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-welcome.html). Observe que gerar tokens com a AWS CLI é bom para fins de testes, mas para uso em produção, recomendamos que você gere tokens do lado do servidor com o AWS SDK (consulte as instruções abaixo).

1. Execute o comando `create-participant-token` com o ARN do palco. Inclua qualquer uma ou todas as seguintes funcionalidades: `"PUBLISH"` e `"SUBSCRIBE"`.

   ```
   aws ivs-realtime create-participant-token --stage-arn arn:aws:ivs:us-west-2:376666121854:stage/VSWjvX5XOkU3 --capabilities '["PUBLISH", "SUBSCRIBE"]'
   ```

1. Isso retorna um token de participante:

   ```
   {
       "participantToken": {
           "capabilities": [
               "PUBLISH",
               "SUBSCRIBE"
           ],
           "expirationTime": "2023-06-03T07:04:31+00:00",
           "participantId": "tU06DT5jCJeb",
           "token": "eyJhbGciOiJLTVMiLCJ0eXAiOiJKV1QifQ.eyJleHAiOjE2NjE1NDE0MjAsImp0aSI6ImpGcFdtdmVFTm9sUyIsInJlc291cmNlIjoiYXJuOmF3czppdnM6dXMtd2VzdC0yOjM3NjY2NjEyMTg1NDpzdGFnZS9NbzhPUWJ0RGpSIiwiZXZlbnRzX3VybCI6IndzczovL3VzLXdlc3QtMi5ldmVudHMubGl2ZS12aWRlby5uZXQiLCJ3aGlwX3VybCI6Imh0dHBzOi8vNjZmNzY1YWM4Mzc3Lmdsb2JhbC53aGlwLmxpdmUtdmlkZW8ubmV0IiwiY2FwYWJpbGl0aWVzIjp7ImFsbG93X3B1Ymxpc2giOnRydWUsImFsbG93X3N1YnNjcmliZSI6dHJ1ZX19.MGQCMGm9affqE3B2MAb_DSpEm0XEv25hfNNhYn5Um4U37FTpmdc3QzQKTKGF90swHqVrDgIwcHHHIDY3c9eanHyQmcKskR1hobD0Q9QK_GQETMQS54S-TaKjllW9Qac6c5xBrdAk"
       }
   }
   ```

1. Salve esse token. Você precisará dele para ingressar no palco e enviar e receber vídeos.

### Instruções do AWS SDK
<a name="getting-started-distribute-tokens-sdk"></a>

É possível usar o AWS SDK para criar tokens. Abaixo estão as instruções para o AWS SDK usando JavaScript. 

**Importante:** esse código deve ser executado no lado do servidor e sua saída transferida para o cliente.

**Pré-requisito:** para usar o exemplo de código abaixo, é necessário instalar o pacote aws-sdk/client-ivs-realtime. Para obter mais detalhes, consulte [Getting started with the AWS SDK for JavaScript](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/getting-started.html).

```
import { IVSRealTimeClient, CreateParticipantTokenCommand } from "@aws-sdk/client-ivs-realtime";

const ivsRealtimeClient = new IVSRealTimeClient({ region: 'us-west-2' });
const stageArn = 'arn:aws:ivs:us-west-2:123456789012:stage/L210UYabcdef';
const createStageTokenRequest = new CreateParticipantTokenCommand({
  stageArn,
});
const response = await ivsRealtimeClient.send(createStageTokenRequest);
console.log('token', response.participantToken.token);
```

# Etapa 4: Integrar o SDK de Transmissão do IVS
<a name="getting-started-broadcast-sdk"></a>

O IVS fornece um SDK de transmissão para Web, Android e iOS que você pode integrar à sua aplicação. O SDK de transmissão é usado para enviar e receber vídeos. Caso tenha [configurado a ingestão do RTMP para seu palco](https://docs.aws.amazon.com/ivs/latest/RealTimeUserGuide/rt-stream-ingest.html), você pode usar qualquer codificador que possa transmitir para um endpoint do RTMP (por exemplo, OBS ou ffmpeg).

Nesta seção, escrevemos uma aplicação simples que possibilita que dois ou mais participantes interajam em tempo real. As etapas abaixo orientam você na criação de uma aplicação chamada BasicRealTime. O código completo da aplicação está no CodePen e no GitHub:
+  Web: [https://codepen.io/amazon-ivs/pen/ZEqgrpo](https://codepen.io/amazon-ivs/pen/ZEqgrpo) 
+  Android: [https://github.com/aws-samples/amazon-ivs-real-time-streaming-android-samples](https://github.com/aws-samples/amazon-ivs-real-time-streaming-android-samples) 
+  iOS: [https://github.com/aws-samples/amazon-ivs-real-time-streaming-ios-samples](https://github.com/aws-samples/amazon-ivs-real-time-streaming-ios-samples) 

## Web
<a name="getting-started-broadcast-sdk-web"></a>

### Configurar arquivos
<a name="getting-started-broadcast-sdk-web-setup"></a>

Para começar a configurar seus arquivos, crie uma pasta e um arquivo HTML e JS inicial:

```
mkdir realtime-web-example
cd realtime-web-example
touch index.html
touch app.js
```

É possível instalar o SDK de transmissão usando uma etiqueta de script ou um npm. Nosso exemplo usa a etiqueta de script para simplificar, mas é fácil de modificar se você optar por usar o npm posteriormente.

### Uso de uma etiqueta de script
<a name="getting-started-broadcast-sdk-web-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).

Quando carregada via etiqueta `<script>`, a biblioteca expõe uma variável global no escopo da janela chamada `IVSBroadcastClient`.

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

Para instalar o pacote npm:

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

Agora é possível acessar o objeto IVSBroadcastClient:

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

## Android
<a name="getting-started-broadcast-sdk-android"></a>

### Criar o projeto Android
<a name="getting-started-broadcast-sdk-android-project"></a>

1. No Android Studio, crie um **New Project**.

1. Escolha **Empty Views Activity**.

   Observação: em algumas versões mais antigas do Android Studio, a atividade baseada em visualizações é chamada de **Empty Activity**. Se a janela do Android Studio mostrar **Empty Activity** e *não* mostrar **Empty Views** Activity, selecione **Empty Activity**. Caso contrário, não selecione **Empty Activity**, pois usaremos APIs de visualização (não o Jetpack Compose).

1. Em **Name**, coloque o nome do seu projeto e, em seguida, selecione **Finish**.

### Instalar o SDK de transmissão
<a name="getting-started-broadcast-sdk-android-install"></a>

Para adicionar a biblioteca de transmissão do Amazon IVS para Android ao seu ambiente de desenvolvimento do Android, 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 Amazon IVS). Em projetos mais recentes, o repositório `mavenCentral` pode já estar incluso em seu arquivo `settings.gradle`. Se for esse o caso, você pode omitir o bloco `repositories`. Para nossa amostra, também precisaremos habilitar a associação de dados no bloco `android`.

```
android {
    dataBinding.enabled true
}

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

Como alternativa, para instalar o SDK manualmente, baixe a versão mais recente neste local:

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

## iOS
<a name="getting-started-broadcast-sdk-ios"></a>

### Criar o projeto iOS
<a name="getting-started-broadcast-sdk-ios-project"></a>

1. Crie um novo projeto no Xcode.

1. Em **Platform**, selecione **iOS**.

1. Em **Application**, selecione **App**.

1. Em **Product Name**, insira o nome de produto da sua aplicação e, em seguida, selecione **Next**.

1. Escolha (navegue até) um diretório no qual deseja salvar o projeto e, em seguida, selecione **Create**.

Em seguida, é necessário trazer o SDK. Para ver instruções, consulte [Instalar a biblioteca](broadcast-ios-getting-started.md#broadcast-ios-install) no *Guia do SDK de Transmissão do iOS*.

### Configurar permissões
<a name="getting-started-broadcast-sdk-ios-config"></a>

Você precisa atualizar o `Info.plist` do seu projeto para adicionar duas novas entradas para `NSCameraUsageDescription` e `NSMicrophoneUsageDescription`. Para os valores, inclua explicações para o usuário sobre por que sua aplicação está solicitando acesso à câmera e ao microfone.

![\[Configuração de permissões do iOS.\]](http://docs.aws.amazon.com/pt_br/ivs/latest/RealTimeUserGuide/images/iOS_Configure.png)


# Etapa 5: Publicar e assinar o vídeo
<a name="getting-started-pub-sub"></a>

Você pode publicar e assinar (em tempo real) no IVS com:
+ Os [SDKs de transmissão nativos do IVS](https://docs.aws.amazon.com//ivs/latest/LowLatencyUserGuide/getting-started-set-up-streaming.html#broadcast-sdk), que são compatíveis com o WebRTC e RTMPS. Recomendamos isso, especialmente para cenários de produção. Veja os detalhes abaixo para [Web](getting-started-pub-sub-web.md), [Android](getting-started-pub-sub-android.md) e [iOS](getting-started-pub-sub-ios.md).
+ O console do Amazon IVS: adequado para testar fluxos. Consulte abaixo.
+ Outros codificadores de software e hardware de streaming: você pode usar qualquer codificador de streaming que seja compatível com os protocolos RTMP, RTMPS ou WHIP. Para obter mais informações, consulte [Ingestão de streaming](rt-stream-ingest.md).

## Console do IVS
<a name="getting-started-pub-sub-console"></a>

1. Abra o [console do Amazon IVS](https://console.aws.amazon.com/ivs).

   (Você também pode acessar o console do Amazon IVS por meio do [Console de Gerenciamento da AWS](https://console.aws.amazon.com/).)

1. No painel de navegação, selecione **Palcos**. (Se o painel de navegação estiver fechado, expanda-o selecionando o ícone de hambúrguer.)

1. Selecione o palco em que você deseja assinar ou publicar para acessar a respectiva página de detalhes.

1. Para assinar: se o palco tiver um ou mais publicadores, você poderá assinar pressionando o botão **Assinar**, na guia **Assinar**. (As guias estão abaixo da seção **Configuração geral**.)

1. Para publicar:

   1. Selecione a guia **Publicar**.

   1. Você será solicitado a conceder para o console do IVS acesso à sua câmera e microfone; **Aceite** essas permissões.

   1. Na parte inferior da guia **Publicar**, use as caixas suspensas para selecionar dispositivos de entrada para o microfone e a câmera.

   1. Para começar a publicar, selecione **Iniciar publicação**.

   1. Para ver o conteúdo publicado, volte para a guia **Assinar**.

   1. Para parar de publicar, vá até a guia **Publicar** e pressione o botão **Parar de publicar** na parte inferior.

**Observação**: a assinatura e a publicação consomem recursos, e você incorrerá em uma taxa por hora pelo tempo em que estiver conectado ao palco. Para saber mais, consulte [Streaming em tempo real](https://aws.amazon.com/ivs/pricing/#Real-Time_Streaming) na página de preços do IVS.

# Publicar e assinar com o SDK de Transmissão na Web do IVS
<a name="getting-started-pub-sub-web"></a>

Esta seção descreve as etapas envolvidas na publicação e assinatura de um estágio usando a sua aplicação Web.

## Criar um padrão em HTML
<a name="getting-started-pub-sub-web-html"></a>

Primeiro, criaremos o padrão em HTML e importaremos a biblioteca como uma etiqueta de script:

```
<!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/1.33.0/amazon-ivs-web-broadcast.js"></script>
</head>

<body>

<!-- TODO - fill in with next sections -->
<script src="./app.js"></script>

</body>
</html>
```

## Aceitar entrada de tokens e adicionar botões Ingressar/Sair
<a name="getting-started-pub-sub-web-join"></a>

Aqui, preencheremos o corpo com nossos controles de entrada. Eles recebem o token como entrada e configuram os botões **Ingressar** e **Sair**. Normalmente, as aplicações solicitarão o token da API da sua aplicação, mas, para este exemplo, você copiará e colará o token na entrada do token.

```
<h1>IVS Real-Time Streaming</h1>
<hr />

<label for="token">Token</label>
<input type="text" id="token" name="token" />
<button class="button" id="join-button">Join</button>
<button class="button" id="leave-button" style="display: none;">Leave</button>
<hr />
```

## Adicionar elementos de contêiner de mídia
<a name="getting-started-pub-sub-web-media"></a>

Esses elementos manterão a mídia para nossos participantes locais e remotos. Adicionamos uma etiqueta de script para carregar a lógica da nossa aplicação definida em `app.js`.

```
<!-- Local Participant -->
<div id="local-media"></div>

<!-- Remote Participants -->
<div id="remote-media"></div>

<!-- Load Script -->
<script src="./app.js"></script>
```

Isso completa a página HTML, e você verá o seguinte ao carregar `index.html` em um navegador:

![\[Visualização do streaming em tempo real em um navegador: configuração de HTML concluída.\]](http://docs.aws.amazon.com/pt_br/ivs/latest/RealTimeUserGuide/images/RT_Browser_View.png)


## Criar app.js
<a name="getting-started-pub-sub-web-appjs"></a>

Agora definiremos o conteúdo do nosso arquivo `app.js`. Comece com a importação de todas as propriedades necessárias do SDK global:

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

## Criar variáveis ​​da aplicação
<a name="getting-started-pub-sub-web-vars"></a>

Estabeleça variáveis ​​para manter referências aos nossos elementos HTML dos botões **Ingressar** e **Sair** e armazenar o estado para a aplicação:

```
let joinButton = document.getElementById("join-button");
let leaveButton = document.getElementById("leave-button");

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

## Criar joinStage 1: definir a função e validar a entrada
<a name="getting-started-pub-sub-web-joinstage1"></a>

A função `joinStage` recebe o token de entrada, cria uma conexão com o palco e começa a publicar o vídeo e o áudio recuperados de `getUserMedia`.

Para começar, definimos a função e validamos o estado e a entrada do token. Desenvolveremos essa função nas próximas seções.

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

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

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

  // Fill in with the next sections
};
```

## Criar joinStage 2: obter mídia para publicação
<a name="getting-started-pub-sub-web-joinstage2"></a>

Aqui está a mídia que será publicada no palco:

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

async function getMic() {
  return navigator.mediaDevices.getUserMedia({
    video: false,
    audio: true
  });
}

// Retrieve the User Media currently set on the page
localCamera = await getCamera();
localMic = await getMic();

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

## Criar joinStage 3: definir a estratégia do palco e criar o palco
<a name="getting-started-pub-sub-web-joinstage3"></a>

Essa estratégia de palco corresponde ao ponto central da lógica de decisão que o SDK usa para definir o que publicar e em quais participantes se inscrever. Para obter mais informações sobre a finalidade da função, consulte [Strategy](web-publish-subscribe.md#web-publish-subscribe-concepts-strategy).

Essa estratégia é simples. Após ingressar no palco, publique os streams que acabamos de recuperar e inscreva-se nos áudios e vídeos de todos os participantes remotos:

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

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

## Criar joinStage 4: lidar com eventos de palco e renderizar mídia
<a name="getting-started-pub-sub-web-joinstage4"></a>

Os palcos emitem muitos eventos. Precisamos receber `STAGE_PARTICIPANT_STREAMS_ADDED` e `STAGE_PARTICIPANT_LEFT` para renderizar mídia e removê-la da página. Um conjunto mais completo de eventos está listado em [Eventos](web-publish-subscribe.md#web-publish-subscribe-concepts-events).

Observe que criamos quatro funções auxiliares aqui para nos ajudar no gerenciamento dos elementos DOM necessários: `setupParticipant`, `teardownParticipant`, `createVideoEl` e `createContainer`.

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

  if (connected) {
    joining = false;
    joinButton.style = "display: none";
    leaveButton.style = "display: inline-block";
  }
});

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


// Helper functions for managing DOM

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

## Criar joinStage 5: ingressar no palco
<a name="getting-started-pub-sub-web-joinstage5"></a>

Concluiremos nossa função `joinStage` ao finalmente ingressar no palco.

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

## Criar leaveStage
<a name="getting-started-pub-sub-web-leavestage"></a>

Defina a função `leaveStage` que o botão Sair invocará.

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

  joining = false;
  connected = false;
};
```

## Inicializar manipuladores de eventos de entrada
<a name="getting-started-pub-sub-web-handlers"></a>

Adicionaremos uma última função ao nosso arquivo `app.js`. Essa função é invocada imediatamente quando a página é carregada e estabelece manipuladores de eventos para ingressar e sair do palco.

```
const init = async () => {
  try {
    // Prevents issues on Safari/FF so devices are not blank
    await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
  } catch (e) {
    alert(
      "Problem retrieving media! Enable camera and microphone permissions."
    );
  }

  joinButton.addEventListener("click", () => {
    joinStage();
  });

  leaveButton.addEventListener("click", () => {
    leaveStage();
    joinButton.style = "display: inline-block";
    leaveButton.style = "display: none";
  });
};

init(); // call the function
```

## Executar a aplicação e fornecer um token
<a name="getting-started-pub-sub-run-app"></a>

Nesse ponto, você pode compartilhar a página da Web localmente ou com outras pessoas, [abrir a página](#getting-started-pub-sub-web-media), inserir um token de participante e ingressar no Stage.

## E depois?
<a name="getting-started-pub-sub-next"></a>

Para obter exemplos mais detalhados envolvendo npm, React etc, consulte [SDK de Transmissão do IVS: Guia do Android (streaming em tempo real)](broadcast-web.md).

# Publicar e assinar com o SDK de Transmissão para Android do IVS
<a name="getting-started-pub-sub-android"></a>

Esta seção descreve as etapas envolvidas na publicação e assinatura de um estágio usando a sua aplicação para Android.

## Criar visualizações
<a name="getting-started-pub-sub-android-views"></a>

Começamos com a criação de um layout simples para nossa aplicação usando o arquivo `activity_main.xml` criado automaticamente. O layout contém um `EditText` para adicionar um token, um `Button` Ingressar, um `TextView` para mostrar o estado do palco e um `CheckBox` para alternar a publicação.

![\[Configuração do layout de publicação para sua aplicação Android.\]](http://docs.aws.amazon.com/pt_br/ivs/latest/RealTimeUserGuide/images/Publish_Android_1.png)


Este é o XML por trás da visualização:

```
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:keepScreenOn="true"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".BasicActivity">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:id="@+id/main_controls_container"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@color/cardview_dark_background"
            android:padding="12dp"
            app:layout_constraintTop_toTopOf="parent">

            <EditText
                android:id="@+id/main_token"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:autofillHints="@null"
                android:backgroundTint="@color/white"
                android:hint="@string/token"
                android:imeOptions="actionDone"
                android:inputType="text"
                android:textColor="@color/white"
                app:layout_constraintEnd_toStartOf="@id/main_join"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

            <Button
                android:id="@+id/main_join"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:backgroundTint="@color/black"
                android:text="@string/join"
                android:textAllCaps="true"
                android:textColor="@color/white"
                android:textSize="16sp"
                app:layout_constraintBottom_toBottomOf="@+id/main_token"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toEndOf="@id/main_token" />

            <TextView
                android:id="@+id/main_state"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/state"
                android:textColor="@color/white"
                android:textSize="18sp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@id/main_token" />

            <TextView
                android:id="@+id/main_publish_text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/publish"
                android:textColor="@color/white"
                android:textSize="18sp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toStartOf="@id/main_publish_checkbox"
                app:layout_constraintTop_toBottomOf="@id/main_token" />

            <CheckBox
                android:id="@+id/main_publish_checkbox"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:buttonTint="@color/white"
                android:checked="true"
                app:layout_constraintBottom_toBottomOf="@id/main_publish_text"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintTop_toTopOf="@id/main_publish_text" />

        </androidx.constraintlayout.widget.ConstraintLayout>

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/main_recycler_view"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            app:layout_constraintTop_toBottomOf="@+id/main_controls_container"
            app:layout_constraintBottom_toBottomOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
<layout>
```

Nós referenciamos alguns IDs de string aqui, então criaremos nosso arquivo `strings.xml` completo agora:

```
<resources>
    <string name="app_name">BasicRealTime</string>
    <string name="join">Join</string>
    <string name="leave">Leave</string>
    <string name="token">Participant Token</string>
    <string name="publish">Publish</string>
    <string name="state">State: %1$s</string>
</resources>
```

Vincularemos essas visualizações no XML à nossa `MainActivity.kt`:

```
import android.widget.Button
import android.widget.CheckBox
import android.widget.EditText
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView

private lateinit var checkboxPublish: CheckBox
private lateinit var recyclerView: RecyclerView
private lateinit var buttonJoin: Button
private lateinit var textViewState: TextView
private lateinit var editTextToken: EditText

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    checkboxPublish = findViewById(R.id.main_publish_checkbox)
    recyclerView = findViewById(R.id.main_recycler_view)
    buttonJoin = findViewById(R.id.main_join)
    textViewState = findViewById(R.id.main_state)
    editTextToken = findViewById(R.id.main_token)
}
```

Agora, criamos uma visualização do item para nosso `RecyclerView`. Para fazer isso, clique com o botão direito do mouse em seu diretório `res/layout` e selecione **New > Layout Resource File**. Nomeie esse novo arquivo como `item_stage_participant.xml`.

![\[Criação de uma visualização do item para sua aplicação Android com o RecyclerView.\]](http://docs.aws.amazon.com/pt_br/ivs/latest/RealTimeUserGuide/images/Publish_Android_2.png)


O layout desse item é simples. Ele contém uma visualização para renderizar o stream de vídeo de um participante e uma lista de rótulos para exibir informações sobre o participante:

![\[Criação de uma visualização do item para sua aplicação Android com o RecyclerView: rótulos.\]](http://docs.aws.amazon.com/pt_br/ivs/latest/RealTimeUserGuide/images/Publish_Android_3.png)


Este é o XML:

```
<?xml version="1.0" encoding="utf-8"?>
<com.amazonaws.ivs.realtime.basicrealtime.ParticipantItem xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <FrameLayout
        android:id="@+id/participant_preview_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:background="@android:color/darker_gray" />

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:background="#50000000"
        android:orientation="vertical"
        android:paddingLeft="4dp"
        android:paddingTop="2dp"
        android:paddingRight="4dp"
        android:paddingBottom="2dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <TextView
            android:id="@+id/participant_participant_id"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@android:color/white"
            android:textSize="16sp"
            tools:text="You (Disconnected)" />

        <TextView
            android:id="@+id/participant_publishing"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@android:color/white"
            android:textSize="16sp"
            tools:text="NOT_PUBLISHED" />

        <TextView
            android:id="@+id/participant_subscribed"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@android:color/white"
            android:textSize="16sp"
            tools:text="NOT_SUBSCRIBED" />

        <TextView
            android:id="@+id/participant_video_muted"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@android:color/white"
            android:textSize="16sp"
            tools:text="Video Muted: false" />

        <TextView
            android:id="@+id/participant_audio_muted"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@android:color/white"
            android:textSize="16sp"
            tools:text="Audio Muted: false" />

        <TextView
            android:id="@+id/participant_audio_level"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@android:color/white"
            android:textSize="16sp"
            tools:text="Audio Level: -100 dB" />

    </LinearLayout>

</com.amazonaws.ivs.realtime.basicrealtime.ParticipantItem>
```

Este arquivo XML infla uma classe que ainda não criamos, `ParticipantItem`. Como o XML inclui o namespace completo, certifique-se de atualizar esse arquivo XML com o seu namespace. Criaremos esta classe e configuraremos as visualizações posteriormente, por isso deixe em branco por enquanto.

Crie uma nova classe Kotlin, `ParticipantItem`:

```
package com.amazonaws.ivs.realtime.basicrealtime

import android.content.Context
import android.util.AttributeSet
import android.widget.FrameLayout
import android.widget.TextView
import kotlin.math.roundToInt

class ParticipantItem @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0,
    defStyleRes: Int = 0,
) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) {

    private lateinit var previewContainer: FrameLayout
    private lateinit var textViewParticipantId: TextView
    private lateinit var textViewPublish: TextView
    private lateinit var textViewSubscribe: TextView
    private lateinit var textViewVideoMuted: TextView
    private lateinit var textViewAudioMuted: TextView
    private lateinit var textViewAudioLevel: TextView

    override fun onFinishInflate() {
        super.onFinishInflate()
        previewContainer = findViewById(R.id.participant_preview_container)
        textViewParticipantId = findViewById(R.id.participant_participant_id)
        textViewPublish = findViewById(R.id.participant_publishing)
        textViewSubscribe = findViewById(R.id.participant_subscribed)
        textViewVideoMuted = findViewById(R.id.participant_video_muted)
        textViewAudioMuted = findViewById(R.id.participant_audio_muted)
        textViewAudioLevel = findViewById(R.id.participant_audio_level)
    }
}
```

## Permissões
<a name="getting-started-pub-sub-android-perms"></a>

Para usar a câmera e o microfone, você precisa solicitar permissões por parte do usuário. Para isso, seguiremos um fluxo de permissões padrão:

```
override fun onStart() {
    super.onStart()
    requestPermission()
}

private val requestPermissionLauncher =
    registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
        if (permissions[Manifest.permission.CAMERA] == true && permissions[Manifest.permission.RECORD_AUDIO] == true) {
            viewModel.permissionGranted() // we will add this later
        }
    }

private val permissions = listOf(
    Manifest.permission.CAMERA,
    Manifest.permission.RECORD_AUDIO,
)

private fun requestPermission() {
    when {
        this.hasPermissions(permissions) -> viewModel.permissionGranted() // we will add this later
        else -> requestPermissionLauncher.launch(permissions.toTypedArray())
    }
}

private fun Context.hasPermissions(permissions: List<String>): Boolean {
    return permissions.all {
        ContextCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_GRANTED
    }
}
```

## Estado da aplicação
<a name="getting-started-pub-sub-android-app-state"></a>

Nossa aplicação acompanha os participantes localmente em um `MainViewModel.kt` e o estado será comunicado de volta à `MainActivity` usando o [StateFlow](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-state-flow/) do Kotlin.

Crie uma nova classe Kotlin, `MainViewModel`:

```
package com.amazonaws.ivs.realtime.basicrealtime

import android.app.Application
import androidx.lifecycle.AndroidViewModel

class MainViewModel(application: Application) : AndroidViewModel(application), Stage.Strategy, StageRenderer {

}
```

Em `MainActivity.kt`, gerenciamos nosso modelo de visualização:

```
import androidx.activity.viewModels

private val viewModel: MainViewModel by viewModels()
```

Para usar `AndroidViewModel` e essas extensões `ViewModel` do Kotlin, será necessário adicionar o seguinte ao arquivo `build.gradle` do seu módulo:

```
implementation 'androidx.core:core-ktx:1.10.1'
implementation "androidx.activity:activity-ktx:1.7.2"
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.10.0'
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"

def lifecycle_version = "2.6.1"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
```

### Adaptador RecyclerView
<a name="getting-started-pub-sub-android-app-state-recycler"></a>

Criaremos uma subclasse `RecyclerView.Adapter` simples para acompanhar nossos participantes e atualizar nosso `RecyclerView` em relação aos eventos do palco. Porém, antes precisamos de uma classe que represente um participante. Crie uma nova classe Kotlin, `StageParticipant`:

```
package com.amazonaws.ivs.realtime.basicrealtime

import com.amazonaws.ivs.broadcast.Stage
import com.amazonaws.ivs.broadcast.StageStream

class StageParticipant(val isLocal: Boolean, var participantId: String?) {
    var publishState = Stage.PublishState.NOT_PUBLISHED
    var subscribeState = Stage.SubscribeState.NOT_SUBSCRIBED
    var streams = mutableListOf<StageStream>()

    val stableID: String
        get() {
            return if (isLocal) {
                "LocalUser"
            } else {
                requireNotNull(participantId)
            }
        }
}
```

Usaremos essa classe na classe `ParticipantAdapter` que criaremos a seguir. Começamos com a definição da classe e com a criação de uma variável para acompanhar os participantes:

```
package com.amazonaws.ivs.realtime.basicrealtime

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView

class ParticipantAdapter : RecyclerView.Adapter<ParticipantAdapter.ViewHolder>() {

    private val participants = mutableListOf<StageParticipant>()
```

Também temos que definir nosso `RecyclerView.ViewHolder` antes de implementar o restante das substituições:

```
class ViewHolder(val participantItem: ParticipantItem) : RecyclerView.ViewHolder(participantItem)
```

Usando isso, podemos implementar as substituições `RecyclerView.Adapter` padrão:

```
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
    val item = LayoutInflater.from(parent.context)
        .inflate(R.layout.item_stage_participant, parent, false) as ParticipantItem
    return ViewHolder(item)
}

override fun getItemCount(): Int {
    return participants.size
}

override fun getItemId(position: Int): Long =
    participants[position]
        .stableID
        .hashCode()
        .toLong()

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    return holder.participantItem.bind(participants[position])
}

override fun onBindViewHolder(holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
    val updates = payloads.filterIsInstance<StageParticipant>()
    if (updates.isNotEmpty()) {
        updates.forEach { holder.participantItem.bind(it) // implemented later }
    } else {
        super.onBindViewHolder(holder, position, payloads)
    }
}
```

Por fim, adicionamos novos métodos que chamaremos de nosso `MainViewModel` quando forem feitas alterações nos participantes. Esses métodos correspondem a operações CRUD padrão no adaptador.

```
fun participantJoined(participant: StageParticipant) {
    participants.add(participant)
    notifyItemInserted(participants.size - 1)
}

fun participantLeft(participantId: String) {
    val index = participants.indexOfFirst { it.participantId == participantId }
    if (index != -1) {
        participants.removeAt(index)
        notifyItemRemoved(index)
    }
}

fun participantUpdated(participantId: String?, update: (participant: StageParticipant) -> Unit) {
    val index = participants.indexOfFirst { it.participantId == participantId }
    if (index != -1) {
        update(participants[index])
        notifyItemChanged(index, participants[index])
    }
}
```

De volta ao `MainViewModel`, é necessário criar e manter uma referência a este adaptador:

```
internal val participantAdapter = ParticipantAdapter()
```

## Estado do estágio
<a name="getting-started-pub-sub-android-views-stage-state"></a>

Também precisamos acompanhar algum estado do palco no `MainViewModel`. Definiremos essas propriedades agora:

```
private val _connectionState = MutableStateFlow(Stage.ConnectionState.DISCONNECTED)
val connectionState = _connectionState.asStateFlow()

private var publishEnabled: Boolean = false
    set(value) {
        field = value
        // Because the strategy returns the value of `checkboxPublish.isChecked`, just call `refreshStrategy`.
        stage?.refreshStrategy()
    }

private var deviceDiscovery: DeviceDiscovery? = null
private var stage: Stage? = null
private var streams = mutableListOf<LocalStageStream>()
```

Para ver sua própria visualização prévia antes de ingressar em um palco, criamos um participante local imediatamente:

```
init {
    deviceDiscovery = DeviceDiscovery(application)

    // Create a local participant immediately to render our camera preview and microphone stats
    val localParticipant = StageParticipant(true, null)
    participantAdapter.participantJoined(localParticipant)
}
```

Precisamos ter certeza de limpar esses recursos quando o nosso `ViewModel` for limpo. Substituímos `onCleared()` imediatamente, para não esquecermos de limpar esses recursos.

```
override fun onCleared() {
    stage?.release()
    deviceDiscovery?.release()
    deviceDiscovery = null
    super.onCleared()
}
```

Agora preenchemos nossa propriedade `streams` local assim que as permissões forem concedidas, implementando o método `permissionsGranted` que chamamos anteriormente:

```
internal fun permissionGranted() {
    val deviceDiscovery = deviceDiscovery ?: return
    streams.clear()
    val devices = deviceDiscovery.listLocalDevices()
    // Camera
    devices
        .filter { it.descriptor.type == Device.Descriptor.DeviceType.CAMERA }
        .maxByOrNull { it.descriptor.position == Device.Descriptor.Position.FRONT }
        ?.let { streams.add(ImageLocalStageStream(it)) }
    // Microphone
    devices
        .filter { it.descriptor.type == Device.Descriptor.DeviceType.MICROPHONE }
        .maxByOrNull { it.descriptor.isDefault }
        ?.let { streams.add(AudioLocalStageStream(it)) }

    stage?.refreshStrategy()

    // Update our local participant with these new streams
    participantAdapter.participantUpdated(null) {
        it.streams.clear()
        it.streams.addAll(streams)
    }
}
```

## Implementação do SDK do palco
<a name="getting-started-pub-sub-android-stage-sdk"></a>

Existem três [conceitos](android-publish-subscribe.md#android-publish-subscribe-concepts) principais que fundamentam a funcionalidade em tempo real: palco, estratégia e renderizador. O objetivo do projeto é minimizar a quantidade de lógica do lado do cliente que é necessária para desenvolver um produto funcional.

### Stage.Strategy
<a name="getting-started-pub-sub-android-stage-sdk-strategy"></a>

Nossa implementação de `Stage.Strategy` é simples:

```
override fun stageStreamsToPublishForParticipant(
    stage: Stage,
    participantInfo: ParticipantInfo
): MutableList<LocalStageStream> {
    // Return the camera and microphone to be published.
    // This is only called if `shouldPublishFromParticipant` returns true.
    return streams
}

override fun shouldPublishFromParticipant(stage: Stage, participantInfo: ParticipantInfo): Boolean {
    return publishEnabled
}

override fun shouldSubscribeToParticipant(stage: Stage, participantInfo: ParticipantInfo): Stage.SubscribeType {
    // Subscribe to both audio and video for all publishing participants.
    return Stage.SubscribeType.AUDIO_VIDEO
}
```

Para resumir, publicamos com base em nosso estado `publishEnabled` interno e, se publicarmos, usaremos os streams que coletamos anteriormente. Por fim, para esta amostra, sempre nos inscrevemos em outros participantes, recebendo seus áudios e vídeos.

### StageRenderer
<a name="getting-started-pub-sub-android-stage-sdk-renderer"></a>

A implementação de `StageRenderer` também é bastante simples, embora, devido ao número de funções, contenha um pouco mais de código. A abordagem geral neste renderizador é atualizar nosso `ParticipantAdapter` quando o SDK nos notifica sobre uma alteração em um participante. Existem certos cenários nos quais lidamos com os participantes locais de maneira diferente, pois decidimos gerenciá-los nós mesmos para que eles possam ver a visualização prévia da câmera antes de ingressar.

```
override fun onError(exception: BroadcastException) {
    Toast.makeText(getApplication(), "onError ${exception.localizedMessage}", Toast.LENGTH_LONG).show()
    Log.e("BasicRealTime", "onError $exception")
}

override fun onConnectionStateChanged(
    stage: Stage,
    connectionState: Stage.ConnectionState,
    exception: BroadcastException?
) {
    _connectionState.value = connectionState
}

override fun onParticipantJoined(stage: Stage, participantInfo: ParticipantInfo) {
    if (participantInfo.isLocal) {
        // If this is the local participant joining the stage, update the participant with a null ID because we
        // manually added that participant when setting up our preview
        participantAdapter.participantUpdated(null) {
            it.participantId = participantInfo.participantId
        }
    } else {
        // If they are not local, add them normally
        participantAdapter.participantJoined(
            StageParticipant(
                participantInfo.isLocal,
                participantInfo.participantId
            )
        )
    }
}

override fun onParticipantLeft(stage: Stage, participantInfo: ParticipantInfo) {
    if (participantInfo.isLocal) {
        // If this is the local participant leaving the stage, update the ID but keep it around because
        // we want to keep the camera preview active
        participantAdapter.participantUpdated(participantInfo.participantId) {
            it.participantId = null
        }
    } else {
        // If they are not local, have them leave normally
        participantAdapter.participantLeft(participantInfo.participantId)
    }
}

override fun onParticipantPublishStateChanged(
    stage: Stage,
    participantInfo: ParticipantInfo,
    publishState: Stage.PublishState
) {
    // Update the publishing state of this participant
    participantAdapter.participantUpdated(participantInfo.participantId) {
        it.publishState = publishState
    }
}

override fun onParticipantSubscribeStateChanged(
    stage: Stage,
    participantInfo: ParticipantInfo,
    subscribeState: Stage.SubscribeState
) {
    // Update the subscribe state of this participant
    participantAdapter.participantUpdated(participantInfo.participantId) {
        it.subscribeState = subscribeState
    }
}

override fun onStreamsAdded(stage: Stage, participantInfo: ParticipantInfo, streams: MutableList<StageStream>) {
    // We don't want to take any action for the local participant because we track those streams locally
    if (participantInfo.isLocal) {
        return
    }
    // For remote participants, add these new streams to that participant's streams array.
    participantAdapter.participantUpdated(participantInfo.participantId) {
        it.streams.addAll(streams)
    }
}

override fun onStreamsRemoved(stage: Stage, participantInfo: ParticipantInfo, streams: MutableList<StageStream>) {
    // We don't want to take any action for the local participant because we track those streams locally
    if (participantInfo.isLocal) {
        return
    }
    // For remote participants, remove these streams from that participant's streams array.
    participantAdapter.participantUpdated(participantInfo.participantId) {
        it.streams.removeAll(streams)
    }
}

override fun onStreamsMutedChanged(
    stage: Stage,
    participantInfo: ParticipantInfo,
    streams: MutableList<StageStream>
) {
    // We don't want to take any action for the local participant because we track those streams locally
    if (participantInfo.isLocal) {
        return
    }
    // For remote participants, notify the adapter that the participant has been updated. There is no need to modify
    // the `streams` property on the `StageParticipant` because it is the same `StageStream` instance. Just
    // query the `isMuted` property again.
    participantAdapter.participantUpdated(participantInfo.participantId) {}
}
```

## Implementação de um RecyclerView personalizado com o LayoutManager
<a name="getting-started-pub-sub-android-layout"></a>

A organização de diferentes números de participantes pode ser complexa. Você deseja que eles ocupem todo o quadro da visualização primária, mas não quer lidar com a configuração de cada participante independentemente. Para facilitar isso, abordaremos a implementação de um `RecyclerView.LayoutManager`.

Crie outra nova classe, `StageLayoutManager`, que deve abranger o `GridLayoutManager`. Essa classe foi projetada para calcular o layout de cada participante com base no número de participantes em um layout de linha/coluna com base no fluxo. Cada linha tem a mesma altura que as outras, mas as colunas podem ter larguras diferentes por linha. Consulte o comentário do código acima da variável `layouts` para obter uma descrição de como personalizar esse comportamento.

```
package com.amazonaws.ivs.realtime.basicrealtime

import android.content.Context
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView

class StageLayoutManager(context: Context?) : GridLayoutManager(context, 6) {

    companion object {
        /**
         * This 2D array contains the description of how the grid of participants should be rendered
         * The index of the 1st dimension is the number of participants needed to active that configuration
         * Meaning if there is 1 participant, index 0 will be used. If there are 5 participants, index 4 will be used.
         *
         * The 2nd dimension is a description of the layout. The length of the array is the number of rows that
         * will exist, and then each number within that array is the number of columns in each row.
         *
         * See the code comments next to each index for concrete examples.
         *
         * This can be customized to fit any layout configuration needed.
         */
        val layouts: List<List<Int>> = listOf(
            // 1 participant
            listOf(1), // 1 row, full width
            // 2 participants
            listOf(1, 1), // 2 rows, all columns are full width
            // 3 participants
            listOf(1, 2), // 2 rows, first row's column is full width then 2nd row's columns are 1/2 width
            // 4 participants
            listOf(2, 2), // 2 rows, all columns are 1/2 width
            // 5 participants
            listOf(1, 2, 2), // 3 rows, first row's column is full width, 2nd and 3rd row's columns are 1/2 width
            // 6 participants
            listOf(2, 2, 2), // 3 rows, all column are 1/2 width
            // 7 participants
            listOf(2, 2, 3), // 3 rows, 1st and 2nd row's columns are 1/2 width, 3rd row's columns are 1/3rd width
            // 8 participants
            listOf(2, 3, 3),
            // 9 participants
            listOf(3, 3, 3),
            // 10 participants
            listOf(2, 3, 2, 3),
            // 11 participants
            listOf(2, 3, 3, 3),
            // 12 participants
            listOf(3, 3, 3, 3),
        )
    }

    init {
        spanSizeLookup = object : SpanSizeLookup() {
            override fun getSpanSize(position: Int): Int {
                if (itemCount <= 0) {
                    return 1
                }
                // Calculate the row we're in
                val config = layouts[itemCount - 1]
                var row = 0
                var curPosition = position
                while (curPosition - config[row] >= 0) {
                    curPosition -= config[row]
                    row++
                }
                // spanCount == max spans, config[row] = number of columns we want
                // So spanCount / config[row] would be something like 6 / 3 if we want 3 columns.
                // So this will take up 2 spans, with a max of 6 is 1/3rd of the view.
                return spanCount / config[row]
            }
        }
    }

    override fun onLayoutChildren(recycler: RecyclerView.Recycler?, state: RecyclerView.State?) {
        if (itemCount <= 0 || state?.isPreLayout == true) return

        val parentHeight = height
        val itemHeight = parentHeight / layouts[itemCount - 1].size // height divided by number of rows.

        // Set the height of each view based on how many rows exist for the current participant count.
        for (i in 0 until childCount) {
            val child = getChildAt(i) ?: continue
            val layoutParams = child.layoutParams as RecyclerView.LayoutParams
            if (layoutParams.height != itemHeight) {
                layoutParams.height = itemHeight
                child.layoutParams = layoutParams
            }
        }
        // After we set the height for all our views, call super.
        // This works because our RecyclerView can not scroll and all views are always visible with stable IDs.
        super.onLayoutChildren(recycler, state)
    }

    override fun canScrollVertically(): Boolean = false
    override fun canScrollHorizontally(): Boolean = false
}
```

De volta a `MainActivity.kt`, é necessário definir o adaptador e o gerenciador de layout para o nosso `RecyclerView`:

```
// In onCreate after setting recyclerView.
recyclerView.layoutManager = StageLayoutManager(this)
recyclerView.adapter = viewModel.participantAdapter
```

## Conexão de ações da interface do usuário
<a name="getting-started-pub-sub-android-actions"></a>

Estamos quase finalizando, existem apenas algumas ações da interface do usuário que precisamos conectar.

Primeiro, faremos com que nossa `MainActivity` observe as alterações de `StateFlow` do `MainViewModel`:

```
// At the end of your onCreate method
lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.CREATED) {
        viewModel.connectionState.collect { state ->
            buttonJoin.setText(if (state == ConnectionState.DISCONNECTED) R.string.join else R.string.leave)
            textViewState.text = getString(R.string.state, state.name)
        }
    }
}
```

Em seguida, adicionamos receptores ao nosso botão Ingressar e à caixa de seleção Publicar:

```
buttonJoin.setOnClickListener {
    viewModel.joinStage(editTextToken.text.toString())
}
checkboxPublish.setOnCheckedChangeListener { _, isChecked ->
    viewModel.setPublishEnabled(isChecked)
}
```

Ambas as funcionalidades de chamada acima estão em nosso `MainViewModel`, que implementamos agora:

```
internal fun joinStage(token: String) {
    if (_connectionState.value != Stage.ConnectionState.DISCONNECTED) {
        // If we're already connected to a stage, leave it.
        stage?.leave()
    } else {
        if (token.isEmpty()) {
            Toast.makeText(getApplication(), "Empty Token", Toast.LENGTH_SHORT).show()
            return
        }
        try {
            // Destroy the old stage first before creating a new one.
            stage?.release()
            val stage = Stage(getApplication(), token, this)
            stage.addRenderer(this)
            stage.join()
            this.stage = stage
        } catch (e: BroadcastException) {
            Toast.makeText(getApplication(), "Failed to join stage ${e.localizedMessage}", Toast.LENGTH_LONG).show()
            e.printStackTrace()
        }
    }
}

internal fun setPublishEnabled(enabled: Boolean) {
    publishEnabled = enabled
}
```

## Renderização dos participantes
<a name="getting-started-pub-sub-android-participants"></a>

Por fim, precisamos renderizar os dados que recebemos do SDK no item do participante que criamos anteriormente. Já temos a lógica do `RecyclerView` finalizada, então só precisamos implementar a API `bind` em `ParticipantItem`.

Começaremos com a adição da função vazia e, em seguida, abordaremos ela detalhadamente:

```
fun bind(participant: StageParticipant) {

}
```

Primeiro, trataremos do estado fácil, do ID do participante, do estado de publicação e do estado de inscrição. Para esses, apenas atualizamos nosso `TextViews` diretamente:

```
val participantId = if (participant.isLocal) {
    "You (${participant.participantId ?: "Disconnected"})"
} else {
    participant.participantId
}
textViewParticipantId.text = participantId
textViewPublish.text = participant.publishState.name
textViewSubscribe.text = participant.subscribeState.name
```

Em seguida, atualizaremos os estados de áudio e vídeo silenciados. Para obter o estado silenciado, precisamos encontrar o `ImageDevice` e o `AudioDevice` na matriz de streams. Para otimizar a performance, lembramos os últimos IDs de dispositivo anexados.

```
// This belongs outside the `bind` API.
private var imageDeviceUrn: String? = null
private var audioDeviceUrn: String? = null

// This belongs inside the `bind` API.
val newImageStream = participant
    .streams
    .firstOrNull { it.device is ImageDevice }
textViewVideoMuted.text = if (newImageStream != null) {
    if (newImageStream.muted) "Video muted" else "Video not muted"
} else {
    "No video stream"
}

val newAudioStream = participant
    .streams
    .firstOrNull { it.device is AudioDevice }
textViewAudioMuted.text = if (newAudioStream != null) {
    if (newAudioStream.muted) "Audio muted" else "Audio not muted"
} else {
    "No audio stream"
}
```

Por fim, desejamos renderizar uma visualização prévia para `imageDevice`:

```
if (newImageStream?.device?.descriptor?.urn != imageDeviceUrn) {
    // If the device has changed, remove all subviews from the preview container
    previewContainer.removeAllViews()
    (newImageStream?.device as? ImageDevice)?.let {
        val preview = it.getPreviewView(BroadcastConfiguration.AspectMode.FIT)
        previewContainer.addView(preview)
        preview.layoutParams = FrameLayout.LayoutParams(
            FrameLayout.LayoutParams.MATCH_PARENT,
            FrameLayout.LayoutParams.MATCH_PARENT
        )
    }
}
imageDeviceUrn = newImageStream?.device?.descriptor?.urn
```

Além disso, exibimos estatísticas de áudio do `audioDevice`:

```
if (newAudioStream?.device?.descriptor?.urn != audioDeviceUrn) {
    (newAudioStream?.device as? AudioDevice)?.let {
        it.setStatsCallback { _, rms ->
            textViewAudioLevel.text = "Audio Level: ${rms.roundToInt()} dB"
        }
    }
}
audioDeviceUrn = newAudioStream?.device?.descriptor?.urn
```

# Publicar e assinar com o SDK de Transmissão para iOS do IVS
<a name="getting-started-pub-sub-ios"></a>

Esta seção descreve as etapas envolvidas na publicação e assinatura de um estágio usando a sua aplicação para iOS.

## Criar visualizações
<a name="getting-started-pub-sub-ios-views"></a>

Começamos usando o arquivo `ViewController.swift` criado automaticamente para importar `AmazonIVSBroadcast` e, em seguida, adicionamos alguns `@IBOutlets` para vincular:

```
import AmazonIVSBroadcast

class ViewController: UIViewController {

    @IBOutlet private var textFieldToken: UITextField!
    @IBOutlet private var buttonJoin: UIButton!
    @IBOutlet private var labelState: UILabel!
    @IBOutlet private var switchPublish: UISwitch!
    @IBOutlet private var collectionViewParticipants: UICollectionView!
```

Agora, criamos essas visualizações e as vinculamos no `Main.storyboard`. Esta é a estrutura de visualização que usaremos:

![\[Uso de Main.storyboard para a criação de uma visualização do iOS.\]](http://docs.aws.amazon.com/pt_br/ivs/latest/RealTimeUserGuide/images/Publish_iOS_1.png)


Para a configuração do AutoLayout, precisamos personalizar três visualizações. A primeira visualização corresponde a **Collection View Participants** (uma `UICollectionView`). Vincule **Leading**, **Trailing** e **Bottom** à **Safe Area**. Também vincule **Top** ao **Controls Container**.

![\[Personalização da visualização Collection View Participants do iOS.\]](http://docs.aws.amazon.com/pt_br/ivs/latest/RealTimeUserGuide/images/Publish_iOS_2.png)


A segunda visualização corresponde ao **Controls Container**. Vincule **Leading**, **Trailing** e **Top** à **Safe Area**:

![\[Personalização da visualização do Controls Container do iOS.\]](http://docs.aws.amazon.com/pt_br/ivs/latest/RealTimeUserGuide/images/Publish_iOS_3.png)


A terceira e última visualização corresponde a **Vertical Stack View**. Vincule **Top**, **Leading**, **Trailing** e **Bottom** à **Superview**. Para estilizar, defina o espaçamento para 8, em vez de 0.

![\[Personalização da visualização Vertical Stack do iOS.\]](http://docs.aws.amazon.com/pt_br/ivs/latest/RealTimeUserGuide/images/Publish_iOS_4.png)


A **UIStackViews** lidará com o layout das visualizações restantes. Para todas as três **UIStackViews**, use **Fill** como **Alignment** e **Distribution**.

![\[Personalização das visualizações restantes do iOS com a UIStackViews.\]](http://docs.aws.amazon.com/pt_br/ivs/latest/RealTimeUserGuide/images/Publish_iOS_5.png)


Por fim, vincularemos essas visualizações ao nosso `ViewController`. Depois da etapa acima, mapeie as seguintes visualizações:
+ **Text Field Join** é vinculado a `textFieldToken`.
+ **Button Join** é vinculado a `buttonJoin`.
+ **Label State** é vinculado a `labelState`.
+ **Switch Publish** é vinculada a `switchPublish`.
+ **Collection View Participants** é vinculada a `collectionViewParticipants`.

Aproveite também para definir a `dataSource` do item **Collection View Participants** para o `ViewController` proprietário:

![\[Definição da dataSource da visualização Collection View Participants para a aplicação iOS.\]](http://docs.aws.amazon.com/pt_br/ivs/latest/RealTimeUserGuide/images/Publish_iOS_6.png)


Agora, criaremos a subclasse `UICollectionViewCell` na qual renderizaremos os participantes. Comece com a criação de um novo arquivo **Cocoa Touch Class**:

![\[Criação de uma UICollectionViewCell para renderizar os participantes em tempo real do iOS.\]](http://docs.aws.amazon.com/pt_br/ivs/latest/RealTimeUserGuide/images/Publish_iOS_7.png)


Nomeie-o como `ParticipantUICollectionViewCell` e torne-o uma subclasse de `UICollectionViewCell` no Swift. Começamos no arquivo Swift novamente, criando nosso `@IBOutlets` para vincular:

```
import AmazonIVSBroadcast

class ParticipantCollectionViewCell: UICollectionViewCell {

    @IBOutlet private var viewPreviewContainer: UIView!
    @IBOutlet private var labelParticipantId: UILabel!
    @IBOutlet private var labelSubscribeState: UILabel!
    @IBOutlet private var labelPublishState: UILabel!
    @IBOutlet private var labelVideoMuted: UILabel!
    @IBOutlet private var labelAudioMuted: UILabel!
    @IBOutlet private var labelAudioVolume: UILabel!
```

No arquivo XIB associado, crie esta hierarquia de visualização:

![\[Criação de uma hierarquia de visualização do iOS no arquivo XIB associado.\]](http://docs.aws.amazon.com/pt_br/ivs/latest/RealTimeUserGuide/images/Publish_iOS_8.png)


Para o AutoLayout, modificaremos as três visualizações novamente. A primeira visualização corresponde a **View Preview Container**. Defina **Trailing**, **Leading**, **Top** e **Bottom** para a **Participant Collection View Cell**.

![\[Personalização da visualização View Preview Container do iOS.\]](http://docs.aws.amazon.com/pt_br/ivs/latest/RealTimeUserGuide/images/Publish_iOS_9.png)


A segunda visualização corresponde a **View**. Defina **Leading** e **Top** para a **Participant Collection View Cell** e altere o valor para 4.

![\[Personalização da visualização View do iOS.\]](http://docs.aws.amazon.com/pt_br/ivs/latest/RealTimeUserGuide/images/Publish_iOS_10.png)


A terceira visualização corresponde a **Stack View**. Defina **Trailing**, **Leading**, **Top** e **Bottom** para a **Superview** e altere o valor para 4.

![\[Personalização da visualização Stack View do iOS.\]](http://docs.aws.amazon.com/pt_br/ivs/latest/RealTimeUserGuide/images/Publish_iOS_11.png)


## Permissões e temporizador de ociosidade
<a name="getting-started-pub-sub-ios-perms"></a>

Retornando ao nosso `ViewController`, desabilitaremos o temporizador de ociosidade do sistema para evitar que o dispositivo entre em hibernação enquanto nossa aplicação estiver sendo utilizada:

```
override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    // Prevent the screen from turning off during a call.
    UIApplication.shared.isIdleTimerDisabled = true
}

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

Em seguida, solicitamos permissões de câmera e de microfone por parte do sistema:

```
private func checkPermissions() {
    checkOrGetPermission(for: .video) { [weak self] granted in
        guard granted else {
            print("Video permission denied")
            return
        }
        self?.checkOrGetPermission(for: .audio) { [weak self] granted in
            guard granted else {
                print("Audio permission denied")
                return
            }
            self?.setupLocalUser() // we will cover this later
        }
    }
}

private func checkOrGetPermission(for mediaType: AVMediaType, _ result: @escaping (Bool) -> Void) {
    func mainThreadResult(_ success: Bool) {
        DispatchQueue.main.async {
            result(success)
        }
    }
    switch AVCaptureDevice.authorizationStatus(for: mediaType) {
    case .authorized: mainThreadResult(true)
    case .notDetermined:
        AVCaptureDevice.requestAccess(for: mediaType) { granted in
            mainThreadResult(granted)
        }
    case .denied, .restricted: mainThreadResult(false)
    @unknown default: mainThreadResult(false)
    }
}
```

## Estado da aplicação
<a name="getting-started-pub-sub-ios-app-state"></a>

Precisamos configurar nosso `collectionViewParticipants` com o arquivo de layout que criamos anteriormente:

```
override func viewDidLoad() {
    super.viewDidLoad()
    // We render everything to exactly the frame, so don't allow scrolling.
    collectionViewParticipants.isScrollEnabled = false
    collectionViewParticipants.register(UINib(nibName: "ParticipantCollectionViewCell", bundle: .main), forCellWithReuseIdentifier: "ParticipantCollectionViewCell")
}
```

Para representar cada participante, criamos uma estrutura simples chamada `StageParticipant`. Isso pode ser incluso no arquivo `ViewController.swift` ou um novo arquivo pode ser criado.

```
import Foundation
import AmazonIVSBroadcast

struct StageParticipant {
    let isLocal: Bool
    var participantId: String?
    var publishState: IVSParticipantPublishState = .notPublished
    var subscribeState: IVSParticipantSubscribeState = .notSubscribed
    var streams: [IVSStageStream] = []

    init(isLocal: Bool, participantId: String?) {
        self.isLocal = isLocal
        self.participantId = participantId
    }
}
```

Para acompanhar esses participantes, mantemos uma variedade deles como propriedade privada em nosso `ViewController`:

```
private var participants = [StageParticipant]()
```

Essa propriedade será usada para potencializar nosso `UICollectionViewDataSource` que foi vinculado do roteiro visual anteriormente:

```
extension ViewController: UICollectionViewDataSource {

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return participants.count
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ParticipantCollectionViewCell", for: indexPath) as? ParticipantCollectionViewCell {
            cell.set(participant: participants[indexPath.row])
            return cell
        } else {
            fatalError("Couldn't load custom cell type 'ParticipantCollectionViewCell'")
        }
    }

}
```

Para ver sua própria visualização prévia antes de ingressar em um palco, criamos um participante local imediatamente:

```
override func viewDidLoad() {
    /* existing UICollectionView code */
    participants.append(StageParticipant(isLocal: true, participantId: nil))
}
```

Isso resulta na renderização de uma célula de participante imediatamente após a execução da aplicação, representando o participante local.

Os usuários querem ver a si mesmos antes de ingressar em um palco, por isso, em seguida, implementamos o método `setupLocalUser()` que foi chamado pelo código de gerenciamento de permissões anteriormente. Armazenamos a referência da câmera e do microfone como objetos `IVSLocalStageStream`.

```
private var streams = [IVSLocalStageStream]()
private let deviceDiscovery = IVSDeviceDiscovery()

private func setupLocalUser() {
    // Gather our camera and microphone once permissions have been granted
    let devices = deviceDiscovery.listLocalDevices()
    streams.removeAll()
    if let camera = devices.compactMap({ $0 as? IVSCamera }).first {
        streams.append(IVSLocalStageStream(device: camera))
        // Use a front camera if available.
        if let frontSource = camera.listAvailableInputSources().first(where: { $0.position == .front }) {
            camera.setPreferredInputSource(frontSource)
        }
    }
    if let mic = devices.compactMap({ $0 as? IVSMicrophone }).first {
        streams.append(IVSLocalStageStream(device: mic))
    }
    participants[0].streams = streams
    participantsChanged(index: 0, changeType: .updated)
}
```

Aqui encontramos a câmera e o microfone do dispositivo por meio do SDK e os armazenamos em nosso objeto `streams` local e, em seguida, atribuímos a matriz de `streams` do primeiro participante (o participante local que criamos anteriormente) aos nossos `streams`. Por fim, chamamos `participantsChanged` com um `index` de 0 e `changeType` de `updated`. Essa função é uma função auxiliar para atualizar nosso `UICollectionView` com animações bonitas. Ela será algo assim:

```
private func participantsChanged(index: Int, changeType: ChangeType) {
    switch changeType {
    case .joined:
        collectionViewParticipants?.insertItems(at: [IndexPath(item: index, section: 0)])
    case .updated:
        // Instead of doing reloadItems, just grab the cell and update it ourselves. It saves a create/destroy of a cell
        // and more importantly fixes some UI flicker. We disable scrolling so the index path per cell
        // never changes.
        if let cell = collectionViewParticipants?.cellForItem(at: IndexPath(item: index, section: 0)) as? ParticipantCollectionViewCell {
            cell.set(participant: participants[index])
        }
    case .left:
        collectionViewParticipants?.deleteItems(at: [IndexPath(item: index, section: 0)])
    }
}
```

Não se preocupe com `cell.set` ainda; abordaremos isso mais tarde, mas é aí que renderizaremos o conteúdo da célula com base no participante.

O `ChangeType` é uma enumeração simples:

```
enum ChangeType {
    case joined, updated, left
}
```

Por fim, desejamos acompanhar se o palco está conectado. Usamos um simples `bool` para acompanhar isso, que atualizará automaticamente nossa interface do usuário quando ela for atualizada.

```
private var connectingOrConnected = false {
    didSet {
        buttonJoin.setTitle(connectingOrConnected ? "Leave" : "Join", for: .normal)
        buttonJoin.tintColor = connectingOrConnected ? .systemRed : .systemBlue
    }
}
```

## Implementar o SDK do palco
<a name="getting-started-pub-sub-ios-stage-sdk"></a>

Existem três [conceitos](ios-publish-subscribe.md#ios-publish-subscribe-concepts) principais que fundamentam a funcionalidade em tempo real: palco, estratégia e renderizador. O objetivo do projeto é minimizar a quantidade de lógica do lado do cliente que é necessária para desenvolver um produto funcional.

### IVSStageStrategy
<a name="getting-started-pub-sub-ios-stage-sdk-strategy"></a>

Nossa implementação de `IVSStageStrategy` é simples:

```
extension ViewController: IVSStageStrategy {
    func stage(_ stage: IVSStage, streamsToPublishForParticipant participant: IVSParticipantInfo) -> [IVSLocalStageStream] {
        // Return the camera and microphone to be published.
        // This is only called if `shouldPublishParticipant` returns true.
        return streams
    }

    func stage(_ stage: IVSStage, shouldPublishParticipant participant: IVSParticipantInfo) -> Bool {
        // Our publish status is based directly on the UISwitch view
        return switchPublish.isOn
    }

    func stage(_ stage: IVSStage, shouldSubscribeToParticipant participant: IVSParticipantInfo) -> IVSStageSubscribeType {
        // Subscribe to both audio and video for all publishing participants.
        return .audioVideo
    }
}
```

Para resumir, realizaremos publicações somente se o botão de publicação estiver na posição “ligado” e, se publicarmos, usaremos os streams que coletamos anteriormente. Por fim, para esta amostra, sempre nos inscrevemos em outros participantes, recebendo seus áudios e vídeos.

### IVSStageRenderer
<a name="getting-started-pub-sub-ios-stage-sdk-renderer"></a>

A implementação de `IVSStageRenderer` também é bastante simples, embora, devido ao número de funções, contenha um pouco mais de código. A abordagem geral neste renderizador é atualizar nossa matriz de `participants` quando o SDK nos notifica sobre uma alteração em um participante. Existem certos cenários nos quais lidamos com os participantes locais de maneira diferente, pois decidimos gerenciá-los nós mesmos para que eles possam ver a visualização prévia da câmera antes de ingressar.

```
extension ViewController: IVSStageRenderer {

    func stage(_ stage: IVSStage, didChange connectionState: IVSStageConnectionState, withError error: Error?) {
        labelState.text = connectionState.text
        connectingOrConnected = connectionState != .disconnected
    }

    func stage(_ stage: IVSStage, participantDidJoin participant: IVSParticipantInfo) {
        if participant.isLocal {
            // If this is the local participant joining the Stage, update the first participant in our array because we
            // manually added that participant when setting up our preview
            participants[0].participantId = participant.participantId
            participantsChanged(index: 0, changeType: .updated)
        } else {
            // If they are not local, add them to the array as a newly joined participant.
            participants.append(StageParticipant(isLocal: false, participantId: participant.participantId))
            participantsChanged(index: (participants.count - 1), changeType: .joined)
        }
    }

    func stage(_ stage: IVSStage, participantDidLeave participant: IVSParticipantInfo) {
        if participant.isLocal {
            // If this is the local participant leaving the Stage, update the first participant in our array because
            // we want to keep the camera preview active
            participants[0].participantId = nil
            participantsChanged(index: 0, changeType: .updated)
        } else {
            // If they are not local, find their index and remove them from the array.
            if let index = participants.firstIndex(where: { $0.participantId == participant.participantId }) {
                participants.remove(at: index)
                participantsChanged(index: index, changeType: .left)
            }
        }
    }

    func stage(_ stage: IVSStage, participant: IVSParticipantInfo, didChange publishState: IVSParticipantPublishState) {
        // Update the publishing state of this participant
        mutatingParticipant(participant.participantId) { data in
            data.publishState = publishState
        }
    }

    func stage(_ stage: IVSStage, participant: IVSParticipantInfo, didChange subscribeState: IVSParticipantSubscribeState) {
        // Update the subscribe state of this participant
        mutatingParticipant(participant.participantId) { data in
            data.subscribeState = subscribeState
        }
    }

    func stage(_ stage: IVSStage, participant: IVSParticipantInfo, didChangeMutedStreams streams: [IVSStageStream]) {
        // We don't want to take any action for the local participant because we track those streams locally
        if participant.isLocal { return }
        // For remote participants, notify the UICollectionView that they have updated. There is no need to modify
        // the `streams` property on the `StageParticipant` because it is the same `IVSStageStream` instance. Just
        // query the `isMuted` property again.
        if let index = participants.firstIndex(where: { $0.participantId == participant.participantId }) {
            participantsChanged(index: index, changeType: .updated)
        }
    }

    func stage(_ stage: IVSStage, participant: IVSParticipantInfo, didAdd streams: [IVSStageStream]) {
        // We don't want to take any action for the local participant because we track those streams locally
        if participant.isLocal { return }
        // For remote participants, add these new streams to that participant's streams array.
        mutatingParticipant(participant.participantId) { data in
            data.streams.append(contentsOf: streams)
        }
    }

    func stage(_ stage: IVSStage, participant: IVSParticipantInfo, didRemove streams: [IVSStageStream]) {
        // We don't want to take any action for the local participant because we track those streams locally
        if participant.isLocal { return }
        // For remote participants, remove these streams from that participant's streams array.
        mutatingParticipant(participant.participantId) { data in
            let oldUrns = streams.map { $0.device.descriptor().urn }
            data.streams.removeAll(where: { stream in
                return oldUrns.contains(stream.device.descriptor().urn)
            })
        }
    }

    // A helper function to find a participant by its ID, mutate that participant, and then update the UICollectionView accordingly.
    private func mutatingParticipant(_ participantId: String?, modifier: (inout StageParticipant) -> Void) {
        guard let index = participants.firstIndex(where: { $0.participantId == participantId }) else {
            fatalError("Something is out of sync, investigate if this was a sample app or SDK issue.")
        }

        var participant = participants[index]
        modifier(&participant)
        participants[index] = participant
        participantsChanged(index: index, changeType: .updated)
    }
}
```

Este código usa uma extensão para converter o estado da conexão em um texto amigável:

```
extension IVSStageConnectionState {
    var text: String {
        switch self {
        case .disconnected: return "Disconnected"
        case .connecting: return "Connecting"
        case .connected: return "Connected"
        @unknown default: fatalError()
        }
    }
}
```

## Implementação de um UICollectionViewLayout personalizado
<a name="getting-started-pub-sub-ios-layout"></a>

A organização de diferentes números de participantes pode ser complexa. Você deseja que eles ocupem todo o quadro da visualização primária, mas não quer lidar com a configuração de cada participante independentemente. Para facilitar isso, abordaremos a implementação de um `UICollectionViewLayout`.

Crie outro novo arquivo, `ParticipantCollectionViewLayout.swift`, que deve abranger o `UICollectionViewLayout`. Essa classe usará outra classe chamada `StageLayoutCalculator`, que abordaremos em breve. A classe recebe os valores de quadros calculados para cada participante e, em seguida, gera os objetos `UICollectionViewLayoutAttributes` necessários.

```
import Foundation
import UIKit

/**
 Code modified from https://developer.apple.com/documentation/uikit/views_and_controls/collection_views/layouts/customizing_collection_view_layouts?language=objc
 */
class ParticipantCollectionViewLayout: UICollectionViewLayout {

    private let layoutCalculator = StageLayoutCalculator()

    private var contentBounds = CGRect.zero
    private var cachedAttributes = [UICollectionViewLayoutAttributes]()

    override func prepare() {
        super.prepare()

        guard let collectionView = collectionView else { return }

        cachedAttributes.removeAll()
        contentBounds = CGRect(origin: .zero, size: collectionView.bounds.size)

        layoutCalculator.calculateFrames(participantCount: collectionView.numberOfItems(inSection: 0),
                                         width: collectionView.bounds.size.width,
                                         height: collectionView.bounds.size.height,
                                         padding: 4)
        .enumerated()
        .forEach { (index, frame) in
            let attributes = UICollectionViewLayoutAttributes(forCellWith: IndexPath(item: index, section: 0))
            attributes.frame = frame
            cachedAttributes.append(attributes)
            contentBounds = contentBounds.union(frame)
        }
    }

    override var collectionViewContentSize: CGSize {
        return contentBounds.size
    }

    override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
        guard let collectionView = collectionView else { return false }
        return !newBounds.size.equalTo(collectionView.bounds.size)
    }

    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        return cachedAttributes[indexPath.item]
    }

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        var attributesArray = [UICollectionViewLayoutAttributes]()

        // Find any cell that sits within the query rect.
        guard let lastIndex = cachedAttributes.indices.last, let firstMatchIndex = binSearch(rect, start: 0, end: lastIndex) else {
            return attributesArray
        }

        // Starting from the match, loop up and down through the array until all the attributes
        // have been added within the query rect.
        for attributes in cachedAttributes[..<firstMatchIndex].reversed() {
            guard attributes.frame.maxY >= rect.minY else { break }
            attributesArray.append(attributes)
        }

        for attributes in cachedAttributes[firstMatchIndex...] {
            guard attributes.frame.minY <= rect.maxY else { break }
            attributesArray.append(attributes)
        }

        return attributesArray
    }

    // Perform a binary search on the cached attributes array.
    func binSearch(_ rect: CGRect, start: Int, end: Int) -> Int? {
        if end < start { return nil }

        let mid = (start + end) / 2
        let attr = cachedAttributes[mid]

        if attr.frame.intersects(rect) {
            return mid
        } else {
            if attr.frame.maxY < rect.minY {
                return binSearch(rect, start: (mid + 1), end: end)
            } else {
                return binSearch(rect, start: start, end: (mid - 1))
            }
        }
    }
}
```

A classe mais importante é a `StageLayoutCalculator.swift`. Ela foi projetada para calcular os quadros de cada participante com base no número de participantes em um layout de linha/coluna baseado em fluxo. Cada linha tem a mesma altura que as outras, mas as colunas podem ter larguras diferentes por linha. Consulte o comentário do código acima da variável `layouts` para obter uma descrição de como personalizar esse comportamento.

```
import Foundation
import UIKit

class StageLayoutCalculator {

    /// This 2D array contains the description of how the grid of participants should be rendered
    /// The index of the 1st dimension is the number of participants needed to active that configuration
    /// Meaning if there is 1 participant, index 0 will be used. If there are 5 participants, index 4 will be used.
    ///
    /// The 2nd dimension is a description of the layout. The length of the array is the number of rows that
    /// will exist, and then each number within that array is the number of columns in each row.
    ///
    /// See the code comments next to each index for concrete examples.
    ///
    /// This can be customized to fit any layout configuration needed.
    private let layouts: [[Int]] = [
        // 1 participant
        [ 1 ], // 1 row, full width
        // 2 participants
        [ 1, 1 ], // 2 rows, all columns are full width
        // 3 participants
        [ 1, 2 ], // 2 rows, first row's column is full width then 2nd row's columns are 1/2 width
        // 4 participants
        [ 2, 2 ], // 2 rows, all columns are 1/2 width
        // 5 participants
        [ 1, 2, 2 ], // 3 rows, first row's column is full width, 2nd and 3rd row's columns are 1/2 width
        // 6 participants
        [ 2, 2, 2 ], // 3 rows, all column are 1/2 width
        // 7 participants
        [ 2, 2, 3 ], // 3 rows, 1st and 2nd row's columns are 1/2 width, 3rd row's columns are 1/3rd width
        // 8 participants
        [ 2, 3, 3 ],
        // 9 participants
        [ 3, 3, 3 ],
        // 10 participants
        [ 2, 3, 2, 3 ],
        // 11 participants
        [ 2, 3, 3, 3 ],
        // 12 participants
        [ 3, 3, 3, 3 ],
    ]

    // Given a frame (this could be for a UICollectionView, or a Broadcast Mixer's canvas), calculate the frames for each
    // participant, with optional padding.
    func calculateFrames(participantCount: Int, width: CGFloat, height: CGFloat, padding: CGFloat) -> [CGRect] {
        if participantCount > layouts.count {
            fatalError("Only \(layouts.count) participants are supported at this time")
        }
        if participantCount == 0 {
            return []
        }
        var currentIndex = 0
        var lastFrame: CGRect = .zero

        // If the height is less than the width, the rows and columns will be flipped.
        // Meaning for 6 participants, there will be 2 rows of 3 columns each.
        let isVertical = height > width

        let halfPadding = padding / 2.0

        let layout = layouts[participantCount - 1] // 1 participant is in index 0, so `-1`.
        let rowHeight = (isVertical ? height : width) / CGFloat(layout.count)

        var frames = [CGRect]()
        for row in 0 ..< layout.count {
            // layout[row] is the number of columns in a layout
            let itemWidth = (isVertical ? width : height) / CGFloat(layout[row])
            let segmentFrame = CGRect(x: (isVertical ? 0 : lastFrame.maxX) + halfPadding,
                                      y: (isVertical ? lastFrame.maxY : 0) + halfPadding,
                                      width: (isVertical ? itemWidth : rowHeight) - padding,
                                      height: (isVertical ? rowHeight : itemWidth) - padding)

            for column in 0 ..< layout[row] {
                var frame = segmentFrame
                if isVertical {
                    frame.origin.x = (itemWidth * CGFloat(column)) + halfPadding
                } else {
                    frame.origin.y = (itemWidth * CGFloat(column)) + halfPadding
                }
                frames.append(frame)
                currentIndex += 1
            }

            lastFrame = segmentFrame
            lastFrame.origin.x += halfPadding
            lastFrame.origin.y += halfPadding
        }
        return frames
    }

}
```

De volta ao `Main.storyboard`, certifique-se de definir a classe de layout de `UICollectionView` para a classe que acabamos de criar:

![\[Xcode interface showing storyboard with UICollectionView and its layout settings.\]](http://docs.aws.amazon.com/pt_br/ivs/latest/RealTimeUserGuide/images/Publish_iOS_12.png)


## Conexão de ações da interface do usuário
<a name="getting-started-pub-sub-ios-actions"></a>

Estamos quase finalizando, mas há algumas `IBActions` que precisamos criar.

Primeiro, abordaremos o botão Ingressar. Ele responde de forma diferente com base no valor de `connectingOrConnected`. Quando tudo já está conectado, ele apenas deixa o palco. Se houver desconexão, ele lê o texto do token `UITextField` e cria um novo `IVSStage` com esse texto. Em seguida, adicionamos nosso `ViewController` como `strategy`, `errorDelegate` e renderizador para o `IVSStage` e, finalmente, ingressamos no palco de forma assíncrona.

```
@IBAction private func joinTapped(_ sender: UIButton) {
    if connectingOrConnected {
        // If we're already connected to a Stage, leave it.
        stage?.leave()
    } else {
        guard let token = textFieldToken.text else {
            print("No token")
            return
        }
        // Hide the keyboard after tapping Join
        textFieldToken.resignFirstResponder()
        do {
            // Destroy the old Stage first before creating a new one.
            self.stage = nil
            let stage = try IVSStage(token: token, strategy: self)
            stage.errorDelegate = self
            stage.addRenderer(self)
            try stage.join()
            self.stage = stage
        } catch {
            print("Failed to join stage - \(error)")
        }
    }
}
```

A outra ação da interface do usuário que precisamos conectar é a opção de publicação:

```
@IBAction private func publishToggled(_ sender: UISwitch) {
    // Because the strategy returns the value of `switchPublish.isOn`, just call `refreshStrategy`.
    stage?.refreshStrategy()
}
```

## Renderização dos participantes
<a name="getting-started-pub-sub-ios-participants"></a>

Por fim, precisamos renderizar os dados que recebemos do SDK na célula do participante que criamos anteriormente. Já temos a lógica do `UICollectionView` finalizada, então só precisamos implementar a API `set` em `ParticipantCollectionViewCell.swift`.

Começaremos com a adição da função `empty` e, em seguida, abordaremos ela detalhadamente:

```
func set(participant: StageParticipant) {
   
}
```

Primeiro, tratamos do estado fácil, do ID do participante, do estado de publicação e do estado de inscrição. Para esses, apenas atualizamos nosso `UILabels` diretamente:

```
labelParticipantId.text = participant.isLocal ? "You (\(participant.participantId ?? "Disconnected"))" : participant.participantId
labelPublishState.text = participant.publishState.text
labelSubscribeState.text = participant.subscribeState.text
```

As propriedades de texto das enumerações de publicação e inscrição vêm de extensões locais:

```
extension IVSParticipantPublishState {
    var text: String {
        switch self {
        case .notPublished: return "Not Published"
        case .attemptingPublish: return "Attempting to Publish"
        case .published: return "Published"
        @unknown default: fatalError()
        }
    }
}

extension IVSParticipantSubscribeState {
    var text: String {
        switch self {
        case .notSubscribed: return "Not Subscribed"
        case .attemptingSubscribe: return "Attempting to Subscribe"
        case .subscribed: return "Subscribed"
        @unknown default: fatalError()
        }
    }
}
```

Em seguida, atualizamos os estados de áudio e vídeo silenciados. Para obter os estados silenciados, precisamos encontrar o `IVSImageDevice` e o `IVSAudioDevice` da matriz de `streams`. Para otimizar a performance, lembraremos dos últimos dispositivos conectados.

```
// This belongs outside `set(participant:)`
private var registeredStreams: Set<IVSStageStream> = []
private var imageDevice: IVSImageDevice? {
    return registeredStreams.lazy.compactMap { $0.device as? IVSImageDevice }.first
}
private var audioDevice: IVSAudioDevice? {
    return registeredStreams.lazy.compactMap { $0.device as? IVSAudioDevice }.first
}

// This belongs inside `set(participant:)`
let existingAudioStream = registeredStreams.first { $0.device is IVSAudioDevice }
let existingImageStream = registeredStreams.first { $0.device is IVSImageDevice }

registeredStreams = Set(participant.streams)

let newAudioStream = participant.streams.first { $0.device is IVSAudioDevice }
let newImageStream = participant.streams.first { $0.device is IVSImageDevice }

// `isMuted != false` covers the stream not existing, as well as being muted.
labelVideoMuted.text = "Video Muted: \(newImageStream?.isMuted != false)"
labelAudioMuted.text = "Audio Muted: \(newAudioStream?.isMuted != false)"
```

Por fim, desejamos renderizar uma visualização prévia para o `imageDevice` e exibir as estatísticas de áudio do `audioDevice`:

```
if existingImageStream !== newImageStream {
    // The image stream has changed
    updatePreview() // We’ll cover this next
}

if existingAudioStream !== newAudioStream {
    (existingAudioStream?.device as? IVSAudioDevice)?.setStatsCallback(nil)
    audioDevice?.setStatsCallback( { [weak self] stats in
        self?.labelAudioVolume.text = String(format: "Audio Level: %.0f dB", stats.rms)
    })
    // When the audio stream changes, it will take some time to receive new stats. Reset the value temporarily.
    self.labelAudioVolume.text = "Audio Level: -100 dB"
}
```

A última função que precisamos criar é `updatePreview()`, que adiciona uma visualização prévia do participante à nossa visualização:

```
private func updatePreview() {
    // Remove any old previews from the preview container
    viewPreviewContainer.subviews.forEach { $0.removeFromSuperview() }
    if let imageDevice = self.imageDevice {
        if let preview = try? imageDevice.previewView(with: .fit) {
            viewPreviewContainer.addSubviewMatchFrame(preview)
        }
    }
}
```

O código acima usa uma função auxiliar na `UIView` para facilitar a incorporação de subvisualizações:

```
extension UIView {
    func addSubviewMatchFrame(_ view: UIView) {
        view.translatesAutoresizingMaskIntoConstraints = false
        self.addSubview(view)
        NSLayoutConstraint.activate([
            view.topAnchor.constraint(equalTo: self.topAnchor, constant: 0),
            view.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 0),
            view.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 0),
            view.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 0),
        ])
    }
}
```