

# Mise en route avec le streaming en temps réel IVS
<a name="getting-started"></a>

Ce document explique les étapes nécessaires à l’intégration du streaming en temps réel Amazon IVS dans votre application.

**Topics**
+ [Présentation de la diffusion en temps réel IVS](getting-started-introduction.md)
+ [Étape 1 : configurer des autorisations IAM](getting-started-iam-permissions.md)
+ [Étape 2 : Créer une étape avec Enregistrement optionnel des participants](getting-started-create-stage.md)
+ [Étape 3 :Distribuer des jetons de participants](getting-started-distribute-tokens.md)
+ [Étape 4 :Intégrer le SDK de diffusion IVS](getting-started-broadcast-sdk.md)
+ [Étape 5 :Publier une vidéo et s’y abonner](getting-started-pub-sub.md)

# Présentation de la diffusion en temps réel IVS
<a name="getting-started-introduction"></a>

Cette section répertorie les conditions préalables à l'utilisation de la diffusion en temps réel et présente la terminologie clé.

## Conditions préalables
<a name="getting-started-introduction-prereq"></a>

Avant d’utiliser le streaming en temps réel pour la première fois, exécutez les tâches suivantes. Pour obtenir des instructions, consultez la section [Mise en route avec le streaming à faible latence IVS](https://docs.aws.amazon.com/ivs/latest/LowLatencyUserGuide/getting-started.html).
+ Créer un compte AWS
+ Configurer les utilisateurs root et administratifs

## Autres références
<a name="getting-started-introduction-extref"></a>
+ [Référence du SDK de diffusion Web IVS](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference)
+ [Référence du SDK de diffusion Android IVS](https://aws.github.io/amazon-ivs-broadcast-docs/latest/android/)
+ [Référence du SDK de diffusion iOS IVS](https://aws.github.io/amazon-ivs-broadcast-docs/latest/ios/)
+ [Référence de l’API de streaming en temps réel IVS](https://docs.aws.amazon.com/ivs/latest/RealTimeAPIReference/Welcome.html)

## Terminologie du streaming en temps réel
<a name="getting-started-introduction-terminology"></a>


| Terme | Description | 
| --- | --- | 
| Étape | Un espace virtuel où les participants peuvent échanger des vidéos en temps réel. | 
| Host (Hôte) | Un participant qui envoie une vidéo locale vers la scène. | 
| Lecteur | Un participant qui reçoit la vidéo des hôtes. | 
| Participant | Un utilisateur connecté à la scène en tant qu’hôte ou spectateur. | 
| Jeton de participant | Un jeton qui authentifie un participant lorsqu’il rejoint une scène. | 
| SDK de diffusion | Une bibliothèque cliente qui permet aux participants d’envoyer et de recevoir des vidéos. | 

## Présentation des étapes
<a name="getting-started-introduction-steps"></a>

1. [Configurer des autorisations IAM](getting-started-iam-permissions.md) : créez une politique AWS Identity and Access Management (IAM) qui donne aux utilisateurs un ensemble d’autorisations de base et attribuez cette politique aux utilisateurs.

1. [Créer une scène](getting-started-create-stage.md) : créez un espace virtuel où les participants peuvent échanger des fichiers audio et vidéo en temps réel.

1. [Distribuer des jetons aux participants](getting-started-distribute-tokens.md) : envoyez des jetons aux participants pour qu’ils puissent rejoindre votre scène.

1. [Intégrer le SDK de diffusion IVS](getting-started-broadcast-sdk.md) : ajoutez le SDK de diffusion à votre application pour permettre aux participants d’envoyer et de recevoir des vidéos : [Web](getting-started-broadcast-sdk.md#getting-started-broadcast-sdk-web), [Android](getting-started-broadcast-sdk.md#getting-started-broadcast-sdk-android) et [iOS](getting-started-broadcast-sdk.md#getting-started-broadcast-sdk-ios).

1. [Publication et abonnement à une vidéo](getting-started-pub-sub.md) : envoyez votre vidéo vers la scène et recevez des vidéos d’autres hôtes : [console IVS](getting-started-pub-sub.md#getting-started-pub-sub-console), [Publication et abonnement avec le SDK de diffusion Web IVS](getting-started-pub-sub-web.md), [Publication et abonnement avec le SDK de diffusion Android IVS](getting-started-pub-sub-android.md) et [Publication et abonnement avec le SDK de diffusion iOS IVS](getting-started-pub-sub-ios.md).

# Étape 1 : configurer des autorisations IAM
<a name="getting-started-iam-permissions"></a>

Ensuite, vous devez créer une politique AWS Identity and Access Management (IAM) qui donne aux utilisateurs un ensemble d’autorisations de base (par exemple, pour créer une scène Amazon IVS et créer des jetons de participants) et affecter cette politique aux utilisateurs. Vous pouvez soit attribuer les autorisations lors de la création d’un [nouvel utilisateur](#iam-permissions-new-user), soit ajouter des autorisations à un [utilisateur existant](#iam-permissions-existing-user). Les deux procédures sont données ci-dessous.

Pour plus d’informations (par exemple, pour en savoir plus sur les utilisateurs et les politiques IAM, comment attacher une politique à un utilisateur et comment limiter les actions des utilisateurs sur Amazon IVS), consultez :
+ [Création d’un utilisateur IAM](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users_create.html#Using_CreateUser_console) dans le *Guide de l’utilisateur IAM*
+ Les informations dans [Sécurité d’Amazon IVS](https://docs.aws.amazon.com/ivs/latest/LowLatencyUserGuide/security.html) concernent l’IAM et les « politiques gérées pour IVS ». 
+ Les informations IAM contenues dans [Sécurité Amazon IVS](https://docs.aws.amazon.com/ivs/latest/LowLatencyUserGuide/security.html)

Vous pouvez utiliser une politique gérée par AWS existante pour Amazon IVS ou créer une politique qui personnalise les autorisations que vous souhaitez accorder à un ensemble d’utilisateurs, de groupes ou de rôles. Les deux approches sont décrites ci-dessous.

## Utiliser une politique existante pour les autorisations IVS
<a name="iam-permissions-existing-policy"></a>

Dans la plupart des cas, vous souhaiterez utiliser une politique gérée par AWS pour Amazon IVS. Elles sont décrites en détail dans la section [Managed Policies for IVS](https://docs.aws.amazon.com/ivs/latest/LowLatencyUserGuide/security-iam-awsmanpol.html) de *Sécurité IVS*.
+ Utilisez la politique gérée par AWS `IVSReadOnlyAccess` pour permettre à vos développeurs d’applications d’accéder à toutes les opérations des API IVS Get et List (pour la diffusion à faible latence et en temps réel).
+ Utilisez la politique gérée par AWS `IVSFullAccess` pour permettre à vos développeurs d’applications d’accéder à toutes les opérations des API IVS (pour la diffusion à faible latence et en temps réel).

## Facultatif : créer une politique personnalisée pour les autorisations Amazon IVS
<a name="iam-permissions-new-policy"></a>

Procédez comme suit :

1. Connectez-vous à la console de gestion AWS et ouvrez la console IAM à l’adresse [https://console.aws.amazon.com/iam/](https://console.aws.amazon.com/iam/).

1. Dans le panneau de navigation, sélectionnez **Politicies** (Politiques), puis **Create policy** (Créer une politique). La fenêtre **Spécifier les autorisations** s’ouvre.

1. Dans la fenêtre **Spécifier les autorisations**, choisissez l’onglet **JSON** et copiez/collez la politique IVS suivante dans la zone de texte de l’**Éditeur de politique**. (La politique n’inclut pas toutes les actions Amazon IVS. Vous pouvez ajouter/supprimer (Autoriser/Refuser) des autorisations d’accès aux opérations selon vos besoins. Consultez [Référence de l’API de diffusion en temps réel IVS](https://docs.aws.amazon.com//ivs/latest/RealTimeAPIReference/Welcome.html) pour en savoir plus sur les opérations 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. Toujours dans la fenêtre **Spécifier les autorisations**, choisissez **Suivant** (faites défiler la fenêtre vers le bas pour voir ceci). Une fenêtre **Vérifier et créer** s’ouvre. 

1. Dans la fenêtre **Vérifier et créer**, saisissez un **Nom de politique** et ajoutez éventuellement une **Description**. Notez le nom de la politique, car vous en aurez besoin lors de la création des utilisateurs (ci-dessous). En bas de la fenêtre, choisissez **Create policy** (Créer la politique).

1. Vous êtes renvoyé à la fenêtre de la console IAM, où vous devriez voir une bannière confirmant que votre nouvelle politique a été créée.

## Créer un nouvel utilisateur et ajouter des autorisations
<a name="iam-permissions-new-user"></a>

### Clés d’accès utilisateur IAM
<a name="iam-permissions-new-user-access-keys"></a>

Les clés d’accès IAM sont constituées d’un ID de clé d’accès et d’une clé d’accès secrète. Elles permettent de signer les demandes de manière programmatique auprès des services AWS. Si vous n’avez pas de clés d’accès, vous pouvez en créer à l’aide de la console de gestion AWS. En guise de bonnes pratiques, ne créez pas de clés d’accès pour l’utilisateur root.

*La seule fois où vous pouvez visualiser ou télécharger une clé d’accès secrète est lors de sa création. Vous ne pouvez pas la récupérer par la suite.* Toutefois, pour créer des clés d’accès, vous devez disposer des autorisations permettant d’effectuer les opérations IAM nécessaires.

Conservez toujours les clés d’accès en lieu sûr. Ne les partagez jamais avec des tiers (même si une demande semble provenir d’Amazon). Pour de plus amples informations, veuillez consulter [Gestion des clés d’accès pour les utilisateurs IAM](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html) dans le *Guide de l’utilisateur IAM*.

### Procédure
<a name="iam-permissions-new-user-procedure"></a>

Procédez comme suit :

1. Dans le panneau de navigation, choisissez **Utilisateurs**, puis **Créer un utilisateur**. Une fenêtre **Spécifier les détails de l’utilisateur** s’ouvre. 

1. Dans la fenêtre **Spécifier les détails de l’utilisateur** :

   1. Sous **Détails de l’utilisateur**, saisissez le nouveau **Nom d’utilisateur** à créer.

   1. Cochez **Fournir un accès utilisateur à l’AWS Management Console**.

   1. Sous **Mot de passe de la console**, sélectionnez **Mot de passe généré automatiquement**.

   1. Cochez **Les utilisateurs doivent créer un nouveau mot de passe lors de la prochaine connexion**.

   1. Choisissez **Suivant**. Une fenêtre **Définir les autorisations** s’ouvre.

1. Pour **Définir les autorisations**, sélectionnez **Attacher directement les politiques**. Une fenêtre **Politiques d’autorisations** s’ouvre.

1. Dans le champ de recherche, entrez le nom d’une politique IVS (soit une politique gérée par AWS, soit une politique personnalisée que vous avez créée précédemment). Lorsqu’elle est trouvée, cochez la case pour sélectionner la politique.

1. Choisissez **Suivant** (en bas de la fenêtre). Une fenêtre **Vérifier et créer** s’ouvre.

1. Dans la fenêtre **Vérifier et créer**, vérifiez que toutes les informations utilisateur sont correctes, puis choisissez **Créer un utilisateur** (en bas de la fenêtre).

1. La fenêtre **Récupérer le mot de passe** s’ouvre et contient les **Informations de connexion à la console**. *Enregistrez ces informations de manière sécurisée pour pouvoir vous y référer ultérieurement*. Une fois que vous avez termin, choisissez **Retourner à la liste des utilisateurs**.

## Ajouter des autorisations à un utilisateur existant
<a name="iam-permissions-existing-user"></a>

Procédez comme suit :

1. Connectez-vous à la console de gestion AWS et ouvrez la console IAM à l’adresse [https://console.aws.amazon.com/iam/](https://console.aws.amazon.com/iam/).

1. Dans le panneau de navigation, choisissez**Users** (Utilisateurs), puis choisissez un nom d’utilisateur existant à mettre à jour. (Choisissez le nom en cliquant dessus ; ne cochez pas la case de sélection.)

1. Sur la page **Récapitulatif**, dans l’onglet **Autorisations**, choisissez **Ajouter des autorisations**. La fenêtre **Ajouter des autorisations** s’ouvre.

1. Sélectionnez **Attach existing policies directly (Attacher directement les politiques existantes)**. Une fenêtre **Politiques d’autorisations** s’ouvre.

1. Dans le champ de recherche, entrez le nom d’une politique IVS (soit une politique gérée par AWS, soit une politique personnalisée que vous avez créée précédemment). Lorsque la politique est trouvée, cochez la case pour la sélectionner.

1. Choisissez **Suivant** (en bas de la fenêtre). Une fenêtre **Vérifier** s’ouvre.

1. Dans la fenêtre **Vérifier**, sélectionnez **Ajouter des autorisations** (en bas de la fenêtre).

1. Sur la page **Summary** (Récapitulatif), confirmez que la politique IVS a été ajoutée.

# Étape 2 : Créer une étape avec Enregistrement optionnel des participants
<a name="getting-started-create-stage"></a>

Une scène est un espace virtuel où les participants peuvent échanger des vidéos en temps réel. Il s’agit de la ressource de base de l’API de streaming en temps réel. Vous pouvez créer une scène à l’aide de la console ou de l’opération CreateStage.

Nous vous recommandons, dans la mesure du possible, de créer une scène pour chaque session logique et de le supprimer une fois terminé, plutôt que de conserver les anciennes scènes en vue d’une éventuelle réutilisation. Si les ressources périmées (anciennes scènes, à ne pas réutiliser) ne sont pas nettoyées, vous atteindrez probablement la limite du nombre maximum de scènes plus rapidement.

Vous pouvez créer une étape, avec ou sans enregistrement des participants individuels, via la console Amazon IVS ou AWS CLI. La création et l'enregistrement d'étape sont discutés ci-dessous.

## Enregistrement des participants individuels
<a name="getting-started-create-stage-ipr-overview"></a>

Vous avez la possibilité d'activer l'enregistrement des participants individuels pour une étape. Si la fonction d'enregistrement des participants individuels vers S3 est activée, toutes les diffusions sur l'étape sont enregistrés et stockés dans un compartiment de stockage Amazon S3 que vous possédez. Par la suite, l'enregistrement est disponible pour la lecture à la demande.

*Cette configuration est une option avancée.* L'enregistrement est désactivé par défaut lors de la création d'une étape.

Avant de pouvoir configurer une étape pour l'enregistrement, vous devez créer une *configuration de stockage*. Il s'agit d'une ressource qui spécifie un emplacement Amazon S3 où les flux enregistrés pour l’étape sont stockés. Vous pouvez créer et gérer des configurations de stockage à l'aide de la console ou de la CLI ; les deux procédures sont présentées ci-dessous. Une fois la configuration de stockage créée, vous pouvez l'associer à une étape de deux façons : lorsque vous créez l’étape (comme décrit ci-dessous) ou lorsque vous mettez à jour une étape existante. (Dans l’API, consultez [CreateStage](https://docs.aws.amazon.com//ivs/latest/RealTimeAPIReference/API_CreateStage.html) et [UpdateStage](https://docs.aws.amazon.com//ivs/latest/RealTimeAPIReference/API_UpdateStage.html).) Vous pouvez associer plusieurs étapes à la même configuration de stockage. Vous pouvez supprimer une configuration de stockage qui n'est plus associée à aucune étape.

Gardez les contraintes suivantes à l'esprit :
+ Vous devez être propriétaire du compartiment S3. Autrement dit, le compte qui configure une étape à enregistrer doit posséder le compartiment S3 où les enregistrements seront stockés.
+ L’étape, la configuration de stockage et l'emplacement S3 doivent se situer dans la même région AWS. Si vous créez des étapes dans d'autres régions et souhaitez les enregistrer, vous devez également configurer des configurations de stockage et des compartiments S3 dans ces régions.

L'enregistrement dans votre compartiment S3 nécessite une autorisation avec vos informations d'identification AWS. Pour donner à IVS l'accès requis, un [Rôle lié à un service](https://docs.aws.amazon.com/IAM/latest/UserGuide/using-service-linked-roles.html) (SLR) AWS IAM est créé automatiquement lors de la création de la configuration d'enregistrement : le SLR ne peut donner l'autorisation d'écriture IVS que sur le compartiment spécifique.

Notez que les problèmes de réseau entre l'emplacement de streaming et AWS, ou au sein d'AWS, peuvent entraîner une perte de données lors de l'enregistrement de votre flux. Dans ce cas, Amazon IVS accorde la priorité au flux en direct et non aux enregistrements. Pour la redondance, enregistrez localement via votre outil de streaming.

Pour plus d'informations (y compris comment configurer le post-traitement ou la lecture de VOD sur vos fichiers enregistrés), consultez [Enregistrement des participants individuels](rt-individual-participant-recording.md).

### Comment désactiver l’enregistrement
<a name="getting-started-disable-recording"></a>

Pour désactiver l'enregistrement Amazon S3 sur une étape existante :
+ Console : sur la page de détails de l'étape concernée, dans la section **Enregistrer les participants individuels**, désactivez **Activer l'enregistrement automatique** sous **Enregistrement automatique vers S3**, puis choisissez **Enregistrer les modifications**. Cette action dissocie la configuration de stockage de l’étape ; les flux de cette étape ne seront plus enregistrés.
+ CLI : exécutez la commande `update-stage` et indiquez une chaîne vide pour l'ARN de la configuration d'enregistrement.

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

  Cette action renvoie un objet d’étape avec une chaîne vide pour `storageConfigurationArn`, indiquant que l’enregistrement a été désactivé.

## Instructions de console pour la création d’une étape IVS
<a name="getting-started-create-stage-console"></a>

1. Ouvrez la [console Amazon IVS](https://console.aws.amazon.com/ivs).

   (Vous pouvez également accéder à la console Amazon IVS via la [console de gestion AWS](https://console.aws.amazon.com/).)

1. Dans le panneau de navigation de gauche, sélectionnez **Étapes**, puis sélectionnez **Créer une étape**. La fenêtre **Créer une étape** s’affiche.  
![\[Utilisez la fenêtre Créer une étape pour créer une nouvelle étape et un jeton de participant correspondant.\]](http://docs.aws.amazon.com/fr_fr/ivs/latest/RealTimeUserGuide/images/Create_Stage_Console_IPR.png)

1. Saisissez éventuellement un **Nom d’étape**.

1. Si vous souhaitez activer l'enregistrement des participants individuels, suivez les étapes décrites dans [Configurer l'enregistrement automatique des participants individuels sur Amazon S3 (facultatif)](#getting-started-create-stage-ipr) ci-dessous.

1. Sélectionnez **Créer une étape** pour créer l’étape. La page de détails de l’étape s’affiche pour la nouvelle étape.

### Configurer l'enregistrement automatique des participants individuels sur Amazon S3 (facultatif)
<a name="getting-started-create-stage-ipr"></a>

Procédez comme suit pour activer l'enregistrement des participants individuels lors de la création d'une étape :

1. Sur la page **Créer une étape**, sous **Enregistrer les participants individuels**, activez **Activer l'enregistrement automatique**. Des champs supplémentaires s'affichent, pour choisir les **Types de médias enregistrés**, pour choisir une **Configuration de stockage** existante ou en créer une nouvelle et pour choisir d'enregistrer des miniatures à intervalles réguliers.  
![\[Utilisez la boîte de dialogue Enregistrer des participants individuels pour configurer l'enregistrement des participants individuels pour une étape.\]](http://docs.aws.amazon.com/fr_fr/ivs/latest/RealTimeUserGuide/images/Create_Stage_Console_enable_IPR.png)

1. Choisissez les types de médias à enregistrer.

1. Choisissez **Créer une configuration de stockage**. Une nouvelle fenêtre s'ouvre, avec des options permettant de créer un compartiment Amazon S3 et de le joindre à la nouvelle configuration d'enregistrement.  
![\[Utilisez la fenêtre Créer une configuration de stockage pour créer une nouvelle configuration de stockage por une étape.\]](http://docs.aws.amazon.com/fr_fr/ivs/latest/RealTimeUserGuide/images/Create_Storage_Configuration_IPR.png)

1. Renseignez les champs :

   1. Saisissez éventuellement un **Nom de configuration de stockage**.

   1. Entrez un **nom de compartiment**.

1. Sélectionnez **Créer une configuration de stockage** pour créer une nouvelle ressource de configuration de stockage avec un ARN unique. La création d’une configuration d’enregistrement ne prend que quelques secondes, mais cela peut aller jusqu’à 20 secondes. Une fois la configuration de stockage créée, vous revenez à la fenêtre **Créer une étape**. Là, la zone **Enregistrer les participants individuels** affiche votre nouvelle **Configuration de stockage** et le compartiment S3 (**stockage**) que vous avez créé.  
![\[Pour créer une étape à l’aide de la console IVS : nouvelle configuration de stockage créée.\]](http://docs.aws.amazon.com/fr_fr/ivs/latest/RealTimeUserGuide/images/Create_Stage_Console_Storage_Configuration.png)

1. Vous pouvez éventuellement activer des options autres que celles par défaut, telles que les réplicas des enregistrements des participants, la fusion des enregistrements des participants individuels et l’enregistrement des miniatures.  
![\[Créer une étape à l'aide de la console IVS : activez les options avancées telles que l'enregistrement des miniatures et l'assemblage IPR.\]](http://docs.aws.amazon.com/fr_fr/ivs/latest/RealTimeUserGuide/images/Create_Stage_Console_IPR_Stitching.png)

## Instructions de la CLI pour la création d’une étape IVS
<a name="getting-started-create-stage-cli"></a>

Pour installer la CLI AWS, consultez la section [Installation ou mise à jour de la dernière version de la CLI AWS](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html).

Vous pouvez désormais utiliser l'interface de ligne de commande pour créer et gérer des ressources en suivant l'une des deux procédures ci-dessous, selon que vous souhaitez créer une étape avec ou sans enregistrement des participants individuels activé.

### Créer une étape sans enregistrement des participants individuels
<a name="getting-started-create-stage-cli-without-ipr"></a>

L’API d’étape se trouve dans l’espace de noms ivs-realtime. Par exemple, pour créer une étape :

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

La réponse est :

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

### Créer une étape avec enregistrement des participants individuels
<a name="getting-started-create-stage-cli-with-ipr"></a>

Pour créer une étape avec enregistrement des participants individuels activé :

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

Vous pouvez également transmettre le paramètre `thumbnailConfiguration` pour définir manuellement le mode de stockage des miniatures et l’intervalle en secondes entre les miniatures :

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

Vous pouvez également transmettre le paramètre `recordingReconnectWindowSeconds` pour activer la fusion des enregistrements fragmentés de participants individuels :

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

La réponse est :

```
{
   "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"
   }
}
```

# Étape 3 :Distribuer des jetons de participants
<a name="getting-started-distribute-tokens"></a>

Maintenant que vous avez une scène, vous devez créer des jetons et les distribuer aux participants pour leur permettre de rejoindre la scène et de commencer à envoyer et recevoir des vidéos. Il existe deux méthodes pour générer des jetons :
+ [Créer](#getting-started-distribute-tokens-self-signed) des jetons à l’aide d’une paire de clés.
+ [Créer des jetons à l’aide de l’API de diffusion en temps réel IVS](#getting-started-distribute-tokens-api).

Les deux méthodes sont décrites ci-dessous.

## Création de jetons à l’aide d’une paire de clés
<a name="getting-started-distribute-tokens-self-signed"></a>

Vous pouvez créer des jetons sur votre application serveur et les distribuer aux participants pour qu’ils rejoignent une scène. Vous devez générer une paire de clés publique/privée ECDSA pour signer les jetons JWT et importer la clé publique dans IVS. IVS pourra alors vérifier les jetons lors de l’accès à la scène. 

IVS ne propose pas d’expiration des clés. En cas de compromission de votre clé privée, vous devrez supprimer l’ancienne clé publique.

### Création d’une nouvelle paire de clés
<a name="getting-started-distribute-tokens-self-signed-create-key-pair"></a>

Il existe différentes méthodes pour créer une paire de clés. Ci-dessous, nous en donnons deux exemples.

Pour créer une paire de clés dans la console, procédez comme suit :

1. [Ouvrez la console Amazon IVS](https://console.aws.amazon.com/ivs). Choisissez la région de votre scène si ce n’est pas déjà fait.

1. Dans le menu de navigation de gauche, sélectionnez **Diffusion en temps réel > Clés publiques**.

1. Choisissez **Créer une clé publique**. Une boîte de dialogue **Créer une clé publique** s’affiche.

1. Suivez les instructions et sélectionnez **Create** (Créer).

1. Amazon IVS génère une nouvelle paire de clés. La clé publique est importée comme ressource et la clé privée est immédiatement téléchargeable. La clé publique peut être téléchargée ultérieurement si nécessaire.

   Amazon IVS génère la clé côté client et ne stocke pas la clé privée. ***Assurez-vous d'enregistrer la clé ; vous ne pourrez pas la récupérer ultérieurement.***

Pour créer une paire de clés P384 EC avec OpenSSL (vous devrez peut-être d'abord installer [OpenSSL](https://www.openssl.org/source/)), procédez comme suit. Ce processus vous permet d'accéder à la fois aux clés privées et publiques. Vous n'avez besoin de la clé publique que si vous souhaitez tester la vérification de vos jetons.

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

Importez à présent votre nouvelle clé publique à l'aide des instructions suivantes.

### Importation de la clé publique
<a name="getting-started-distribute-tokens-import-public-key"></a>

Une fois la paire de clés créée, vous pouvez importer la clé publique dans IVS. Notre système n'a pas besoin de la clé privée, mais vous l'utilisez pour signer des jetons.

Pour importer une clé publique existante avec la console :

1. [Ouvrez la console Amazon IVS](https://console.aws.amazon.com/ivs). Choisissez la région de votre scène si ce n’est pas déjà fait.

1. Dans le menu de navigation de gauche, sélectionnez **Diffusion en temps réel > Clés publiques**.

1. Choisissez **Importer**. Une boîte de dialogue **Importer une clé publique** s’affiche.

1. Suivez les instructions et sélectionnez **Import** (Importer).

1. Amazon IVS importe votre clé publique et génère une ressource clé publique.

Pour importer une clé publique existante avec la CLI :

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

Vous pouvez omettre `--region <aws-region>` si la région se trouve dans votre fichier de configuration AWS local.

Voici un exemple de réponse :

```
{
    "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": {}
    }
}
```

### Requête d'API
<a name="getting-started-distribute-tokens-create-api"></a>

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

### Création et signature du jeton
<a name="getting-started-distribute-tokens-self-signed-generate-sign"></a>

Pour plus d’informations sur l’utilisation des JWT et des bibliothèques prises en charge pour la signature de jetons, consultez la page [jwt.io](https://jwt.io/). Sur l'interface jwt.io, vous devez saisir votre clé privée pour signer les jetons. La clé publique n'est nécessaire que si vous souhaitez vérifier les jetons.

Tous les JWT présentent trois champs : header (en-tête), payload (charge utile) et signature.

Les schémas JSON pour l’en-tête et les données utiles du jeton JWT sont décrits ci-dessous. Vous pouvez également copier un modèle JSON depuis la console IVS. Pour obtenir l’en-tête et les données utiles JSON depuis la console IVS :

1. [Ouvrez la console Amazon IVS](https://console.aws.amazon.com/ivs). Choisissez la région de votre scène si ce n’est pas déjà fait.

1. Dans le menu de navigation à gauche, choisissez **Diffusion en temps réel > Scènes**.

1. Sélectionnez la scène que vous souhaitez utiliser. Sélectionnez **Voir les détails**.

1. Dans la section **Jetons de participant**, déroulez le menu à côté de **Créer un jeton**.

1. Sélectionnez **Créer l’en-tête et les données utiles du jeton**.

1. Remplissez le formulaire et copiez l’en-tête et les données utiles du jeton JWT affichés en bas de la fenêtre.

#### Schéma du jeton : en-tête
<a name="getting-started-distribute-tokens-self-signed-generate-sign-header"></a>

header spécifie :
+ `alg` est l'algorithme de signature. Il s’agit d’ES384, un algorithme de signature ECDSA qui utilise l’algorithme de hachage SHA-384.
+ `typ` est le type de jeton, JWT.
+ `kid` est l’ARN de la clé publique utilisée pour signer le jeton. Il doit s’agir du même ARN que celui renvoyé par la requête 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”
}
```

#### Schéma du jeton : données utiles
<a name="getting-started-distribute-tokens-self-signed-generate-sign-payload"></a>

Les données utiles contiennent des données spécifiques à IVS. Tous les champs sauf `user_id` sont obligatoires.
+ `RegisteredClaims` dans la spécification JWT sont des revendications réservées qui doivent être fournies pour que le jeton de scène soit valide : 
  + `exp` (heure d’expiration) est un horodatage Unix UTC indiquant le moment où le jeton expire. (Le horodatage Unix est une valeur numérique représentant le nombre de secondes entre le 1970-01-01T00:00:00Z UTC et la date/heure UTC spécifiée, sans tenir compte des secondes intercalaires.) Le jeton est validé lorsque le participant rejoint une scène. IVS fournit des jetons avec un TTL par défaut de 12 heures, ce que nous recommandons ; ce délai peut être prolongé jusqu’à un maximum de 14 jours à compter de la date d’émission (iat). Cette valeur doit être un nombre entier.
  + `iat` (émis à l’heure) est un horodatage Unix UTC du moment où le jeton JWT a été émis. (Voir la note pour `exp` concernant les horodatages Unix.) Cette valeur doit être un nombre entier.
  + `jti` (ID JWT) est l’identifiant de participant utilisé pour le suivi et pour faire référence à un participant auquel le jeton est accordé. Chaque jeton doit avoir un identifiant de participant unique. Il doit s’agir d’une chaîne de caractères sensible à la casse, d’une longueur maximale de 64 caractères, contenant uniquement des caractères alphanumériques, des tirets (-) et des underscores (\$1). Aucun autre caractère spécial n’est autorisé. 
+ `user_id` est un nom facultatif attribué par le client pour aider à identifier le jeton ; il peut être utilisé pour associer un participant à un utilisateur dans les propres systèmes du client. Cela doit correspondre au champ `userId` dans la requête API [CreateParticipantToken](https://docs.aws.amazon.com/ivs/latest/RealTimeAPIReference/API_CreateParticipantToken.html). Il peut s’agir de n’importe quel texte encodé en UTF-8 et d’une chaîne de 128 caractères maximum. *Ce champ est visible par tous les participants à la scène et ne doit pas être utilisé pour des informations d’identification personnelle, confidentielles ou sensibles.*
+ `resource` est l’ARN de la scène ; par exemple `arn:aws:ivs:us-east-1:123456789012:stage/oRmLNwuCeMlQ`.
+ `topic` est l’ID de la scène, qui peut être extrait de l’ARN de la scène. Par exemple, si l’ARN de la scène est `arn:aws:ivs:us-east-1:123456789012:stage/oRmLNwuCeMlQ`, l’ID de la scène est `oRmLNwuCeMlQ`.
+ `events_url` doit être le point de terminaison des événements renvoyé par l’opération CreateStage ou GetStage. Il est recommandé de mettre en cache cette valeur lors de la création de la scène, pour une durée maximale de 14 jours. Un exemple de valeur est `wss://global.events.live-video.net`.
+ `whip_url` doit être le point de terminaison WHIP renvoyé par l’opération CreateStage ou GetStage. Il est recommandé de mettre en cache cette valeur lors de la création de la scène, pour une durée maximale de 14 jours. Un exemple de valeur est `https://453fdfd2ad24df.global-bm.whip.live-video.net`.
+ `capabilities` spécifie les capacités du jeton ; les valeurs valides sont `allow_publish` et `allow_subscribe`. Pour les jetons réservés aux abonnés, définissez uniquement `allow_subscribe` sur `true`.
+ `attributes` est un champ facultatif dans lequel vous pouvez spécifier des attributs fournis par l’application pour les encoder dans le jeton et les attacher à une scène. Les clés et valeurs de la carte peuvent contenir du texte encodé en UTF-8. La longueur totale maximale de ce champ est de 1 Ko. *Ce champ est visible par tous les participants à la scène et ne doit pas être utilisé pour des informations d’identification personnelle, confidentielles ou sensibles.*
+ `version` doit avoir pour valeur `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"
  }
  ```

#### Schéma du jeton : signature
<a name="getting-started-distribute-tokens-self-signed-generate-sign-signature"></a>

Pour créer la signature, utilisez la clé privée avec l’algorithme spécifié dans l’en-tête (ES384) pour signer l’en-tête encodé et la charge utile encodée.

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

#### Instructions
<a name="getting-started-distribute-tokens-self-signed-generate-sign-instructions"></a>

1. Générez la signature du jeton à l’aide d’un algorithme de signature ES384 et une clé privée associée à la clé publique fournie à IVS.

1. Assemblez le jeton.

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

## Création de jetons à l’aide de l’API de diffusion en temps réel IVS
<a name="getting-started-distribute-tokens-api"></a>

![\[Distribuer les jetons aux participants : flux de travail relatif aux jetons de scène\]](http://docs.aws.amazon.com/fr_fr/ivs/latest/RealTimeUserGuide/images/Distribute_Participant_Tokens.png)


Comme indiqué ci-dessus, une application client demande un jeton à votre application côté serveur, et l’application côté serveur appelle CreateParticipantToken à l’aide d’un SDK AWS ou de requêtes signées SigV4. Étant donné que les informations d’identification AWS sont utilisées pour appeler l’API, le jeton doit être généré dans une application sécurisée côté serveur, et non dans l’application côté client.

Lors de la création d’un jeton de participant, vous pouvez éventuellement spécifier des attributs et/ou des fonctionnalités :
+ Vous pouvez spécifier des attributs fournis par l’application pour les encoder dans le jeton et les attacher à une scène. Les clés et valeurs de la carte peuvent contenir du texte encodé en UTF-8. La longueur totale maximale de ce champ est de 1 Ko. *Ce champ est visible par tous les participants à la scène et ne doit pas être utilisé pour des informations d’identification personnelle, confidentielles ou sensibles.*
+ Vous pouvez spécifier les fonctionnalités activées par le jeton. La valeur par défaut est `PUBLISH` et `SUBSCRIBE`, qui permet au participant d’envoyer et de recevoir des fichiers audio et vidéo, mais vous pouvez émettre des jetons dotés d’un sous-ensemble de fonctionnalités. Par exemple, vous pouvez émettre un jeton n’ayant que la capacité `SUBSCRIBE` pour les modérateurs. Dans ce cas, les modérateurs peuvent voir les participants qui envoient des vidéos, mais ne peuvent pas envoyer leurs propres vidéos.

Pour plus de détails, voir [CreateParticipantToken](https://docs.aws.amazon.com//ivs/latest/RealTimeAPIReference/API_CreateParticipantToken.html).

Vous pouvez créer des jetons participants via la console ou la CLI à des fins de test et de développement, mais il est fort probable que vous souhaitiez les créer à l’aide du SDK AWS dans votre environnement de production.

Vous aurez besoin d’un moyen de distribuer des jetons depuis votre serveur à chaque client (via une requête d’API par exemple). Nous ne proposons pas cette fonctionnalité. Pour ce guide, vous pouvez simplement copier et coller les jetons dans le code client en suivant les étapes suivantes.

**Important** : traitez les jetons comme des éléments opaques, c’est-à-dire ne créez pas de fonctionnalités basées sur le contenu des jetons. Le format des jetons pourrait changer à l’avenir.

### Instructions de la console
<a name="getting-started-distribute-tokens-console"></a>

1. Accédez à la scène que vous avez créée à l’étape précédente.

1. Sélectionnez **Créer un jeton**. La fenêtre **Créer un jeton** s’affiche.

1. Saisissez un ID utilisateur à associer au jeton. Il peut s’agir de n’importe quel texte codé en UTF-8. 

1. Sélectionnez **Créer**.

1. Copiez le jeton. *Important : veillez à enregistrer le jeton. IVS ne le stocke pas et vous ne pourrez pas le récupérer ultérieurement*.

### Instructions de la CLI
<a name="getting-started-distribute-tokens-cli"></a>

La création d’un jeton avec l’AWS CLI nécessite que vous téléchargiez et configuriez d’abord la CLI sur votre machine. Pour plus de détails, consultez le [‬Guide de l’utilisateur de l’Interface de ligne de commande AWS‭](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-welcome.html). Notez que la génération de jetons avec l’AWS CLI est utile à des fins de test, mais pour une utilisation en production, nous vous recommandons de générer des jetons côté serveur avec le SDK AWS (voir les instructions ci-dessus).

1. Exécutez la commande `create-participant-token` avec l’ARN de la scène. Incluez l’une ou l’ensemble des fonctions suivantes : `"PUBLISH"` ou `"SUBSCRIBE"`.

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

1. Cela renvoie un jeton de participant :

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

1. Enregistrez ce jeton. Vous en aurez besoin pour rejoindre la scène et pour envoyer et recevoir des vidéos.

### Instructions du SDK AWS
<a name="getting-started-distribute-tokens-sdk"></a>

Vous pouvez utiliser le SDK AWS pour créer des jetons. Vous trouverez ci-dessous les instructions concernant le SDK AWS utilisant JavaScript. 

**Important :** ce code doit être exécuté côté serveur et sa sortie doit être transmise au client.

**Prérequis :** pour utiliser l’exemple de code ci-dessous, vous devez installer le package aws-sdk/client-ivs-realtime. Pour plus d’informations, consultez la section [Mise en route avec le SDK AWS pour 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);
```

# Étape 4 :Intégrer le SDK de diffusion IVS
<a name="getting-started-broadcast-sdk"></a>

IVS fournit un SDK de diffusion pour le Web, Android et iOS que vous pouvez intégrer à votre application. Le SDK de diffusion est utilisé à la fois pour envoyer et recevoir des vidéos. Si vous avez [configuré l’ingestion RTMP pour votre scène](https://docs.aws.amazon.com/ivs/latest/RealTimeUserGuide/rt-stream-ingest.html), vous pouvez utiliser n’importe quel encodeur capable de diffuser vers un point de terminaison RTMP (par exemple, OBS ou ffmpeg).

Dans cette section, nous écrivons une application simple qui permet à deux participants ou plus d’interagir en temps réel. Les étapes ci-dessous vous guident dans la création d’une application appelée BasicRealTime. Le code complet de l’application se trouve sur CodePen et 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>

### Fichiers de configuration
<a name="getting-started-broadcast-sdk-web-setup"></a>

Pour commencer, configurez vos fichiers en créant un dossier et un fichier HTML et JS initial :

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

Vous pouvez installer le SDK de diffusion à l’aide d’une balise de script ou de npm. Notre exemple utilise la balise script pour des raisons de simplicité, mais est facile à modifier si vous choisissez d’utiliser npm ultérieurement.

### Utilisation d’une balise de script
<a name="getting-started-broadcast-sdk-web-script"></a>

Le kit SDK de diffusion Web est distribué sous forme de bibliothèque JavaScript et peut être consulté à l'adresse [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).

Lorsqu’elle est chargée via une balise `<script>`, la bibliothèque expose une variable globale dans la portée de la fenêtre nommée `IVSBroadcastClient`.

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

Pour installer le package npm :

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

Vous pouvez désormais accéder à l’objet IVSBroadcastClient :

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

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

### Création du projet Android
<a name="getting-started-broadcast-sdk-android-project"></a>

1. Dans Android Studio, créez un **New Project**.

1. Choisissez **Empty Views Activity**.

   Remarque : dans certaines anciennes versions d’Android Studio, l’activité basée sur les vues est appelée **Empty Activity**. Si votre fenêtre Android Studio affiche **Empty Activity** et *ne montre pas* **Empty Views Activity**, sélectionnez **Empty Activity**. Sinon, ne sélectionnez pas **Empty Activity**, car nous utiliserons les API View (et non Jetpack Compose).

1. Donnez un **Nom** à votre projet, puis sélectionnez **Finish**.

### Installation du SDK Broadcast
<a name="getting-started-broadcast-sdk-android-install"></a>

Pour ajouter la bibliothèque de diffusion Android Amazon IVS à votre environnement de développement Android, ajoutez la bibliothèque au fichier `build.gradle` de votre module, comme indiqué ici (pour la dernière version du SDK de diffusion Amazon IVS). Dans les nouveaux projets, le référentiel `mavenCentral` est peut-être déjà inclus dans votre fichier `settings.gradle`, si c’est le cas, vous pouvez omettre le bloc `repositories`. Pour notre exemple, nous devrons également activer la liaison de données dans le bloc `android`.

```
android {
    dataBinding.enabled true
}

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

Vous pouvez également installer le kit SDK manuellement, en téléchargeant la dernière version à partir du lien suivant :

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

### Création du projet iOS
<a name="getting-started-broadcast-sdk-ios-project"></a>

1. Crée un projet Xcode.

1. Pour **Platform**, sélectionnez **iOS**.

1. Pour **Application**, sélectionnez **App**.

1. Saisissez le **Product Name** de votre application, puis sélectionnez **Next**.

1. Choisissez (naviguez jusqu’à) un répertoire dans lequel enregistrer le projet, puis sélectionnez **Create**.

Ensuite, vous devez importer le SDK. Pour obtenir des instructions, consultez [Installer la bibliothèque](broadcast-ios-getting-started.md#broadcast-ios-install) dans le *guide du kit SDK de diffusion iOS*.

### Configuration des autorisations
<a name="getting-started-broadcast-sdk-ios-config"></a>

Vous devez mettre à jour votre projet `Info.plist` pour ajouter deux nouvelles entrées pour `NSCameraUsageDescription` et `NSMicrophoneUsageDescription`. Pour les valeurs, expliquez à l’utilisateur pourquoi votre application demande l’accès à la caméra et au microphone.

![\[Configurez des autorisations iOS.\]](http://docs.aws.amazon.com/fr_fr/ivs/latest/RealTimeUserGuide/images/iOS_Configure.png)


# Étape 5 :Publier une vidéo et s’y abonner
<a name="getting-started-pub-sub"></a>

Vous pouvez publier/vous abonner (en temps réel) à IVS avec :
+ Les [kits SDK de diffusion IVS](https://docs.aws.amazon.com//ivs/latest/LowLatencyUserGuide/getting-started-set-up-streaming.html#broadcast-sdk) natifs, qui prennent en charge WebRTC et RTMPS. Nous le recommandons, en particulier pour des scénarios de production. Consultez les détails ci-dessous pour [Web](getting-started-pub-sub-web.md), [Android](getting-started-pub-sub-android.md) et [iOS](getting-started-pub-sub-ios.md).
+ La console Amazon IVS : elle convient pour tester les flux. Reportez-vous à ci-dessous.
+ D’autres logiciels et encodeurs matériels de diffusion : vous pouvez utiliser tout encodeur prenant en charge les protocoles RTMP, RTMPS ou WHIP. Consultez [Ingestion de flux](rt-stream-ingest.md) pour plus d’informations.

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

1. Ouvrez la [console Amazon IVS](https://console.aws.amazon.com/ivs).

   (Vous pouvez également accéder à la console Amazon IVS via la [console de gestion AWS](https://console.aws.amazon.com/).)

1. Dans le panneau de navigation, sélectionnez **Scènes**. (Si le panneau de navigation est réduit, agrandissez-le en sélectionnant une icône en forme de hamburger.)

1. Sélectionnez la scène à laquelle vous souhaitez vous abonner ou publier pour accéder à la page de détails.

1. Pour vous abonner : si la scène compte un ou plusieurs diffuseurs de publication, vous pouvez vous y abonner en appuyant sur le bouton **S’abonner**, sous l’onglet **S’abonner**. (Les onglets se trouvent sous la section **Configuration générale**.)

1. Pour publier :

   1. Sélectionnez l’onglet **Publier**.

   1. Vous serez invité à autoriser la console IVS à accéder à votre caméra et à votre microphone ; **autorisez** ces autorisations.

   1. Au bas de l’onglet **Publier**, utilisez les listes déroulantes pour sélectionner les périphériques d’entrée pour le microphone et la caméra.

   1. Pour commencer à publier, sélectionnez **Commencer à publier**.

   1. Pour consulter le contenu que vous avez publié, retournez à l’onglet **S’abonner**.

   1. Pour arrêter la publication, accédez à l’onglet **Publier** et appuyez sur le bouton **Arrêter la publication** en bas de l’écran.

**Remarque** : s’abonner et publier consomment des ressources, et un tarif horaire sera appliqué pour le temps passé connecté à la scène. Pour en savoir plus, consultez la section [Diffusion en temps réel](https://aws.amazon.com/ivs/pricing/#Real-Time_Streaming) sur la page de tarification IVS.

# Publication et abonnement avec le SDK de diffusion Web IVS
<a name="getting-started-pub-sub-web"></a>

Cette section explique les étapes nécessaires à la publication et à l'abonnement à une étape à l'aide de votre application web.

## Créer un modèle de code HTML
<a name="getting-started-pub-sub-web-html"></a>

Commençons par créer le modèle de code HTML et importons la bibliothèque sous forme de balise 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>
```

## Accepter la saisie du jeton et ajouter des boutons Rejoindre/Quitter
<a name="getting-started-pub-sub-web-join"></a>

Ici, nous remplissons le corps avec nos contrôles d’entrée. Ceux-ci prennent comme entrée le jeton, et ils configurent les boutons **Rejoindre** et **Quitter**. Généralement, les applications demandent le jeton à partir de l’API de votre application, mais dans cet exemple, vous allez copier et coller le jeton dans l’entrée du jeton.

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

## Ajouter des éléments de conteneur multimédia
<a name="getting-started-pub-sub-web-media"></a>

Ces éléments hébergeront les médias pour nos participants locaux et distants. Nous ajoutons une balise de script pour charger la logique de notre application définie dans `app.js`.

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

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

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

Ceci termine la page HTML et vous devriez le voir lors du chargement du fichier `index.html` dans un navigateur :

![\[Afficher le streaming en temps réel dans un navigateur : configuration HTML terminée.\]](http://docs.aws.amazon.com/fr_fr/ivs/latest/RealTimeUserGuide/images/RT_Browser_View.png)


## Crée le fichier app.js
<a name="getting-started-pub-sub-web-appjs"></a>

Passons à la définition du contenu de notre fichier `app.js`. Commencez par importer toutes les propriétés requises depuis le répertoire global du SDK :

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

## Créer les variables d’application
<a name="getting-started-pub-sub-web-vars"></a>

Définissez des variables pour contenir les références aux éléments HTML de nos boutons **Rejoindre** et **Quitter** et pour stocker l’état de l’application :

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

## Créer joinStage 1 : définition de la fonction et validation de la saisie
<a name="getting-started-pub-sub-web-joinstage1"></a>

La fonction `joinStage` prend le jeton d’entrée, crée une connexion à la scène et commence à publier la vidéo et l’audio extraits de `getUserMedia`.

Pour commencer, nous définissons la fonction et validons l’état et la saisie du jeton. Nous développerons cette fonction dans les prochaines sections.

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

## Créer joinStage 2 : publier le contenu multimédia
<a name="getting-started-pub-sub-web-joinstage2"></a>

Voici les médias qui seront publiés vers la scène :

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

## Créer joinStage 3 : définir la stratégie de la scène et créer la scène
<a name="getting-started-pub-sub-web-joinstage3"></a>

Cette stratégie de scène est au cœur de la logique de décision utilisée par le SDK pour décider du contenu à publier et des participants auxquels s’abonner. Pour plus d’informations sur l’objectif de la fonction, consultez la section [Stratégie](web-publish-subscribe.md#web-publish-subscribe-concepts-strategy).

Cette stratégie est simple. Après avoir rejoint la scène, publiez les flux que vous venez de récupérer et abonnez-vous au son et à la vidéo de chaque participant distant :

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

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

## Créer joinStage 4 : gérer les événements de la scène et le rendu multimédia
<a name="getting-started-pub-sub-web-joinstage4"></a>

Les scènes génèrent de nombreux événements. Nous devrons écouter les événements `STAGE_PARTICIPANT_STREAMS_ADDED` et `STAGE_PARTICIPANT_LEFT` pour afficher et supprimer du contenu multimédia vers et depuis la page. Un ensemble plus complet d’événements est répertorié dans [Événements](web-publish-subscribe.md#web-publish-subscribe-concepts-events).

Notez que nous créons ici quatre fonctions d’assistance pour nous aider à gérer les éléments DOM nécessaires : `setupParticipant`, `teardownParticipant`, `createVideoEl` et `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;
}
```

## Créer joinStage 5 : rejoindre la scène
<a name="getting-started-pub-sub-web-joinstage5"></a>

Complétons notre fonction `joinStage` en rejoignant enfin la scène \$1

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

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

Définissez la fonction `leaveStage` qui sera invoquée par le bouton « Quitter ».

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

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

## Initialiser les gestionnaires d’entrées et d’événements
<a name="getting-started-pub-sub-web-handlers"></a>

Nous allons ajouter une dernière fonction à notre fichier `app.js`. Cette fonction est invoquée dès le chargement de la page et établit des gestionnaires d’événements pour rejoindre et quitter la scène.

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

## Exécutez l’application et fournissez un jeton
<a name="getting-started-pub-sub-run-app"></a>

À ce stade, vous pouvez partager la page Web localement ou avec d’autres personnes, [ouvrir la page](#getting-started-pub-sub-web-media), saisir un jeton de participant et rejoindre l’étape.

## Quelle est la prochaine étape ?
<a name="getting-started-pub-sub-next"></a>

Pour obtenir des exemples plus détaillés impliquant npm, React, etc., consultez le [SDK de diffusion IVS : guide pour le Web (Streaming en temps réel)](broadcast-web.md).

# Publication et abonnement avec le SDK de diffusion Android IVS
<a name="getting-started-pub-sub-android"></a>

Cette section explique les étapes nécessaires à la publication et à l'abonnement à une étape à l'aide de votre application Android.

## Créer les vues
<a name="getting-started-pub-sub-android-views"></a>

Nous commençons par créer une disposition simple pour notre application à l’aide du fichier `activity_main.xml` créé automatiquement. La disposition contient un `EditText` pour ajouter un jeton, un `Button` Rejoindre, un `TextView` pour afficher l’état de la scène, et une `CheckBox` pour commuter l’état de publication.

![\[Configurez la disposition de publication pour votre application Android.\]](http://docs.aws.amazon.com/fr_fr/ivs/latest/RealTimeUserGuide/images/Publish_Android_1.png)


Voici le code XML qui sous-tend la vue :

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

Nous avons référencé quelques ID de chaînes ici, nous allons donc créer notre fichier `strings.xml` entier maintenant :

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

Lions ces vues dans le XML à notre `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)
}
```

Nous allons maintenant créer une vue d’élément pour notre `RecyclerView`. Pour ce faire, effectuez un clic droit sur votre répertoire `res/layout` et sélectionnez **New > Layout Resource File**. Nommez ce nouveau fichier `item_stage_participant.xml`.

![\[Créez une vue d’élément pour votre application Android RecyclerView.\]](http://docs.aws.amazon.com/fr_fr/ivs/latest/RealTimeUserGuide/images/Publish_Android_2.png)


La disposition de cet élément est simple : elle contient une vue permettant de rendre le flux vidéo d’un participant et une liste d’étiquettes permettant d’afficher des informations sur le participant :

![\[Créez une vue d’élément pour votre application Android RecyclerView - labels.\]](http://docs.aws.amazon.com/fr_fr/ivs/latest/RealTimeUserGuide/images/Publish_Android_3.png)


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

Ce fichier XML renseigne une classe que nous n’avons pas encore créée : `ParticipantItem`. Étant donné que le XML inclut l’espace de nommage complet, veillez à mettre à jour ce fichier XML vers votre espace de nommage. Créons cette classe et configurons les vues, mais sinon, laissez-la vide pour le moment.

Créez une nouvelle 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)
    }
}
```

## Autorisations
<a name="getting-started-pub-sub-android-perms"></a>

Pour utiliser la caméra et le microphone, vous devez demander des autorisations à l’utilisateur. Pour cela, nous suivons un flux d’autorisations standard :

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

## État de l’application
<a name="getting-started-pub-sub-android-app-state"></a>

Notre application permet de suivre les participants au niveau local dans un fichier `MainViewModel.kt` et l’état sera communiqué au `MainActivity` en utilisant le [StateFlow](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-state-flow/) Kotlin.

Créez une 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 {

}
```

Dans `MainActivity.kt`, nous gérons notre modèle de vue :

```
import androidx.activity.viewModels

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

Pour utiliser `AndroidViewModel` et ces extensions Kotlin `ViewModel`, vous devrez ajouter ce qui suit au fichier `build.gradle` de votre module :

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

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

Nous allons créer une sous-classe `RecyclerView.Adapter` pour suivre nos participants et mettre à jour notre `RecyclerView` sur les événements de la scène. Mais d’abord, nous avons besoin d’une classe qui représente un participant. Créez une 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)
            }
        }
}
```

Nous utiliserons cette classe dans classe `ParticipantAdapter` que nous allons créer ensuite. Nous commençons par définir la classe et créer une variable pour suivre les participants :

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

Nous devons également définir notre `RecyclerView.ViewHolder` avant d’implémenter le reste des surcharges :

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

À partir de là, nous pouvons implémenter les surcharges `RecyclerView.Adapter` standard :

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

Enfin, nous ajoutons de nouvelles méthodes que nous appellerons à partir de notre `MainViewModel` lorsque des modifications seront apportées aux participants. Ces méthodes sont des opérations CRUD standard sur l’adaptateur.

```
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])
    }
}
```

En revenant au `MainViewModel`, nous devons créer et conserver une référence à cet adaptateur :

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

## État de l’étape
<a name="getting-started-pub-sub-android-views-stage-state"></a>

Nous devons également suivre certains états de la scène au sein du `MainViewModel`. Définissons maintenant ces propriétés :

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

Pour voir votre propre aperçu avant de rejoindre une scène, nous créons immédiatement un participant local :

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

Nous voulons nous assurer de nettoyer ces ressources lorsque notre `ViewModel` est nettoyé. Nous surchargeons `onCleared()` immédiatement, afin de ne pas oublier de nettoyer ces ressources.

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

Maintenant, nous peuplons notre propriété de `streams` locaux dès que les autorisations sont accordées, en implémentant la méthode `permissionsGranted` que nous avons appelée plus tôt :

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

## Implémentation du SDK Scène
<a name="getting-started-pub-sub-android-stage-sdk"></a>

Trois [concepts](android-publish-subscribe.md#android-publish-subscribe-concepts) de base sous-tendent la fonctionnalité temps réel : scène, stratégie et moteur de rendu. L’objectif de la conception consiste à minimiser la quantité de logique côté client nécessaire à la création d’un produit fonctionnel.

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

L’implémentation de notre `Stage.Strategy` est simple :

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

Pour résumer, nous publions sur la base de notre état `publishEnabled`, et si nous publions, nous publierons les flux que nous avons collectés précédemment. Enfin, pour cet exemple, nous nous abonnons toujours aux autres participants, pour recevoir à la fois leur audio et leur vidéo.

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

L’implémentation de `StageRenderer` est également assez simple, bien que compte tenu du nombre de fonctions, elle contienne un peu plus de code. L’approche générale de ce moteur de rendu consiste à mettre à jour notre `ParticipantAdapter` lorsque le SDK nous informe d’une modification apportée à un participant. Dans certains cas, nous traitons les participants locaux différemment, car nous avons décidé de les gérer nous-mêmes afin qu’ils puissent voir l’aperçu de leur caméra avant de rejoindre le groupe.

```
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) {}
}
```

## Implémentation d’un RecyclerView LayoutManager personnalisé
<a name="getting-started-pub-sub-android-layout"></a>

La répartition des différents nombres de participants peut s’avérer complexe. Vous souhaitez qu’ils occupent l’intégralité du cadre de la vue parent, mais vous ne souhaitez pas gérer la configuration de chaque participant de manière indépendante. Pour vous faciliter la tâche, nous allons procéder à l’implémentation d’un `RecyclerView.LayoutManager`.

Créez une autre classe, `StageLayoutManager`, qui étend `GridLayoutManager`. Cette classe est conçue pour calculer la disposition de chaque participant en fonction du nombre de participants dans une disposition en ligne/colonne basée sur le flux. Chaque ligne a la même hauteur que les autres, mais les colonnes peuvent avoir des largeurs différentes par ligne. Voir le commentaire de code au-dessus de la variable `layouts` pour une description de la façon de personnaliser ce comportement.

```
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 retour dans `MainActivity.kt`, nous devons définir l’adaptateur et le gestionnaire de disposition pour notre `RecyclerView` :

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

## Connexion des actions de l’interface utilisateur
<a name="getting-started-pub-sub-android-actions"></a>

Nous nous rapprochons ; il ne nous reste plus que quelques actions d’interface utilisateur à connecter.

Tout d’abord, nous avons notre `MainActivity` qui observe les changements de `StateFlow` depuis le `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)
        }
    }
}
```

Ensuite, nous ajoutons des auditeurs à notre bouton Rejoindre et à la case à cocher Publier :

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

Les deux fonctionnalités d’appel ci-dessus sont disponibles dans notre `MainViewModel`, que nous implémentons maintenant :

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

## Affichage des participants
<a name="getting-started-pub-sub-android-participants"></a>

Enfin, nous devons rendre les données que nous recevons du SDK sur l’élément participant que nous avons créé précédemment. La logique du `RecyclerView` est déjà terminée, il ne nous reste plus qu’à implémenter l’API `bind` dans `ParticipantItem`.

Nous allons commencer par ajouter une fonction vide, puis la parcourir étape par étape :

```
fun bind(participant: StageParticipant) {

}
```

Nous allons d’abord gérer l’état simplifié, l’identifiant du participant, l’état de publication et l’état d’abonnement. Pour ceux-ci, nous mettons simplement à jour nos `TextViews` directement :

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

Ensuite, nous mettrons à jour les états de coupure de l’audio et de la vidéo. Pour obtenir l’état de coupure, nous devons trouver `ImageDevice` et `AudioDevice` à partir du tableau de flux. Pour optimiser les performances, nous mémorisons les derniers identifiants des appareils connectés.

```
// 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"
}
```

Enfin, nous voulons afficher un aperçu du `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
```

Et nous affichons les statistiques audio du `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
```

# Publication et abonnement avec le SDK de diffusion iOS IVS
<a name="getting-started-pub-sub-ios"></a>

Cette section explique les étapes nécessaires à la publication et à l'abonnement à une étape à l'aide de votre application iOS.

## Créer les vues
<a name="getting-started-pub-sub-ios-views"></a>

Nous commençons par utiliser le fichier `ViewController.swift` créé automatiquement pour importer `AmazonIVSBroadcast`, puis ajouter quelques `@IBOutlets` à lier :

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

Nous créons maintenant ces vues et les relions dans `Main.storyboard`. Voici la structure de vue que nous allons utiliser :

![\[Utilisez Main.storyboard pour créer une vue iOS.\]](http://docs.aws.amazon.com/fr_fr/ivs/latest/RealTimeUserGuide/images/Publish_iOS_1.png)


Pour la configuration d’AutoLayout, nous devons personnaliser trois vues. La première vue est **Collection View Participants** (une `UICollectionView`). Liez **Leading**, **Trailing**, et **Bottom** à **Safe Area**. Liez également **Top** à **Controls Container**.

![\[Personnaliser la vue iOS Collection View Participants.\]](http://docs.aws.amazon.com/fr_fr/ivs/latest/RealTimeUserGuide/images/Publish_iOS_2.png)


La deuxième vue est **Controls Container**. Liez **Leading**, **Trailing**, et **Top** à **Safe Area** :

![\[Personnalisez la vue iOS Controls Container.\]](http://docs.aws.amazon.com/fr_fr/ivs/latest/RealTimeUserGuide/images/Publish_iOS_3.png)


La troisième et dernière vue est **Vertical Stack View**. Liez **Top**, **Leading**, **Trailing**, et **Bottom** à **Superview**. Pour le style, définissez l’espacement sur 8 au lieu de 0.

![\[Personnalisez la vue iOS Vertical Stack view.\]](http://docs.aws.amazon.com/fr_fr/ivs/latest/RealTimeUserGuide/images/Publish_iOS_4.png)


Les **UIStackViews** s’occupent de gérer la disposition des vues restantes. Pour les trois **UIStackViews**, utilisez **Fill** pour **Alignment** et **Distribution**.

![\[Personnalisez les vues iOS restantes avec UIStackViews.\]](http://docs.aws.amazon.com/fr_fr/ivs/latest/RealTimeUserGuide/images/Publish_iOS_5.png)


Enfin, relions ces vues à notre `ViewController`. De dessus, cartographiez les vues suivantes :
+ **Text Field Join** est relié à `textFieldToken`.
+ **Button Join** est relié à `buttonJoin`.
+ **Label State** est relié à `labelState`.
+ **Switch Publish** est relié à `switchPublish`.
+ **Collection View Participants** est relié à `collectionViewParticipants`.

Profitez également de cette période pour définir la `dataSource` de l’élément **Collection View Participants** sur le `ViewController` propriétaire :

![\[Définissez la dataSource de Collection View Participants pour l’application iOS.\]](http://docs.aws.amazon.com/fr_fr/ivs/latest/RealTimeUserGuide/images/Publish_iOS_6.png)


Nous créons maintenant la sous-classe `UICollectionViewCell` dans laquelle afficher les participants. Commencez par créer un fichier **Cocoa Touch Class** :

![\[Créez un UICollectionViewCell pour afficher les participants iOS en temps réel.\]](http://docs.aws.amazon.com/fr_fr/ivs/latest/RealTimeUserGuide/images/Publish_iOS_7.png)


Nommez-le `ParticipantUICollectionViewCell` et faites-en une sous-classe de `UICollectionViewCell` dans Swift. Nous recommençons dans le fichier Swift, en créant nos `@IBOutlets` à lier :

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

Dans le fichier XIB associé, créez cette hiérarchie de vues :

![\[Créez une hiérarchie de vues iOS dans le fichier XIB associé.\]](http://docs.aws.amazon.com/fr_fr/ivs/latest/RealTimeUserGuide/images/Publish_iOS_8.png)


Pour AutoLayout, nous allons à nouveau modifier trois vues. La première vue est **View Preview Container**. Liez **Trailing**, **Leading**, **Top** et **Bottom** à **Participant Collection View Cell**.

![\[Personnalisez la vue iOS View Preview Container.\]](http://docs.aws.amazon.com/fr_fr/ivs/latest/RealTimeUserGuide/images/Publish_iOS_9.png)


La deuxième vue est **View**. Liez **Leading** et **Top** à **Participant Collection View Cell** et définissez la valeur sur 4.

![\[Personnalisez la vue iOS View.\]](http://docs.aws.amazon.com/fr_fr/ivs/latest/RealTimeUserGuide/images/Publish_iOS_10.png)


La troisième vue est **Stack View**. Liez **Trailing**, **Leading**, **Top** et **Bottom** à **Superview** et définissez la valeur sur 4.

![\[Personnalisez la vue iOS Stack View.\]](http://docs.aws.amazon.com/fr_fr/ivs/latest/RealTimeUserGuide/images/Publish_iOS_11.png)


## Autorisations et minuteur d’inactivité
<a name="getting-started-pub-sub-ios-perms"></a>

De retour à notre `ViewController`, nous allons désactiver le minuteur d’inactivité du système pour empêcher l’appareil de se mettre en veille pendant l’utilisation de notre application :

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

Ensuite, nous demandons les autorisations de caméra et de microphone auprès du système :

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

## État de l’application
<a name="getting-started-pub-sub-ios-app-state"></a>

Nous devons configurer notre `collectionViewParticipants` avec le fichier de disposition que nous avons créé précédemment :

```
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")
}
```

Pour représenter chaque participant, nous créons une structure simple appelée `StageParticipant`. Cela peut être inclus dans le fichier `ViewController.swift`, ou un nouveau fichier peut être créé.

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

Pour suivre ces participants, nous en conservons une liste en tant que propriété privée dans notre `ViewController` :

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

Cette propriété sera utilisée pour alimenter notre `UICollectionViewDataSource` qui était lié depuis le storyboard plus tôt :

```
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'")
        }
    }

}
```

Pour voir votre propre aperçu avant de rejoindre une scène, nous créons immédiatement un participant local :

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

Cela se traduit par le rendu d’une cellule de participant immédiatement après l’exécution de l’application, représentant le participant local.

Les utilisateurs veulent pouvoir se voir eux-mêmes avant de rejoindre une scène. Nous allons donc implémenter la méthode `setupLocalUser()` qui est appelée précédemment par le code de gestion des autorisations. Nous stockons la référence de la caméra et du microphone en tant qu’objets `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)
}
```

Ici, nous avons trouvé la caméra et le microphone de l’appareil via le SDK et les avons stockés dans notre objet local `streams`, puis nous avons assigné le tableau de `streams` du premier participant (le participant local que nous avons créé plus tôt) à notre `streams`. Enfin, nous appelons `participantsChanged` avec un `index` défini sur 0 et un `changeType` défini sur `updated`. Cette fonction est une fonction d’aide à la mise à jour de notre `UICollectionView` avec de belles animations. Voici à quoi cela ressemble :

```
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)])
    }
}
```

Ne vous préoccupez pas encore de `cell.set` ; nous y reviendrons plus tard, mais c’est là que nous afficherons le contenu de la cellule en fonction du participant.

Le `ChangeType` est une simple énumération :

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

Enfin, nous voulons savoir si la scène est connectée. Nous utilisons un simple `bool` pour suivre cela, qui mettra automatiquement à jour notre interface utilisateur lorsqu’elle sera elle-même mise à jour.

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

## Implémenter le SDK Scène
<a name="getting-started-pub-sub-ios-stage-sdk"></a>

Trois [concepts](ios-publish-subscribe.md#ios-publish-subscribe-concepts) de base sous-tendent la fonctionnalité temps réel : scène, stratégie et moteur de rendu. L’objectif de la conception consiste à minimiser la quantité de logique côté client nécessaire à la création d’un produit fonctionnel.

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

L’implémentation de notre `IVSStageStrategy` est simple :

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

En résumé, nous ne publions que si l’interrupteur de publication est en position « activé », et si nous le faisons, seuls les flux que nous avons collectés précédemment sont publiés. Enfin, pour cet exemple, nous nous abonnons toujours aux autres participants, pour recevoir à la fois leur audio et leur vidéo.

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

L’implémentation de `IVSStageRenderer` est également assez simple, bien que compte tenu du nombre de fonctions, elle contienne un peu plus de code. L’approche générale de ce moteur de rendu consiste à mettre à jour notre tableau `participants` lorsque le SDK nous informe d’une modification apportée à un participant. Dans certains cas, nous traitons les participants locaux différemment, car nous avons décidé de les gérer nous-mêmes afin qu’ils puissent voir l’aperçu de leur caméra avant de rejoindre le groupe.

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

Ce code utilise une extension pour convertir l’état de connexion en texte convivial :

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

## Implémentation d’un UICollectionViewLayout personnalisé
<a name="getting-started-pub-sub-ios-layout"></a>

La répartition des différents nombres de participants peut s’avérer complexe. Vous souhaitez qu’ils occupent l’intégralité du cadre de la vue parent, mais vous ne souhaitez pas gérer la configuration de chaque participant de manière indépendante. Pour vous faciliter la tâche, nous allons procéder à l’implémentation d’un `UICollectionViewLayout`.

Créez un autre fichier, `ParticipantCollectionViewLayout.swift`, qui étend `UICollectionViewLayout`. Cette classe utilisera une autre classe appelée `StageLayoutCalculator`, que nous aborderons bientôt. La classe reçoit les valeurs de trame calculées pour chaque participant et génère ensuite les objets `UICollectionViewLayoutAttributes` nécessaires.

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

La classe `StageLayoutCalculator.swift` revêt une plus grande importance. Elle est conçue pour calculer les cadres de chaque participant en fonction du nombre de participants dans une disposition en ligne/colonne basée sur le flux. Chaque ligne a la même hauteur que les autres, mais les colonnes peuvent avoir des largeurs différentes par ligne. Voir le commentaire de code au-dessus de la variable `layouts` pour une description de la façon de personnaliser ce comportement.

```
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 retour à `Main.storyboard`, veillez à utiliser la classe que nous venons de créer pour définir la classe de disposition de `UICollectionView` :

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


## Connexion des actions de l’interface utilisateur
<a name="getting-started-pub-sub-ios-actions"></a>

Nous nous rapprochons, il nous reste quelques `IBActions` à créer.

Nous allons d’abord gérer le bouton Rejoindre. Il répond différemment en fonction de la valeur de `connectingOrConnected`. Lorsqu’il est déjà connecté, il a pour effet de quitter la scène. S’il est déconnecté, il lit le texte du jeton `UITextField` et crée un `IVSStage` avec ce texte. Ensuite, nous ajoutons notre `ViewController` en tant que `strategy`, `errorDelegate`, et moteur de rendu pour `IVSStage`, et enfin nous rejoignons la scène de manière asynchrone.

```
@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)")
        }
    }
}
```

L’autre action de l’interface utilisateur que nous devons connecter est le commutateur de publication :

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

## Affichage des participants
<a name="getting-started-pub-sub-ios-participants"></a>

Enfin, nous devons rendre les données que nous recevons du SDK sur la cellule de participant que nous avons créée précédemment. La logique du `UICollectionView` est déjà terminée, il ne nous reste plus qu’à implémenter l’API `set` dans `ParticipantCollectionViewCell.swift`.

Nous allons commencer par ajouter la fonction `empty`, puis nous l’étudierons étape par étape :

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

Nous gérons d’abord l’état simplifié, l’identifiant du participant, l’état de publication et l’état d’abonnement. Pour ceux-ci, nous mettons simplement à jour nos `UILabels` directement :

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

Les propriétés de texte des énumérations de publication et d’abonnement proviennent d’extensions locales :

```
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()
        }
    }
}
```

Ensuite, nous mettons à jour les états de coupure de l’audio et de la vidéo. Pour obtenir les états de coupure, nous devons trouver `IVSImageDevice` et `IVSAudioDevice` à partir du tableau de `streams`. Pour optimiser les performances, nous mémorisons les derniers identifiants des appareils connectés.

```
// 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)"
```

Enfin, nous voulons afficher un aperçu du `imageDevice` et affichez les statistiques audio du `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"
}
```

La dernière fonction que nous devons créer est `updatePreview()`, qui ajoute un aperçu du participant à notre vue :

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

Ce qui précède utilise une fonction d’assistance sur `UIView` pour faciliter l’intégration de sous-vues :

```
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),
        ])
    }
}
```