

# Kit SDK de diffusion IVS \$1 Diffusion en temps réel
<a name="broadcast"></a>

Le SDK de diffusion par streaming en temps réel Amazon Interactive Video Services (IVS) est destiné aux développeurs qui créent des applications avec Amazon IVS. Ce kit SDK est conçu pour tirer parti de l’architecture Amazon IVS et nous y apporterons continuellement des améliorations et de nouvelles fonctionnalités, en plus d’Amazon IVS. En tant que kit SDK de diffusion natif, il est conçu pour minimiser l’impact sur les performances de votre application et sur les périphériques avec lesquels vos utilisateurs accèdent à votre application.

Notez que le SDK de diffusion est utilisé à la fois pour envoyer et recevoir des vidéos ; c’est-à-dire que vous utilisez le même SDK pour les hôtes et les spectateurs. Aucun SDK de lecteur distinct n’est nécessaire.

Votre application peut tirer parti des fonctionnalités clés du kit SDK de diffusion Amazon IVS :
+ **Streaming haute qualité** : le kit SDK de diffusion prend en charge le streaming haute qualité. Capturez des vidéos à partir de votre caméra et encodez-les jusqu’à 720p.
+ **Ajustements automatiques du débit binaire** : les utilisateurs de smartphones sont mobiles, de sorte que les conditions de leur réseau peuvent changer tout au long de la diffusion. Le kit SDK de diffusion Amazon IVS ajuste automatiquement le débit binaire de la vidéo pour s’adapter aux conditions changeantes du réseau.
+ **Support des formats portrait et paysage** : quelle que soit la façon dont vos utilisateurs tiennent leurs appareils, l’image apparaît dans le bon sens et à la bonne échelle. Le kit SDK de diffusion prend en charge les tailles de canevas portrait et paysage. Il gère automatiquement les proportions lorsque les utilisateurs font pivoter leur appareil et quittent l’orientation configurée.
+ **Streaming sécurisé** : les diffusions de vos utilisateurs sont chiffrées à l’aide de TLS ; ils peuvent donc sécuriser leurs flux.
+ **Périphériques audio externes** : le kit SDK de diffusion Amazon IVS prend en charge les microphones externes à prise audio, USB et Bluetooth SCO.

## Exigences de la plateforme
<a name="broadcast-platform-requirements"></a>

### Plateformes natives
<a name="broadcast-native-platforms"></a>


| Plateforme | Versions prises en charge | 
| --- | --- | 
| Android |  9.0\$1 : notez que les clients peuvent créer avec les versions 6.0\$1 mais ne pourront pas utiliser la fonctionnalité de diffusion en temps réel.  | 
| iOS |  14\$1  | 

IVS prend en charge au moins 4 versions majeures d’iOS et 6 versions majeures d’Android. Notre prise en charge des versions actuelles peut s’étendre au-delà de ces minimums. Si une version majeure n’est plus prise en charge, les clients seront informés par des notes de mise à jour du SDK au moins 3 mois à l’avance.

### Navigateurs de bureau
<a name="browser-desktop"></a>


| Navigateur | Plateformes prises en charge | Versions prises en charge | 
| --- | --- | --- | 
| Chrome | Windows, macOS | Deux versions principales (la version actuelle et la version la plus récente) | 
| Firefox | Windows, macOS | Deux versions principales (la version actuelle et la version la plus récente) | 
| Edge | Windows 8.1\$1 | Deux versions principales (la version actuelle et la version la plus récente) Exclut Edge Legacy | 
| Safari | macOS | Deux versions principales (la version actuelle et la version la plus récente) | 

### Navigateurs mobiles (iOS et Android)
<a name="browser-mobile"></a>


| Navigateur | Plateformes prises en charge | Versions prises en charge | 
| --- | --- | --- | 
| Chrome | iOS, Android | Deux versions principales (la version actuelle et la version la plus récente) | 
| Firefox | Android | Deux versions principales (la version actuelle et la version la plus récente) | 
| Safari | iOS | Deux versions principales (la version actuelle et la version la plus récente) | 

#### Limitations connues
<a name="browser-mobile-limitations"></a>
+ Sur tous les navigateurs Web mobiles, nous vous déconseillons de diffuser ou de vous abonner sans avoir plus de trois diffuseurs de publication simultanés Cela pourrait entrainer des contraintes de performance liées aux artefacts vidéo et aux écrans noirs. Si vous avez besoin de plus de diffuseurs de publication, configurez la [diffusion et l’abonnement audio uniquement](web-publish-subscribe.md#web-publish-subscribe-concepts-strategy-updates).
+ Nous vous déconseillons de composer une scène et de la diffuser sur une chaîne avec Android Mobile Web. Cela pourrait réduire les performances et provoquer d’éventuels plantage. Si une fonctionnalité de diffusion est requise, intégrez le [SDK de diffusion Android par diffusion en temps réel IVS](broadcast-android.md).

## Webview
<a name="broadcast-webviews"></a>

Le SDK de diffusion Web ne prend pas en charge les Webview ou les environnements similaires au Web (téléviseurs, consoles, etc.). Pour les implémentations mobiles, consultez le guide du SDK de diffusion par streaming en temps réel pour [Android](broadcast-android.md) et pour [iOS](broadcast-ios.md).

## Accès requis à l’appareil
<a name="broadcast-device-access"></a>

Le kit SDK de diffusion nécessite l’accès aux caméras et microphones de l’appareil, à la fois ceux intégrés à l’appareil et ceux connectés via Bluetooth, USB ou prise audio.

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

Remarque : le SDK de diffusion est en constante amélioration. Consultez la rubrique [Notes de mise à jour Amazon IVS](release-notes.md) pour connaître les versions disponibles et les problèmes résolus. Le cas échéant, avant de contacter le support technique, mettez à jour la version du kit SDK de diffusion et vérifiez si cela résout votre problème.

### Gestion des versions
<a name="broadcast-support-versioning"></a>

Les kits SDK de diffusion Amazon IVS utilisent la [gestion sémantique des versions](https://semver.org/).

Pour ce sujet, supposons que :
+ la dernière version est la version 4.1.3 ;
+ la dernière version de la version majeure précédente est la version 3.2.4 ;
+ la dernière version de la version 1.x est la version 1.5.6.

De nouvelles fonctions rétrocompatibles sont ajoutées en tant que versions mineures de la dernière version. Dans ce cas, la prochaine série de nouvelles fonctions sera ajoutée dans la version 4.2.0.

Des corrections de bogues mineurs rétrocompatibles sont ajoutées en tant que versions de correctifs de la dernière version. Ici, la prochaine série de corrections de bogues mineurs sera ajoutée en tant que version 4.1.4.

Les corrections de bogues majeurs rétrocompatibles sont traitées différemment. Elles sont ajoutées à plusieurs versions :
+ Version de correctifs de la dernière version. Ici, il s’agit de la version 4.1.4.
+ Version de correctifs de la version mineure précédente. Ici, il s’agit de la version 3.2.5.
+ Version de correctifs de la dernière version 1.x. Ici, il s’agit de la version 1.5.7.

Les principales corrections de bogues sont définies par l’équipe produit d’Amazon IVS. Des exemples typiques sont les mises à jour de sécurité critiques et d’autres correctifs nécessaires pour les clients.

**Remarque :** dans les exemples ci-dessus, les versions publiées s’incrémentent sans ignorer de numéros (par exemple, de 4.1.3 à 4.1.4). En réalité, un ou plusieurs numéros de correctifs peuvent rester internes et ne pas être publiés, de sorte que la version publiée peut s’incrémenter de 4.1.3 à 4.1.6, par exemple.

# Kit SDK de diffusion IVS : Guide pour le Web \$1 Diffusion en temps réel
<a name="broadcast-web"></a>

Le SDK de diffusion Web par streaming en temps réel IVS fournit aux développeurs les outils nécessaires pour créer des expériences interactives en temps réel sur le Web. Ce kit SDK est destiné aux développeurs qui créent des applications web avec Amazon IVS.

Le SDK de diffusion Web permet aux participants d’envoyer et de recevoir des vidéos. Le SDK prend en charge les opérations suivantes :
+ Rejoindre une étape
+ Publier du contenu multimédia à l’intention des autres participants de l’étape
+ S’abonner à du contenu multimédia d’autres participants de l’étape
+ Gérer et surveiller la vidéo et le son publiés sur l’étape
+ Obtenir des statistiques WebRTC pour chaque connexion d’appairage
+ Toutes les opérations à partir du SDK de diffusion Web par streaming à faible latence

**Dernière version du kit SDK de diffusion Web :** 1.33.0 ([Notes de mise à jour](https://docs.aws.amazon.com/ivs/latest/RealTimeUserGuide/release-notes.html#mar12-26-broadcast-web-rt)) 

**Documentation de référence :** pour plus d’informations sur les méthodes les plus importantes disponibles dans le kit SDK de diffusion Web Amazon IVS, consultez la documentation de référence à l’adresse [https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference). Assurez-vous que la version la plus récente du kit SDK est sélectionnée.

**Exemple de code** : les exemples ci-dessous constituent un bon point de départ pour commencer à utiliser rapidement le kit SDK :
+ [Lecture simple](https://codepen.io/amazon-ivs/pen/RNwVBRK)
+ [Publication et abonnement simples](https://codepen.io/amazon-ivs/pen/ZEqgrpo)
+ [Démonstration complète de collaboration en temps réel avec React](https://github.com/aws-samples/amazon-ivs-real-time-collaboration-web-demo/tree/main)

**Exigences de la plateforme** : consultez le [SDK de diffusion Amazon IVS](https://docs.aws.amazon.com//ivs/latest/RealTimeUserGuide/broadcast.html) pour obtenir la liste des plateformes prises en charge

**Remarque :** la publication à partir d’un navigateur est pratique pour les utilisateurs finaux, car elle ne nécessite pas l’installation de logiciels supplémentaires. Cependant, la publication par navigateur est soumise aux contraintes et à la variabilité des environnements de navigateur. Si vous devez donner la priorité à la stabilité (par exemple, pour le streaming d’événements), nous vous recommandons généralement de publier à partir d’une source autre qu’un navigateur (par exemple, OBS Studio ou d’autres encodeurs dédiés), qui ont souvent un accès direct aux ressources du système et évitent les limitations du navigateur. Pour en savoir plus sur les options de publication hors navigateur, consultez la documentation [Ingestion de flux](rt-stream-ingest.md).

# Démarrez avec le SDK de diffusion Web IVS \$1 Streaming en temps réel
<a name="broadcast-web-getting-started"></a>

Ce document explique les étapes nécessaires pour le démarrage avec le SDK de diffusion Web IVS en temps réel.

## Importations
<a name="broadcast-web-getting-started-imports"></a>

Les composantes de base temps réel se trouvent dans un espace de noms différent de celui des modules de diffusion racine.

### Utilisation d’une balise de script
<a name="broadcast-web-getting-started-imports-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).

Les classes et les énumérations définies dans les exemples ci-dessous se trouvent sur l’objet global `IVSBroadcastClient` :

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

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

Pour installer le package `npm` : 

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

Les classes, les énumérations et les types peuvent également être importés depuis le module de package :

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

### Prise en charge du rendu côté serveur
<a name="broadcast-web-getting-started-imports-server-side-rendering"></a>

La bibliothèque de scènes du kit SDK de diffusion Web ne peut pas être chargée dans un contexte côté serveur, car elle référence des éléments de navigateur nécessaires à son fonctionnement lorsqu’elle est chargée. Pour contourner ce problème, chargez la bibliothèque dynamiquement, comme illustré dans la [démonstration de diffusion Web à l’aide de Next et React](https://github.com/aws-samples/amazon-ivs-broadcast-web-demo/blob/main/hooks/useBroadcastSDK.js#L26-L31).

## Demander des autorisations
<a name="broadcast-web-request-permissions"></a>

Votre application doit demander l’autorisation d’accéder à la caméra et au microphone de l’utilisateur, et cela doit être réalisé en utilisant HTTPS. (Ce n’est pas spécifique à Amazon IVS ; cette autorisation est requise pour toute application devant accéder aux caméras et aux microphones.)

Voici un exemple de fonction qui montre comment demander et capturer des autorisations pour les périphériques audio et vidéo :

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

Pour plus d’informations, consultez l’[API Permissions](https://developer.mozilla.org/en-US/docs/Web/API/Permissions_API) et [MediaDevices.getUserMedia()](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia).

## Répertorier les périphériques disponibles
<a name="broadcast-web-request-list-devices"></a>

Pour voir quels périphériques peuvent être capturés, interrogez la méthode [MediaDevices.enumerateDevices()](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/enumerateDevices) du navigateur :

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

## Récupérer un MediaStream depuis un périphérique
<a name="broadcast-web-retrieve-mediastream"></a>

Après avoir acquis la liste des périphériques disponibles, vous pouvez récupérer un flux à partir d’un nombre quelconque de périphériques. Par exemple, vous pouvez utiliser la méthode `getUserMedia()` pour récupérer un flux d’une caméra.

Si vous souhaitez spécifier le périphérique à partir duquel vous souhaitez capturer le flux, vous pouvez définir explicitement le `deviceId` dans la section `audio` ou `video` des contraintes multimédias. Vous pouvez également omettre le `deviceId` et demander aux utilisateurs de sélectionner leurs périphériques à l’invite du navigateur.

Vous pouvez également spécifier une résolution de caméra idéale à l’aide des contraintes `width` et `height`. (Pour en savoir plus sur ces contraintes, [cliquez ici](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints#properties_of_video_tracks).) Le kit SDK applique automatiquement des contraintes de largeur et de hauteur qui correspondent à votre résolution de diffusion maximale ; cependant, il est conseillé de les appliquer vous-même pour vous assurer que le rapport hauteur/largeur de la source n’est pas modifié une fois que vous avez ajouté la source au kit SDK.

Pour la diffusion en temps réel, assurez-vous que le contenu multimédia est limité à une résolution de 720p. Les valeurs de contrainte `getUserMedia` et `getDisplayMedia` pour la largeur et la hauteur ne doivent pas dépasser 921600 (1280×720) lorsqu’elles sont multipliées ensemble. 

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

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

# Publication et abonnement avec le SDK de diffusion Web IVS \$1 Streaming en temps réel
<a name="web-publish-subscribe"></a>

Ce document explique les étapes nécessaires pour la publication et l'abonnement à une étape à l'aide du SDK de diffusion Web IVS en temps réel.

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

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

### Étape
<a name="web-publish-subscribe-concepts-stage"></a>

La classe `Stage` est le principal point d’interaction entre l’application hôte et le kit SDK. Il représente l’étape elle-même et est utilisé pour rejoindre et quitter l’étape. La création et la participation à une étape nécessitent une chaîne de jetons valide et non expirée provenant du plan de contrôle (représentée par `token`). Il est très facile de rejoindre une étape et de la quitter :

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

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

stage.leave();
```

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

L’interface `StageStrategy` permet à l’application hôte de communiquer l’état de l’étape souhaité au kit SDK. Trois fonctions doivent être mises en œuvre : `shouldSubscribeToParticipant`, `shouldPublishParticipant` et `stageStreamsToPublish`. Elles sont toutes abordées ci-dessous.

Pour utiliser une stratégie définie, transmettez-la au constructeur `Stage`. Voici un exemple complet d’application utilisant une stratégie pour publier la webcam d’un participant sur l’étape et s’abonner à tous les participants. L’objectif de chaque fonction de stratégie requise est expliqué en détail dans les sections suivantes.

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

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

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

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

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

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

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


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

#### Abonnement aux participants
<a name="web-publish-subscribe-concepts-strategy-participants"></a>

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

Lorsqu’un participant distant rejoint l’étape, le kit SDK interroge l’application hôte sur l’état de l’abonnement souhaité pour ce participant. Les options sont `NONE`, `AUDIO_ONLY` et `AUDIO_VIDEO`. Lors d’un renvoi d’une valeur pour cette fonction, l’application hôte n’a pas à se soucier de l’état de publication, de l’état actuel de l’abonnement ou de l’état de la connexion de l’étape. Si `AUDIO_VIDEO` est renvoyé, le kit SDK attend que le participant distant effectue une publication avant de s’abonner, puis il met à jour l’application hôte en émettant des événements tout au long du processus.

Voici un exemple d’implémentation :

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

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

Il s’agit de l’implémentation complète de cette fonction pour une application hôte qui souhaite toujours que tous les participants se voient mutuellement, comme une application de chat vidéo.

Des implémentations plus avancées sont également possibles. Par exemple, supposons que l’application fournisse un attribut `role` lors de la création du jeton avec CreateParticipantToken. L’application pourrait utiliser la propriété `attributes` sur `StageParticipantInfo` pour s’abonner de manière sélective à des participants en fonction des attributs fournis par le serveur :

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

Elle peut servir à créer une scène où les modérateurs peuvent surveiller tous les invités sans être vus ou entendus. L’application hôte pourrait utiliser une logique métier supplémentaire pour permettre aux modérateurs de se voir mutuellement tout en restant invisible pour les invités.

#### Configuration d’abonnement aux participants
<a name="web-publish-subscribe-concepts-strategy-participants-config"></a>

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

[Lorsqu’un participant distant est abonné (voir Abonnement aux participants](#web-publish-subscribe-concepts-strategy-participants)), le kit SDK interroge l’application hôte sur une configuration d’abonnement personnalisée pour ce participant. Cette configuration est facultative et permet à l’application hôte de contrôler certains aspects du comportement de l’abonné. Pour plus d’informations sur les options de configuration, consultez [SubscribeConfiguration](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/interfaces/SubscribeConfiguration) dans la documentation de référence du kit SDK.

Voici un exemple d’implémentation :

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

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

Cette implémentation met à jour le délai minimum de la mémoire tampon pour tous les participants abonnés à un préréglage de `MEDIUM`.

Comme pour `shouldSubscribeToParticipant`, des implémentations plus avancées sont possibles. `ParticipantInfo` peut être utilisé pour mettre à jour de manière sélective la configuration d’abonnement pour des participants spécifiques.

Nous recommandons d’utiliser les comportements par défaut. Spécifiez une configuration personnalisée uniquement si vous souhaitez modifier un comportement particulier.

#### Publication
<a name="web-publish-subscribe-concepts-strategy-publishing"></a>

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

Une fois connecté à l’étape, le kit SDK interroge l’application hôte pour savoir si un participant en particulier doit effectuer une publication. Elle n’est invoquée que pour les participants locaux autorisés à publier sur la base du jeton fourni.

Voici un exemple d’implémentation :

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

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

Il s’agit d’une application de chat vidéo standard dans laquelle les utilisateurs souhaitent toujours publier. Ils peuvent désactiver et réactiver leur son et leur vidéo pour être instantanément masqués ou vus/entendus. (Ils peuvent également utiliser la fonction de publication/d’annulation de la publication, mais c’est beaucoup plus lent. Il est préférable de désactiver ou de réactiver le son dans les cas d’utilisation où il est souvent souhaitable de modifier la visibilité.)

#### Choix des flux à publier
<a name="web-publish-subscribe-concepts-strategy-streams"></a>

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

Lors de la publication, cette fonction est utilisée pour déterminer les flux audio et vidéo à publier. Ce point est abordé plus en détail dans la section [Publier un flux multimédia](#web-publish-subscribe-publish-stream).

#### Mise à jour de la stratégie
<a name="web-publish-subscribe-concepts-strategy-updates"></a>

La stratégie se veut dynamique : les valeurs renvoyées par n’importe laquelle des fonctions ci-dessus peuvent être modifiées à tout moment. Par exemple, si l’application hôte ne souhaite pas publier tant que l’utilisateur final n’a pas appuyé sur un bouton, vous pouvez renvoyer une variable depuis `shouldPublishParticipant` (quelque chose comme `hasUserTappedPublishButton`). Lorsque cette variable change en fonction d’une interaction de l’utilisateur final, appelez `stage.refreshStrategy()` pour signaler au kit SDK qu’il doit interroger la stratégie pour connaître les dernières valeurs, en appliquant uniquement les éléments qui ont changé. Si le kit SDK constate que la valeur `shouldPublishParticipant` a changé, il lance le processus de publication. Si les requêtes du kit SDK et toutes les fonctions renvoient la même valeur qu’auparavant, l’appel `refreshStrategy` ne modifie pas l’étape.

Si la valeur de retour de `shouldSubscribeToParticipant` passe de `AUDIO_VIDEO` à `AUDIO_ONLY`, le flux vidéo est supprimé pour tous les participants dont les valeurs renvoyées ont été modifiées, s’il existait déjà un flux vidéo.

En général, l’étape utilise la stratégie pour appliquer au mieux la différence entre les stratégies précédentes et actuelles, sans que l’application hôte n’ait à se soucier de tout l’état requis pour la gérer correctement. Pour cette raison, et pour réduire les frais, envisagez d’appeler `stage.refreshStrategy()`, car cela ne fait rien à moins que la stratégie ne change.

### Événements
<a name="web-publish-subscribe-concepts-events"></a>

Une instance `Stage` est un émetteur d’événements. En utilisant `stage.on()`, l’état de l’étape est communiqué à l’application hôte. Les mises à jour de l’interface utilisateur de l’application hôte peuvent généralement être entièrement prises en charge par les événements. Les événements sont les suivants :

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

Pour la plupart de ces événements, les `ParticipantInfo` correspondantes sont fournies.

Les informations fournies par les événements ne devraient pas avoir d’impact sur les valeurs de retour de la stratégie. Par exemple, la valeur de retour de `shouldSubscribeToParticipant` ne devrait pas changer lors de l’appel de `STAGE_PARTICIPANT_PUBLISH_STATE_CHANGED`. Si l’application hôte souhaite s’abonner à un participant en particulier, elle doit renvoyer le type d’abonnement souhaité, quel que soit l’état de publication de ce participant. Le kit SDK est chargé de s’assurer que l’état souhaité de la stratégie est appliqué au bon moment en fonction de l’état de l’étape.

## Publier un flux multimédia
<a name="web-publish-subscribe-publish-stream"></a>

Les appareils locaux tels que les microphones et les caméras sont récupérés en suivant les mêmes étapes que celles décrites ci-dessus dans [Récupérer un MediaStream depuis un périphérique](broadcast-web-getting-started.md#broadcast-web-retrieve-mediastream). Dans l’exemple, nous utilisons`MediaStream` pour créer une liste d’objets `LocalStageStream` utilisés à des fins de publication par le kit SDK :

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

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

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

## Publier un partage d’écran
<a name="web-publish-subscribe-publish-screenshare"></a>

Les applications doivent souvent publier un partage d’écran en plus de la caméra Web de l’utilisateur. La publication d’un partage d’écran nécessite la création d’un jeton supplémentaire pour la scène, spécifiquement pour la publication du contenu multimédia du partage d’écran. Utilisez `getDisplayMedia` et limitez la résolution à un maximum de 720p. Ensuite, les étapes sont similaires à celles de la publication d’une caméra sur la scène.

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

## Afficher et supprimer des participants
<a name="web-publish-subscribe-participants"></a>

Une fois l’inscription terminée, vous recevez un tableau d’objets `StageStream` via l’événement `STAGE_PARTICIPANT_STREAMS_ADDED`. L’événement vous fournit également des informations sur les participants pour vous aider lors de l’affichage des flux de contenu multimédia :

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

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

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

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

Lorsqu’un participant arrête de publier ou en cas de désabonnement d’un flux, la fonction `STAGE_PARTICIPANT_STREAMS_REMOVED` est appelée avec les flux qui ont été supprimés. Les applications hôte doivent utiliser ce signal pour supprimer le flux vidéo du participant du DOM.

`STAGE_PARTICIPANT_STREAMS_REMOVED` est invoqué pour tous les scénarios dans lesquels un flux peut être supprimé, notamment :
+ Le participant distant arrête de publier.
+ Un appareil local se désabonne ou modifie l’abonnement de `AUDIO_VIDEO` en `AUDIO_ONLY`.
+ Le participant distant quitte l’étape.
+ Le participant local quitte l’étape.

Comme `STAGE_PARTICIPANT_STREAMS_REMOVED` est invoqué pour tous les scénarios, aucune logique métier personnalisée n’est requise pour supprimer des participants de l’interface utilisateur lors des opérations de départ locales ou à distance.

## Désactiver et réactiver le son des flux de médias sociaux
<a name="web-publish-subscribe-mute-streams"></a>

Les objets `LocalStageStream` ont une fonction `setMuted` qui contrôle si le son du flux est désactivé. Cette fonction peut être appelée sur le flux avant ou après son renvoi par la fonction de stratégie `stageStreamsToPublish`.

**Important** : si une nouvelle instance d’objet `LocalStageStream` est renvoyée par `stageStreamsToPublish` après un appel à `refreshStrategy`, l’état muet du nouvel objet de flux est appliqué à l’étape. Lorsque vous créez des instances `LocalStageStream`, veillez à ce que l’état muet attendu soit conservé.

## Surveiller l’état muet du contenu multimédia des participants distants
<a name="web-publish-subscribe-mute-state"></a>

Lorsque les participants modifient l’état muet de leur vidéo ou audio, l’événement `STAGE_STREAM_MUTE_CHANGED` est déclenché avec une liste des flux qui ont changé. Utilisez la propriété `isMuted` sur `StageStream` pour mettre à jour votre interface utilisateur en conséquence :

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

Vous pouvez également consulter [StageParticipantInfo](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference#stageparticipantinfo) pour obtenir des informations sur l’état indiquant si le son ou la vidéo est désactivé :

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

## Obtenir les statistiques WebRTC
<a name="web-publish-subscribe-webrtc-stats"></a>

La méthode `requestQualityStats()` permet d’accéder à des statistiques WebRTC détaillées pour les flux locaux et distants. Cette méthode est disponible à la fois sur les objets LocalStageStream et RemoteStageStream. Elle renvoie des métriques de qualité complètes, notamment la qualité du réseau, les statistiques sur les paquets, les informations sur le débit et les métriquess relatives au cadre.

Il s’agit d’une méthode asynchrone avec laquelle vous pouvez récupérer des statistiques soit via une attente, soit en enchaînant une promesse. Elle renvoie `undefined` lorsque les statistiques ne sont pas disponibles ; par exemple, le flux n’est pas actif ou les statistiques internes ne sont pas disponibles. Si des statistiques sont disponibles, et en fonction du flux (distant ou local, vidéo ou audio), la méthode renvoie un objet [LocalVideoStats](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/interfaces/LocalVideoStats), [LocalAudioStats](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/interfaces/LocalAudioStats), [RemoteVideoStats](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/interfaces/RemoteVideoStats) ou [RemoteAudioStats](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/interfaces/RemoteAudioStats).

Notez que pour les flux vidéo avec diffusion simultanée, le tableau contient plusieurs objets statistiques (un par couche).

**Bonnes pratiques**
+ Fréquence d’interrogation : appelez `requestQualityStats()` à intervalles raisonnables (1 à 5 secondes) pour éviter tout impact sur les performances
+ Gestion des erreurs : vérifiez toujours si la valeur renvoyée est bien `undefined` avant le traitement
+ Gestion de la mémoire : effacez les intervals/délais d’expiration des flux dont vous n’vez plus besoin
+ Qualité du réseau : utilisez `networkQuality` pour recueillir les commentaires des utilisateurs concernant les dégradations possibles causées par le réseau. Pour en svoir plus, consultez [NetworkQuality](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/enumerations/NetworkQuality).

**Exemple d'utilisation**

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

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

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

## Optimisation de contenu multimédia
<a name="web-publish-subscribe-optimizing-media"></a>

Pour des performances optimales, il est recommandé de limiter les appels `getUserMedia` et `getDisplayMedia` aux contraintes suivantes :

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

Vous pouvez restreindre davantage le contenu multimédia à l’aide d’options supplémentaires transmises au constructeur `LocalStageStream` :

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

Dans le code ci-dessus :
+ `minBitrate` définit le débit minimum que le navigateur doit être censé utiliser. Toutefois, un flux vidéo de faible complexité peut pousser l’encodeur à descendre en dessous de ce débit.
+ `maxBitrate` définit un débit maximal que le navigateur ne doit pas dépasser pour ce flux.
+ `maxFramerate` définit une fréquence d’images maximale que le navigateur ne doit pas dépasser pour ce flux.
+ L’option `simulcast` n’est utilisable que sur les navigateurs basés sur Chromium. Elle permet d’envoyer trois couches de rendu du flux.
  + Cela permet au serveur de choisir le rendu à envoyer aux autres participants, en fonction des limites de leur réseau.
  + Lorsque `simulcast` est spécifié en même temps qu’une valeur `maxBitrate` et/ou `maxFramerate`, on s’attend à ce que la couche de rendu la plus élevée soit configurée en tenant compte de ces valeurs, à condition que `maxBitrate` ne descende pas en dessous de la valeur `maxBitrate` par défaut de 900 kbps de la deuxième couche la plus élevée du SDK interne.
  + Si `maxBitrate` est spécifié comme étant trop faible par rapport à la valeur par défaut de la deuxième couche la plus élevée, `simulcast` sera désactivé.
  + `simulcast` ne peut pas être activé et désactivé sans republier le média. Pour ce faire, il faut que `shouldPublishParticipant` renvoie la valeur `false`, que `refreshStrategy` soit appelé, que `shouldPublishParticipant` renvoie la valeur `true` et que `refreshStrategy` soit à nouveau appelé.

## Obtenir les attributs des participants
<a name="web-publish-subscribe-participant-attributes"></a>

Si vous spécifiez des attributs dans la demande de l’opération `CreateParticipantToken`, vous pouvez les voir dans les propriétés `StageParticipantInfo` :

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

## Informations supplémentaires sur les améliorations (SEI)
<a name="web-publish-subscribe-sei-attributes"></a>

L’unité NAL d’informations supplémentaires sur les améliorations (SEI) est utilisée pour stocker des métadonnées alignées sur l’image à côté de la vidéo. Peuvent être utilisées lors de la publication et de l'abonnement à des flux vidéo H.264. Il n'est pas garanti que les données utiles SEI parviennent aux abonnés, en particulier en cas de mauvais état du réseau. Comme les données utiles SEI stockent les données directement dans la structure d’image H.264, cette fonctionnalité ne peut pas être exploitée pour les flux exclusivement audio.

### Insertion de données utiles SEI
<a name="sei-attributes-inserting-sei-payloads"></a>

Les clients de publication peuvent insérer des données utiles SEI dans un flux d'étape en cours de publication en configurant LocalStageStream de leur vidéo pour activer `inBandMessaging` puis en invoquant la méthode `insertSeiMessage`. Notez que l'activation de `inBandMessaging` augmente l'utilisation de la mémoire du SDK.

Les données utiles doivent être du type [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer). La taille des données utiles doit être supérieure à 0 Ko et inférieure à 1 Ko. Le nombre de messages SEI insérés par seconde ne doit pas dépasser 10 Ko par seconde.

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

#### Répétition de données utiles SEI
<a name="sei-attributes-repeating-sei-payloads"></a>

Fournissez éventuellement un `repeatCount` pour répéter l'insertion de données utiles SEI pour les N prochaines trames envoyées. Cela peut être utile pour atténuer les pertes inhérentes qui peuvent survenir en raison du protocole de transport UDP sous-jacent utilisé pour envoyer des vidéos. Notez que cette valeur doit être comprise entre 0 et 30. Les clients destinataires doivent disposer d'une logique permettant de dédupliquer le message.

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

### Lecture de données utiles du SEI
<a name="sei-attributes-reading-sei-payloads"></a>

Les clients abonnés peuvent lire les données utiles SEI d’un éditeur qui publie une vidéo H.264 si elle est présente en configurant le ou les abonnés `SubscribeConfiguration` pour activer `inBandMessaging` et en écoutant l’événement `StageEvents.STAGE_STREAM_SEI_MESSAGE_RECEIVED`, comme le montre l’exemple suivant :

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

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

## Encodage en couches avec Simulcast
<a name="web-publish-subscribe-layered-encoding-simulcast"></a>

L’encodage en couches avec diffusion simultanée est une fonctionnalité de diffusion en temps réel d’IVS qui permet aux diffuseurs d’envoyer plusieurs couches de vidéo de qualité différente et aux abonnés de modifier ces couches de manière dynamique ou manuelle. Cette fonctionnalité est décrite plus en détail dans le document [Optimisations de la diffusion](https://docs.aws.amazon.com//ivs/latest/RealTimeUserGuide/real-time-streaming-optimization.html).

### Configuration du codage en couches (diffuseur de publication)
<a name="web-layered-encoding-simulcast-configure-publisher"></a>

En tant que diffuseur de publication, pour activer l’encodage en couches avec la diffusion simultanée, ajoutez la configuration suivante à votre `LocalStageStream` lors de l’instanciation :

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

En fonction de la résolution d’entrée de votre caméra, un certain nombre de couches seront encodées et envoyées comme défini dans la section [Couches, qualités et fréquences d’images par défaut](real-time-streaming-optimization.md#real-time-streaming-optimization-default-layers) de la section *Optimisations de la diffusion*.

Vous avez également la possibilité de configurer des couches individuelles depuis la configuration de diffusion simultanée :

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

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

Vous pouvez également créer vos propres configurations de couches personnalisées pour un maximum de trois couches. Si vous fournissez un tableau vide ou ne contenant aucune valeur, les valeurs par défaut décrites ci-dessus sont utilisées. Les couches sont décrites avec les propriétés requises suivantes :
+ `height: number;`
+ `width: number;`
+ `maxBitrateKbps: number;`
+ `maxFramerate: number;`

À partir des préréglages, vous pouvez remplacer des propriétés individuelles ou créer une toute nouvelle configuration :

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

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

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

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

Pour connaître les valeurs maximales, les limites et les erreurs qui peuvent être déclenchées lors de la configuration de couches individuelles, consultez la documentation de référence du kit SDK.

### Configuration de l’encodage en couches (abonné)
<a name="web-layered-encoding-simulcast-configure-subscriber"></a>

En tant qu’abonné, il n’est pas nécessaire d’activer l’encodage en couches. Si un diffuseur de publication envoie des couches de diffusion simultanée, le serveur s’adapte dynamiquement entre les couches pour choisir la qualité optimale en fonction de l’appareil de l’abonné et des conditions du réseau.

Sinon, pour choisir les couches explicites que le diffuseur de publication envoie, il existe plusieurs options, décrites ci-dessous.

### Option 1 : préférence pour la qualité de la couche initiale
<a name="web-layered-encoding-simulcast-layer-quality-preference"></a>

En utilisant la stratégie `subscribeConfiguration`, il est possible de choisir la couche initiale que vous voulez recevoir en tant qu’abonné :

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

Par défaut, les abonnés reçoivent toujours d’abord la couche de qualité la plus faible, puis la couche de qualité la plus élevée. Cela permet d’optimiser la consommation de bande passante de l’utilisateur final et d’offrir le meilleur temps d’accès à la vidéo, réduisant ainsi les blocages initiaux de la vidéo pour les utilisateurs sur des réseaux plus faibles.

Ces options sont disponibles pour `InitialLayerPreference` :
+ `LOWEST_QUALITY` : le serveur diffuse d’abord la couche de vidéo de qualité la plus faible. Cela permet d’optimiser la consommation de la bande passante ainsi que le temps d’accès au contenu multimédia. La qualité est définie comme la combinaison de la taille, du débit binaire et de la fréquence d’images de la vidéo. Par exemple, une vidéo 720p est de moins bonne qualité qu’une vidéo 1080p.
+ `HIGHEST_QUALITY` : le serveur délivre d’abord la couche de vidéo de la plus haute qualité. Cela permet d’optimiser la qualité, mais peut augmenter le temps d’accès au contenu multimédia. La qualité est définie comme la combinaison de la taille, du débit binaire et de la fréquence d’images de la vidéo. Par exemple, la vidéo 1080p est de meilleure qualité que la vidéo 720p.

**Remarque :** pour que les préférences de couche initiale (l’appel `initialLayerPreference`) prennent effet, un nouvel abonnement est nécessaire car ces mises à jour ne s’appliquent pas à l’abonnement actif.



### Option 2 : couche préférée pour le flux
<a name="web-layered-encoding-simulcast-preferred-layer"></a>

Une fois qu’un flux a démarré, vous pouvez utiliser la méthode de stratégie `preferredLayerForStream `. Cette méthode expose les informations relatives au participant et au flux.

La méthode de stratégie peut être renvoyée avec ce qui suit :
+ L’objet couche directement, en fonction de ce que renvoie `RemoteStageStream.getLayers` 
+ La chaîne d’étiquette de l’objet couche, basée sur `StageStreamLayer.label`
+ Undefined ou null, qui indique qu’aucune couche ne doit être sélectionnée et que l’adaptation dynamique est préférable

Par exemple, dans la stratégie suivante, les utilisateurs sélectionneront toujours la couche de vidéo de la plus basse qualité disponible :

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

Pour réinitialiser la sélection des couches et revenir à l’adaptation dynamique, renvoyez null ou undefined dans la stratégie. Dans cet exemple, `appState` est une variable fictive qui représente l’état possible de l’application.

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

### Option 3 : aides pour les couches de RemoteStageStream
<a name="web-layered-encoding-simulcast-remotestagestream-helpers"></a>

`RemoteStageStream` possède plusieurs aides qui peuvent être utilisées pour prendre des décisions concernant la sélection des couches et afficher les sélections correspondantes aux utilisateurs finaux :
+ **Événements relatifs aux couches** : tout comme `StageEvents`, l’objet `RemoteStageStream` lui-même possède des événements qui communiquent les modifications apportées aux couches et à l’adaptation de la diffusion simultanée :
  + `stream.on(RemoteStageStreamEvents.ADAPTION_CHANGED, (isAdapting) => {})`
  + `stream.on(RemoteStageStreamEvents.LAYERS_CHANGED, (layers) => {})`
  + `stream.on(RemoteStageStreamEvents.LAYER_SELECTED, (layer, reason) => {})`
+ **Méthodes relatives aux couches** : `RemoteStageStream` possède plusieurs méthodes d’aide qui peuvent être utilisées pour obtenir des informations sur le flux et les couches présentées. Ces méthodes sont disponibles sur le flux distant fourni dans la stratégie `preferredLayerForStream `, ainsi que sur les flux distants exposés via `StageEvents.STAGE_PARTICIPANT_STREAMS_ADDED`.
  + `stream.getLayers`
  + `stream.getSelectedLayer`
  + `stream.getLowestQualityLayer`
  + `stream.getHighestQualityLayer`

Pour plus de détails, consultez la classe `RemoteStageStream` dans la [documentation de référence du kit SDK](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference) (français non garanti). Pour la raison `LAYER_SELECTED`, si le message `UNAVAILABLE` est renvoyé, cela indique que la couche demandée n'a pas pu être sélectionnée. La meilleure sélection est faite à sa place, qui est généralement une couche de qualité inférieure pour maintenir la stabilité du flux.

## Gestion des problèmes de réseau
<a name="web-publish-subscribe-network-issues"></a>

En cas de perte de la connexion réseau de l’appareil local, le kit SDK essaie de se reconnecter en interne sans aucune action de l’utilisateur. Dans certains cas, le kit SDK échoue et une action de l’utilisateur est requise.

D’une manière générale, l’état de l’étape peut être géré via l’événement `STAGE_CONNECTION_STATE_CHANGED` :

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

En général, vous pouvez ignorer un état d’erreur qui survient après avoir rejoint une scène avec succès, car le kit SDK tentera de se rétablir automatiquement. Si le kit SDK signale un état `ERRORED` et que la scène reste dans l’état `CONNECTING` pendant une période prolongée (par exemple, 30 secondes ou plus), cela peut indiquer une déconnexion du réseau.

## Diffuser la scène sur un canal IVS
<a name="web-publish-subscribe-broadcast-stage"></a>

Pour diffuser une étape, créez une session `IVSBroadcastClient` distincte, puis suivez les instructions habituelles de diffusion avec le kit SDK, décrites ci-dessus. La liste des `StageStream` exposés via `STAGE_PARTICIPANT_STREAMS_ADDED` peut être utilisée pour récupérer les flux de contenu multimédia des participants, qui peuvent être appliqués à la composition du flux de diffusion, comme suit :

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

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

Vous pouvez éventuellement composer une scène et la diffuser sur un canal IVS à faible latence, afin de toucher un public plus large. Consultez la section [Activation d’hôtes multiples sur un flux Amazon IVS](https://docs.aws.amazon.com//ivs/latest/LowLatencyUserGuide/multiple-hosts.html) dans le guide de l’utilisateur du streaming à faible latence IVS.

# Problèmes connus et solutions de contournement dans le SDK de diffusion Web IVS \$1 Streaming en temps réel
<a name="broadcast-web-known-issues"></a>

Ce document contient les problèmes connus que vous pouvez rencontrer lors de l'utilisation du SDK de diffusion Web Amazon IVS en temps réel et suggère des solutions de contournement potentielles.
+ Lorsque vous fermez les onglets du navigateur ou quittez des navigateurs sans appeler `stage.leave()`, des utilisateurs peuvent toujours apparaître dans la session avec un cadre figé ou un écran noir pendant maximum 10 secondes.

  **Solution de contournement :** aucune.
+ Les sessions Safari apparaissent par intermittence avec un écran noir pour les utilisateurs qui rejoignent une session en cours.

  **Solution de contournement :** actualisez le navigateur et reconnectez la session.
+ Safari ne se rétablit pas correctement après un changement de réseau.

  **Solution de contournement :** actualisez le navigateur et reconnectez la session.
+ La console du développeur répète une erreur `Error: UnintentionalError at StageSocket.onClose`.

  **Solution de contournement :** il n’est possible de créer qu’une seule étape par jeton de participant. Cette erreur se produit lorsque plusieurs instances `Stage` sont créées avec le même jeton de participant, que l’instance se trouve sur un ou plusieurs appareils.
+ Il se peut que vous éprouviez des difficultés à maintenir un état `StageParticipantPublishState.PUBLISHED` et que vous receviez des états `StageParticipantPublishState.ATTEMPTING_PUBLISH` répétés lorsque vous écoutez l’événement `StageEvents.STAGE_PARTICIPANT_PUBLISH_STATE_CHANGED`.

  **Solution :** limitez la résolution vidéo à 720p lors de l’invocation de `getUserMedia` ou de `getDisplayMedia`. Les valeurs de contrainte `getUserMedia` et `getDisplayMedia` pour la largeur et la hauteur ne doivent pas dépasser 921600 (1280×720) lorsqu’elles sont multipliées ensemble.
+ Lorsque `stage.leave()` est invoqué ou qu'un participant distant quitte, une erreur 404 DELETE apparaît dans la console de débogage du navigateur.

  **Solution de contournement :** aucune. Il s'agit d'une erreur inoffensive.

## Limitations de Safari
<a name="broadcast-web-safari-limitations"></a>
+ Le refus d’une demande d’autorisation nécessite de réinitialiser l’autorisation dans les paramètres du site web Safari au niveau du système d’exploitation.
+ Safari ne détecte pas nativement tous les périphériques aussi efficacement que Firefox ou Chrome. Par exemple, la caméra virtuelle OBS n’est pas détectée.

## Limitations de Firefox
<a name="broadcast-web-firefox-limitations"></a>
+ Les autorisations système doivent être activées pour que Firefox puisse partager l’écran. Après les avoir activées, l’utilisateur doit redémarrer Firefox pour qu’il fonctionne correctement ; sinon, si les autorisations sont perçues comme bloquées, le navigateur renverra une exception [NotFoundError](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia#exceptions).
+ La méthode `getCapabilities` est manquante. Cela signifie que les utilisateurs ne peuvent pas obtenir la résolution ou le rapport hauteur/largeur de la piste multimédia. Consultez ce [thread Bugzilla](https://bugzilla.mozilla.org/show_bug.cgi?id=1179084).
+ Plusieurs propriétés `AudioContext` sont manquantes, par exemple la latence et le nombre de canaux. Cela peut poser un problème aux utilisateurs expérimentés qui souhaitent manipuler les pistes audio.
+ Les flux de caméra provenant de `getUserMedia` sont limités au format 4:3 sur macOS. Consultez le [thread Bugzilla 1](https://bugzilla.mozilla.org/show_bug.cgi?id=1193640) et le [thread Bugzilla 2](https://bugzilla.mozilla.org/show_bug.cgi?id=1306034).
+ La capture audio n’est pas prise en charge avec `getDisplayMedia`. Consultez ce [thread Bugzilla](https://bugzilla.mozilla.org/show_bug.cgi?id=1541425).
+ La fréquence d’images lors de la capture d’écran n’est pas optimale (environ 15 images par seconde ?). Consultez ce [thread Bugzilla](https://bugzilla.mozilla.org/show_bug.cgi?id=1703522).

## Limites du Web mobile
<a name="broadcast-web-mobile-web-limitations"></a>
+ Le partage d’écran [GetDisplayMedia](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia#browser_compatibility) n’est pas pris en charge sur les appareils mobiles.

  **Solution de contournement :** aucune.
+ Le participant met 15 à 30 secondes pour partir lorsqu’il ferme un navigateur sans appeler `leave()`.

  **Solution** : Ajoutez une interface utilisateur qui encourage les utilisateurs à se déconnecter correctement.
+ L’application d’arrière-plan entraîne l’arrêt de la diffusion de vidéos.

  **Solution** : Affichez une liste d’interfaces utilisateur lorsque le diffuseur de publication est suspendu.
+ La fréquence d’images vidéo chute pendant environ 5 secondes après avoir désactivé une caméra sur les appareils Android.

  **Solution de contournement :** aucune.
+ Le flux vidéo est étiré en rotation pour iOS 16.0.

  **Solution** : Affichez une interface utilisateur décrivant ce problème connu du système d’exploitation.
+ Commuter le périphérique d’entrée audio commute automatiquement le périphérique de sortie audio.

  **Solution de contournement :** aucune.
+ Mettre le navigateur en arrière-plan entraîne l’affichage en noir du flux de publication et diffuse uniquement de l’audio.

  **Solution de contournement :** aucune. C’est pour des raisons de sécurité.

# Gestion des erreurs dans le SDK de diffusion Web IVS \$1 Streaming en temps réel
<a name="broadcast-web-error-handling"></a>

Cette section donne un aperçu des conditions d’erreur, de la façon dont le kit SDK de diffusion Web les signale à l’application et des actions que l’application doit prendre lorsqu’elles se produisent. Les erreurs sont signalées par le kit SDK aux écouteurs de l’événement `StageEvents.ERROR` :

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

## Erreurs de scène
<a name="web-error-handling-stage-errors"></a>

Une erreur de scène (StageError) est signalée lorsque le kit SDK rencontre un problème qu’il ne peut pas résoudre seul et qui nécessite généralement une intervention de l’application et/ou une reconnexion au réseau pour se rétablir.

Chaque `StageError` signalé comporte un code (ou `StageErrorCode`), un message (chaîne de caractères) et une catégorie (`StageErrorCategory`). Chacune de ces erreurs est liée à une catégorie d’opération sous-jacente.

La catégorie d’opération de l’erreur est déterminée en fonction de la nature de l’erreur : si elle est liée à la connexion à la scène (`JOIN_ERROR`), à l’envoi de contenu multimédia vers la scène (`PUBLISH_ERROR`) ou à la réception d’un flux multimédia entrant depuis la scène (`SUBSCRIBE_ERROR`).

La propriété code d’un `StageError` indique le problème spécifique :


| Nom | Code | Action recommandée | 
| --- | --- | --- | 
| JETON\$1MALFORMÉ | 1 | Créez un jeton valide, puis réessayez l’instanciation de la scène. | 
| JETON\$1EXPIRÉ | 2 | Créez un jeton non expiré, puis réessayez l’instanciation de la scène. | 
| EXPIRATION | 3 | L’opération a expiré. Si la scène existe et que le jeton est valide, cet échec est probablement dû à un problème du réseau. Dans ce cas, attendez que la connexion de l’appareil se rétablisse. | 
| FAILED | 4 | Une situation fatale s’est produite lors d’une tentative d’opération. Vérifiez les détails de l’erreur. Si la scène existe et que le jeton est valide, cet échec est probablement dû à un problème du réseau. Dans ce cas, attendez que la connexion de l’appareil se rétablisse. Pour la plupart des échecs liés à la stabilité du réseau, le kit SDK effectuera une nouvelle tentative en interne pendant une période pouvant aller jusqu’à 30 secondes avant d’émettre une erreur FAILED.  | 
| ANNULÉE | 5 | Vérifiez le code de l’application et assurez-vous qu’il n’y a pas d’invocations `join`, `refreshStrategy`, ou `replaceStrategy` répétées qui pourraient provoquer le démarrage et l’annulation répétés des opérations avant leur fin. | 
| SCÈNE\$1À\$1CAPACITÉ\$1MAXIMALE | 6 | Cette erreur indique que la scène ou votre compte a atteint sa capacité maximale. Si la scène a atteint sa limite de participants, réessayez l’opération lorsque la scène n’est plus saturée, en actualisant la stratégie. Si votre compte a atteint son quota d’abonnements simultanés ou de diffuseurs de publication simultanés, réduisez l’utilisation ou demandez une augmentation du quota via la [console AWS Service Quotas](https://console.aws.amazon.com/servicequotas/).  | 
| INCOMPATIBILITÉ\$1DE\$1CODEC | 7 | Le codec n’est pas pris en charge par la scène. Vérifiez la compatibilité du codec avec le navigateur et la plateforme. Pour la diffusion en temps réel IVS, les navigateurs doivent prendre en charge le codec H.264 pour la vidéo et le codec Opus pour l’audio. | 
| JETON\$1NON\$1AUTORISÉ | 8 | Le jeton n’est pas autorisé à effectuer l’opération. Recréez le jeton avec les autorisations appropriées, puis réessayez. | 
| STAGE\$1DELETED | 9 | Aucune ; cette erreur se produit lorsque vous essayez de rejoindre une scène supprimée. | 
| PARTICIPANT\$1DISCONNECTED | 10 | Aucune ; cette erreur se produit lorsque vous essayez de rejoindre une étape avec un jeton d’un participant déconnecté. | 

### Exemple de gestion d’erreur de scène (StageError)
<a name="web-error-handling-stage-errors-example"></a>

Utilisez le code StageError pour déterminer si l’erreur est due à un jeton expiré :

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

### Erreurs réseau lorsque vous êtes déjà connecté
<a name="web-error-handling-stage-errors-network"></a>

En cas de perte de la connexion réseau de l’appareil, le kit SDK risque de perdre sa connexion aux serveurs de scène. Des erreurs peuvent s’afficher dans la console car le kit SDK ne peut plus accéder aux services backend. Les publications sur https://broadcast.stats.live-video.net échoueront.

Si vous publiez et/ou que vous vous abonnez, des erreurs liées à des tentatives de publication/d’abonnement apparaîtront dans la console.

En interne, le kit SDK tentera de se reconnecter avec une stratégie de backoff exponentiel.

**Action** : attendez que la connectivité de l’appareil soit rétablie.

## États d’erreur
<a name="web-error-handling-errored-states"></a>

Nous recommandons d’utiliser ces états pour consigner les erreurs dans l’application et afficher des messages aux utilisateurs pour les alerter des problèmes de connexion à la scène pour un participant particulier.

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

Le kit SDK signale `ERRORED` lorsqu’une publication échoue.

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

### S’abonner
<a name="errored-states-subscribe"></a>

Le kit SDK signale `ERRORED` lorsqu’un abonnement échoue. Cela peut se produire en raison des conditions du réseau ou si une étape est à pleine capacité pour les abonnés.

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

# Kit SDK de diffusion IVS : Guide pour Android \$1 Diffusion en temps réel
<a name="broadcast-android"></a>

Le SDK de diffusion Android par streaming en temps réel IVS permet aux participants d’envoyer et de recevoir des vidéos sur Android.

Le package `com.amazonaws.ivs.broadcast` implémente l’interface décrite dans ce document. Le SDK prend en charge les opérations suivantes :
+ Rejoindre une étape 
+ Publier du contenu multimédia à l’intention des autres participants de l’étape
+ S’abonner à du contenu multimédia d’autres participants de l’étape
+ Gérer et surveiller la vidéo et le son publiés sur l’étape
+ Obtenir des statistiques WebRTC pour chaque connexion d’appairage
+ Toutes les opérations à partir du SDK de diffusion Android par streaming à faible latence

**Dernière version du SDK de diffusion Android :** 1.40.0 ([notes de mise à jour](https://docs.aws.amazon.com/ivs/latest/RealTimeUserGuide/release-notes.html#mar12-26-broadcast-android-rt)) 

**Documentation de référence :** pour plus d'informations sur les méthodes les plus importantes disponibles dans le kit SDK de diffusion Android Amazon IVS, consultez la documentation de référence à l'adresse [https://aws.github.io/amazon-ivs-broadcast-docs/1.40.0/android/](https://aws.github.io/amazon-ivs-broadcast-docs/1.40.0/android/).

**Exemple de code : **voir le référentiel d’exemples Android sur GitHub : [https://github.com/aws-samples/amazon-ivs-real-time-streaming-android-samples](https://github.com/aws-samples/amazon-ivs-real-time-streaming-android-samples).

**Exigences de la plateforme :** Android 9.0\$1

# Démarrez avec le SDK de diffusion Android IVS \$1 Streaming en temps réel
<a name="broadcast-android-getting-started"></a>

Ce document explique les étapes nécessaires pour le démarrage avec le SDK de diffusion Android IVS en temps réel.

## Installer la bibliothèque
<a name="broadcast-android-install"></a>

Il existe plusieurs façons d’ajouter la bibliothèque de diffusion Android d’Amazon IVS à votre environnement de développement Android : utiliser Gradle directement, utiliser les catalogues de versions de Gradle ou installer le kit SDK manuellement.

**Utiliser Gradle directement** : ajoutez la bibliothèque dans le fichier `build.gradle` de votre module, comme indiqué ici (pour la dernière version du kit SDK de diffusion IVS) :

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

**Utiliser les catalogues de versions de Gradle** : ajoutez d’abord ceci dans le fichier `build.gradle` de votre module :

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

Ajoutez ensuite les éléments suivants dans le fichier `libs.version.toml` (pour la dernière version du kit SDK de diffusion IVS) :

```
[versions]
ivs="1.40.0"

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

**Installer le kit SDK manuellement** : téléchargez la dernière version à partir de l’emplacement suivant :

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

N’oubliez pas de télécharger le `aar` en ajoutant le suffixe `-stages`.

**Autoriser le contrôle du haut-parleur par le kit SDK** : indépendamment de la méthode d’installation choisie, ajoutez également l’autorisation suivante dans votre manifeste pour permettre au kit SDK d’activer et de désactiver le haut-parleur :

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

## Utilisation du kit SDK avec des symboles de débogage
<a name="broadcast-android-using-debug-symbols-rt"></a>

Nous publions également une version du kit SDK de diffusion Android incluant des symboles de débogage. Vous pouvez utiliser cette version pour améliorer la qualité des rapports de débogage (traces de pile) dans Firebase Crashlytics, en cas de plantages dans le kit SDK de diffusion IVS, par exemple `libbroadcastcore.so`. Lorsque vous signalez ces plantages à l’équipe SDK IVS, des traces de pile de meilleure qualité facilitent la résolution des problèmes.

Pour utiliser cette version du kit SDK, ajoutez la ligne suivante dans vos fichiers de configuration Gradle :

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

Utilisez la ligne ci-dessus au lieu de celle-ci :

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

### Téléchargement des symboles vers Firebase Crashlytics
<a name="android-debug-symbols-rt-firebase-crashlytics"></a>

Assurez-vous que vos fichiers de configuration Gradle sont bien configurés pour Firebase Crashlytics. Suivez les instructions de Google ici :

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

Veillez à ajouter `com.google.firebase:firebase-crashlytics-ndk` en tant que dépendance.

Lorsque vous créez votre application en vue de sa publication, le plug-in Firebase Crashlytics devrait automatiquement télécharger les symboles. Pour ce faire, exécutez l’une des commandes suivantes :

```
gradle uploadCrashlyticsSymbolFileRelease
```

```
./gradlew uploadCrashlyticsSymbolFileRelease
```

(Il n’y a aucun risque si les symboles sont téléchargés deux fois, automatiquement et manuellement.)

### Prévention de l’augmentation de la taille du fichier .apk en version finale
<a name="android-debug-symbols-rt-sizing-apk"></a>

Avant de générer le fichier `.apk` en version finale, le plug-in Android Gradle tente automatiquement de supprimer les informations de débogage des bibliothèques partagées (y compris la bibliothèque `libbroadcastcore.so` du kit SDK de diffusion IVS). Cependant, cela ne fonctionne pas toujours. En conséquence, votre fichier `.apk` pourrait augmenter en taille et vous pourriez recevoir un message d’avertissement du plug-in Android Gradle indiquant qu’il n’a pas pu supprimer les symboles de débogage et qu’il inclut les fichiers `.so` tels quels. Si cela se produit, procédez comme suit :
+ Installez un NDK Android. Toute version récente fonctionnera.
+ Ajoutez `ndkVersion <your_installed_ndk_version_number>` au fichier `build.gradle` de votre application. Procédez ainsi même si votre application elle-même ne contient pas de code natif.

Pour plus d’informations, consultez ce [problème connu](https://issuetracker.google.com/issues/353554169).

## Demander des autorisations
<a name="broadcast-android-permissions"></a>

Votre appli doit demander l’autorisation d’accéder à la caméra et au micro de l’utilisateur. (Ce n’est pas spécifique à Amazon IVS ; cette autorisation est requise pour toute application devant accéder aux caméras et aux micros.)

Ici, nous vérifions si l’utilisateur a déjà accordé des autorisations et, dans le cas contraire, nous les demandons :

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

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

Ici, nous obtenons la réponse de l’utilisateur :

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

# Publication et abonnement avec le SDK de diffusion Android IVS \$1 Streaming en temps réel
<a name="android-publish-subscribe"></a>

Ce document explique les étapes nécessaires pour la publication et l'abonnement à une étape à l'aide du SDK de diffusion Android IVS en temps réel.

## Concepts
<a name="android-publish-subscribe-concepts"></a>

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

### Étape
<a name="android-publish-subscribe-concepts-stage"></a>

La classe `Stage` est le principal point d’interaction entre l’application hôte et le kit SDK. Il représente l’étape elle-même et est utilisé pour rejoindre et quitter l’étape. La création et la participation à une étape nécessitent une chaîne de jetons valide et non expirée provenant du plan de contrôle (représentée par `token`). Il est très facile de rejoindre une étape et de la quitter. 

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

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

stage.leave();
```

La classe `Stage` est également l’endroit où vous pouvez joindre le `StageRenderer` :

```
stage.addRenderer(renderer); // multiple renderers can be added
```

### Strategy
<a name="android-publish-subscribe-concepts-strategy"></a>

L’interface `Stage.Strategy` permet à l’application hôte de communiquer l’état de l’étape souhaité au kit SDK. Trois fonctions doivent être mises en œuvre : `shouldSubscribeToParticipant`, `shouldPublishFromParticipant` et `stageStreamsToPublishForParticipant`. Elles sont toutes abordées ci-dessous.

#### Abonnement aux participants
<a name="android-publish-subscribe-concepts-strategy-participants"></a>

```
Stage.SubscribeType shouldSubscribeToParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo);
```

Lorsqu’un participant distant rejoint l’étape, le kit SDK interroge l’application hôte sur l’état de l’abonnement souhaité pour ce participant. Les options sont `NONE`, `AUDIO_ONLY` et `AUDIO_VIDEO`. Lors d’un renvoi d’une valeur pour cette fonction, l’application hôte n’a pas à se soucier de l’état de publication, de l’état actuel de l’abonnement ou de l’état de la connexion de l’étape. Si `AUDIO_VIDEO` est renvoyé, le kit SDK attend que le participant distant effectue une publication avant de s’abonner, puis il met à jour l’application hôte par le biais du moteur de rendu tout au long du processus.

Voici un exemple d’implémentation :

```
@Override
Stage.SubscribeType shouldSubscribeToParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo) {
	return Stage.SubscribeType.AUDIO_VIDEO;
}
```

Il s’agit de l’implémentation complète de cette fonction pour une application hôte qui souhaite toujours que tous les participants se voient mutuellement, comme une application de chat vidéo.

Des implémentations plus avancées sont également possibles. Utilisez la propriété `userInfo` sur `ParticipantInfo` pour vous abonner de manière sélective aux participants en fonction des attributs fournis par le serveur :

```
@Override
Stage.SubscribeType shouldSubscribeToParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo) {
	switch(participantInfo.userInfo.get(“role”)) {
		case “moderator”:
			return Stage.SubscribeType.NONE;
		case “guest”:
			return Stage.SubscribeType.AUDIO_VIDEO;
		default:
			return Stage.SubscribeType.NONE;
	}
}
```

Elle peut servir à créer une scène où les modérateurs peuvent surveiller tous les invités sans être vus ou entendus. L’application hôte pourrait utiliser une logique métier supplémentaire pour permettre aux modérateurs de se voir mutuellement tout en restant invisible pour les invités.

#### Configuration d’abonnement aux participants
<a name="android-publish-subscribe-concepts-strategy-participants-config"></a>

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

[Lorsqu’un participant distant est abonné (voir Abonnement aux participants](#android-publish-subscribe-concepts-strategy-participants)), le kit SDK interroge l’application hôte sur une configuration d’abonnement personnalisée pour ce participant. Cette configuration est facultative et permet à l’application hôte de contrôler certains aspects du comportement de l’abonné. Pour plus d’informations sur les options de configuration, consultez [SubscribeConfiguration](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/interfaces/SubscribeConfiguration) dans la documentation de référence du kit SDK.

Voici un exemple d’implémentation :

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

    config.jitterBuffer.setMinDelay(JitterBufferConfiguration.JitterBufferDelay.MEDIUM());

    return config;
}
```

Cette implémentation met à jour le délai minimum de la mémoire tampon pour tous les participants abonnés à un préréglage de `MEDIUM`.

Comme pour `shouldSubscribeToParticipant`, des implémentations plus avancées sont possibles. `ParticipantInfo` peut être utilisé pour mettre à jour de manière sélective la configuration d’abonnement pour des participants spécifiques.

Nous recommandons d’utiliser les comportements par défaut. Spécifiez une configuration personnalisée uniquement si vous souhaitez modifier un comportement particulier.

#### Publication
<a name="android-publish-subscribe-concepts-strategy-publishing"></a>

```
boolean shouldPublishFromParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo);
```

Une fois connecté à l’étape, le kit SDK interroge l’application hôte pour savoir si un participant en particulier doit effectuer une publication. Elle n’est invoquée que pour les participants locaux autorisés à publier sur la base du jeton fourni.

Voici un exemple d’implémentation :

```
@Override
boolean shouldPublishFromParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo) {
	return true;
}
```

Il s’agit d’une application de chat vidéo standard dans laquelle les utilisateurs souhaitent toujours publier. Ils peuvent désactiver et réactiver leur son et leur vidéo pour être instantanément masqués ou vus/entendus. (Ils peuvent également utiliser la fonction de publication/d’annulation de la publication, mais c’est beaucoup plus lent. Il est préférable de désactiver ou de réactiver le son dans les cas d’utilisation où il est souvent souhaitable de modifier la visibilité.)

#### Choix des flux à publier
<a name="android-publish-subscribe-concepts-strategy-streams"></a>

```
@Override
List<LocalStageStream> stageStreamsToPublishForParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo);
}
```

Lors de la publication, cette fonction est utilisée pour déterminer les flux audio et vidéo à publier. Ce point est abordé plus en détail dans la section [Publier un flux multimédia](#android-publish-subscribe-publish-stream).

#### Mise à jour de la stratégie
<a name="android-publish-subscribe-concepts-strategy-updates"></a>

La stratégie se veut dynamique : les valeurs renvoyées par n’importe laquelle des fonctions ci-dessus peuvent être modifiées à tout moment. Par exemple, si l’application hôte ne souhaite pas publier tant que l’utilisateur final n’a pas appuyé sur un bouton, vous pouvez renvoyer une variable depuis `shouldPublishFromParticipant` (quelque chose comme `hasUserTappedPublishButton`). Lorsque cette variable change en fonction d’une interaction de l’utilisateur final, appelez `stage.refreshStrategy()` pour signaler au kit SDK qu’il doit interroger la stratégie pour connaître les dernières valeurs, en appliquant uniquement les éléments qui ont changé. Si le kit SDK constate que la valeur `shouldPublishFromParticipant` a changé, il lance le processus de publication. Si les requêtes du kit SDK et toutes les fonctions renvoient la même valeur qu’auparavant, l’appel `refreshStrategy` n’apportera aucune modification à l’étape.

Si la valeur de retour de `shouldSubscribeToParticipant` passe de `AUDIO_VIDEO` à `AUDIO_ONLY`, le flux vidéo sera supprimé pour tous les participants dont les valeurs renvoyées ont été modifiées, s’il existait déjà un flux vidéo.

En général, l’étape utilise la stratégie pour appliquer au mieux la différence entre les stratégies précédentes et actuelles, sans que l’application hôte n’ait à se soucier de tout l’état requis pour la gérer correctement. Pour cette raison, et pour réduire les frais, envisagez d’appeler `stage.refreshStrategy()`, car cela ne fait rien à moins que la stratégie ne change.

### Moteur de rendu
<a name="android-publish-subscribe-concepts-renderer"></a>

L’interface `StageRenderer` communique l’état de l’étape à l’application hôte. Les mises à jour de l’interface utilisateur de l’application hôte peuvent généralement être entièrement optimisées par les événements fournis par le moteur de rendu. Le moteur de rendu fournit les fonctions suivantes :

```
void onParticipantJoined(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo);

void onParticipantLeft(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo);

void onParticipantPublishStateChanged(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull Stage.PublishState publishState);

void onParticipantSubscribeStateChanged(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull Stage.SubscribeState subscribeState);

void onStreamsAdded(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull List<StageStream> streams);

void onStreamsRemoved(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull List<StageStream> streams);

void onStreamsMutedChanged(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull List<StageStream> streams);

void onError(@NonNull BroadcastException exception);

void onConnectionStateChanged(@NonNull Stage stage, @NonNull Stage.ConnectionState state, @Nullable BroadcastException exception);
                
void onStreamAdaptionChanged(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull RemoteStageStream stream, boolean adaption);

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

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

Pour la plupart de ces méthodes, les `Stage` et `ParticipantInfo` correspondantes sont fournies.

Les informations fournies par le moteur de rendu ne devraient pas avoir d’impact sur les valeurs de retour de la stratégie. Par exemple, la valeur de retour de `shouldSubscribeToParticipant` ne devrait pas changer lors de l’appel de `onParticipantPublishStateChanged`. Si l’application hôte souhaite s’abonner à un participant en particulier, elle doit renvoyer le type d’abonnement souhaité, quel que soit l’état de publication de ce participant. Le kit SDK est chargé de s’assurer que l’état souhaité de la stratégie est appliqué au bon moment en fonction de l’état de l’étape.

Le `StageRenderer` peut être attaché à la classe de l’étape :

```
stage.addRenderer(renderer); // multiple renderers can be added
```

Notez que seuls les participants à la publication déclenchent `onParticipantJoined` et que chaque fois qu’un participant arrête de publier ou quitte la session de l’étape, `onParticipantLeft` est déclenché.

## Publier un flux multimédia
<a name="android-publish-subscribe-publish-stream"></a>

Les appareils locaux tels que les microphones et les caméras intégrés sont découverts via `DeviceDiscovery`. Voici un exemple de sélection de la caméra frontale et du microphone par défaut, puis de leur renvoi en tant que `LocalStageStreams` à publier par le SDK :

```
DeviceDiscovery deviceDiscovery = new DeviceDiscovery(context);

List<Device> devices = deviceDiscovery.listLocalDevices();
List<LocalStageStream> publishStreams = new ArrayList<LocalStageStream>();

Device frontCamera = null;
Device microphone = null;

// Create streams using the front camera, first microphone
for (Device device : devices) {
	Device.Descriptor descriptor = device.getDescriptor();
	if (!frontCamera && descriptor.type == Device.Descriptor.DeviceType.Camera && descriptor.position = Device.Descriptor.Position.FRONT) {
		front Camera = device;
	}
	if (!microphone && descriptor.type == Device.Descriptor.DeviceType.Microphone) {
		microphone = device;
	}
}

ImageLocalStageStream cameraStream = new ImageLocalStageStream(frontCamera);
AudioLocalStageStream microphoneStream = new AudioLocalStageStream(microphoneDevice);

publishStreams.add(cameraStream);
publishStreams.add(microphoneStream);

// Provide the streams in Stage.Strategy
@Override
@NonNull List<LocalStageStream> stageStreamsToPublishForParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo) {
	return publishStreams;
}
```

## Afficher et supprimer des participants
<a name="android-publish-subscribe-participants"></a>

Une fois l’abonnement effectué, vous recevrez un tableau d’objets `StageStream` via la fonction `onStreamsAdded` du moteur de rendu. Vous pouvez récupérer l’aperçu à partir d’un `ImageStageStream` :

```
ImagePreviewView preview = ((ImageStageStream)stream).getPreview();

// Add the view to your view hierarchy
LinearLayout previewHolder = findViewById(R.id.previewHolder);
preview.setLayoutParams(new LinearLayout.LayoutParams(
		LinearLayout.LayoutParams.MATCH_PARENT,
		LinearLayout.LayoutParams.MATCH_PARENT));
previewHolder.addView(preview);
```

Vous pouvez récupérer les statistiques au niveau de l’audio à partir d’un `AudioStageStream` :

```
((AudioStageStream)stream).setStatsCallback((peak, rms) -> {
	// handle statistics
});
```

Lorsqu’un participant arrête de publier ou en cas de désabonnement, la fonction `onStreamsRemoved` est appelée avec les flux qui ont été supprimés. Les applications hôte doivent utiliser ce signal pour supprimer le flux vidéo du participant de la hiérarchie des vues.

`onStreamsRemoved` est invoqué pour tous les scénarios dans lesquels un flux peut être supprimé, notamment : 
+ Le participant distant arrête de publier.
+ Un appareil local se désabonne ou modifie l’abonnement de `AUDIO_VIDEO` en `AUDIO_ONLY`.
+ Le participant distant quitte l’étape.
+ Le participant local quitte l’étape.

Comme `onStreamsRemoved` est invoqué pour tous les scénarios, aucune logique métier personnalisée n’est requise pour supprimer des participants de l’interface utilisateur lors des opérations de départ locales ou à distance.

## Désactiver et réactiver le son des flux de médias sociaux
<a name="android-publish-subscribe-mute-streams"></a>

Les objets `LocalStageStream` ont une fonction `setMuted` qui contrôle si le son du flux est désactivé. Cette fonction peut être appelée sur le flux avant ou après son renvoi par la fonction de stratégie `streamsToPublishForParticipant`.

**Important** : si une nouvelle instance d’objet `LocalStageStream` est renvoyée par `streamsToPublishForParticipant` après un appel à `refreshStrategy`, l’état muet du nouvel objet de flux est appliqué à l’étape. Lorsque vous créez des instances `LocalStageStream`, veillez à ce que l’état muet attendu soit conservé.

## Surveiller l’état muet du contenu multimédia des participants distants
<a name="android-publish-subscribe-mute-state"></a>

Lorsqu’un participant modifie l’état muet de son flux vidéo ou audio, la fonction `onStreamMutedChanged` du moteur de rendu est invoquée avec une liste des flux qui ont changé. Utilisez la méthode `getMuted` sur `StageStream` pour mettre à jour votre interface utilisateur en conséquence. 

```
@Override
void onStreamsMutedChanged(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull List<StageStream> streams) {
	for (StageStream stream : streams) {
		boolean muted = stream.getMuted();
		// handle UI changes
	}
}
```

## Obtenir les statistiques WebRTC
<a name="android-publish-subscribe-webrtc-stats"></a>

Pour obtenir les dernières statistiques WebRTC relatives à un flux de publication ou d’abonnement, utilisez `requestRTCStats` sur `StageStream`. Lorsqu’une collecte est réalisée, vous recevez des statistiques via le `StageStream.Listener` que vous pouvez définir sur `StageStream`.

```
stream.requestRTCStats();

@Override
void onRTCStats(Map<String, Map<String, String>> statsMap) {
	for (Map.Entry<String, Map<String, string>> stat : statsMap.entrySet()) {
		for(Map.Entry<String, String> member : stat.getValue().entrySet()) {
			Log.i(TAG, stat.getKey() + “ has member “ + member.getKey() + “ with value “ + member.getValue());
		}
	}
}
```

## Obtenir les attributs des participants
<a name="android-publish-subscribe-participant-attributes"></a>

Si vous spécifiez des attributs dans la demande de l’opération `CreateParticipantToken`, vous pouvez les voir dans les propriétés `ParticipantInfo` :

```
@Override
void onParticipantJoined(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo) {
	for (Map.Entry<String, String> entry : participantInfo.userInfo.entrySet()) {
		Log.i(TAG, “attribute: “ + entry.getKey() + “ = “ + entry.getValue());
	}
}
```

## Messages intégrés
<a name="android-publish-subscribe-embed-messages"></a>

La méthode `embedMessage` sur ImageDevice permet d’insérer des données utiles de métadonnées directement dans les images vidéo lors de la publication. Cela permet une messagerie synchronisée avec les images pour les applications en temps réel. L’intégration de messages est disponible uniquement lors de l’utilisation du SDK pour la publication en temps réel (et non pour la publication à faible latence).

Les messages intégrés ne sont pas garantis d’arriver aux abonnés, car ils sont intégrés directement dans les images vidéo et transmis via UDP, qui ne garantit pas la livraison des paquets. La perte de paquets pendant la transmission peut entraîner la perte de messages, en particulier dans des conditions réseau médiocres. Pour atténuer ce problème, la méthode `embedMessage` inclut un paramètre `repeatCount` qui duplique le message sur plusieurs images consécutives, augmentant ainsi la fiabilité de la livraison. Cette fonctionnalité est disponible uniquement pour les flux vidéo.

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

Les clients de publication peuvent intégrer des données utiles de messages dans leur flux vidéo en utilisant la méthode `embedMessage` sur ImageDevice. La taille des données utiles doit être supérieure à 0 Ko et inférieure à 1 Ko. Le nombre de messages intégrés insérés par seconde ne doit pas dépasser 10 Ko. 

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

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

### Répétition des données utiles de messages
<a name="android-embed-messages-repeat-payloads"></a>

Utilisez `repeatCount` pour dupliquer le message sur plusieurs images afin d’améliorer la fiabilité. La valeur doit être comprise entre 0 et 30. Les clients destinataires doivent disposer d'une logique permettant de dédupliquer le message.

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

### Lecture des messages intégrés
<a name="android-embed-messages-read-messages"></a>

Consultez la section « Obtenir des informations supplémentaires sur les améliorations (SEI) » ci-dessous pour savoir comment lire les messages intégrés à partir des flux entrants.

## Obtenir des informations supplémentaires sur les améliorations (SEI)
<a name="android-publish-subscribe-sei-attributes"></a>

L’unité NAL d’informations supplémentaires sur les améliorations (SEI) est utilisée pour stocker des métadonnées alignées sur l’image à côté de la vidéo. Les clients abonnés peuvent lire les données utiles SEI d’un diffuseur de publication qui publie une vidéo H.264 en inspectant la propriété `embeddedMessages` sur les objets `ImageDeviceFrame` provenant de l’objet `ImageDevice` du diffuseur de publication. Pour ce faire, acquérez `ImageDevice` d’un diffuseur de publication, puis observez chaque image via un rappel fourni à `setOnFrameCallback`, comme le montre l’exemple suivant :

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

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

## Poursuivre la session en arrière-plan
<a name="android-publish-subscribe-background-session"></a>

Lorsque l’application passe en arrière-plan, vous souhaiterez peut-être vous abonner uniquement au contenu audio des autres participants distants ou arrêter de le publier. Pour ce faire, mettez à jour votre implémentation `Strategy` pour arrêter la publication et abonnez-vous à `AUDIO_ONLY` (ou `NONE`, le cas échéant).

```
// Local variables before going into the background
boolean shouldPublish = true;
Stage.SubscribeType subscribeType = Stage.SubscribeType.AUDIO_VIDEO;

// Stage.Strategy implementation
@Override
boolean shouldPublishFromParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo) {
	return shouldPublish;
}

@Override
Stage.SubscribeType shouldSubscribeToParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo) {
	return subscribeType;
}

// In our Activity, modify desired publish/subscribe when we go to background, then call refreshStrategy to update the stage
@Override
void onStop() {
	super.onStop();
	shouldPublish = false;
	subscribeTpye = Stage.SubscribeType.AUDIO_ONLY;
	stage.refreshStrategy();
}
```

## Encodage en couches avec Simulcast
<a name="android-publish-subscribe-layered-encoding-simulcast"></a>

L’encodage en couches avec diffusion simultanée est une fonctionnalité de diffusion en temps réel d’IVS qui permet aux diffuseurs d’envoyer plusieurs couches de vidéo de qualité différente et aux abonnés de configurer ces couches de manière dynamique ou manuelle. Cette fonctionnalité est décrite plus en détail dans le document [Optimisations de la diffusion](real-time-streaming-optimization.md).

### Configuration du codage en couches (diffuseur de publication)
<a name="android-layered-encoding-simulcast-configure-publisher"></a>

En tant que diffuseur de publication, pour activer l’encodage en couches avec la diffusion simultanée, ajoutez la configuration suivante à votre `LocalStageStream` lors de l’instanciation :

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

ImageLocalStageStream cameraStream = new ImageLocalStageStream(frontCamera, config);

// Other Stage implementation code
```

En fonction de la résolution que vous avez définie dans la configuration vidéo, un certain nombre de couches seront encodées et envoyées comme défini dans la section [Couches, qualités et fréquences d’images par défaut](real-time-streaming-optimization.md#real-time-streaming-optimization-default-layers) de la section *Optimisations de la diffusion*.

Vous avez également la possibilité de configurer des couches individuelles depuis la configuration de diffusion simultanée : 

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

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

config.simulcast.setLayers(simulcastLayers);

ImageLocalStageStream cameraStream = new ImageLocalStageStream(frontCamera, config);

// Other Stage implementation code
```

Vous pouvez également créer vos propres configurations de couches personnalisées pour un maximum de trois couches. Si vous fournissez un tableau vide ou ne contenant aucune valeur, les valeurs par défaut décrites ci-dessus sont utilisées. Les couches sont décrites avec les propriétés setter requises suivantes :
+ `setSize: Vec2;`
+ `setMaxBitrate: integer;`
+ `setMinBitrate: integer;`
+ `setTargetFramerate: integer;`

À partir des préréglages, vous pouvez remplacer des propriétés individuelles ou créer une toute nouvelle configuration :

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

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

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

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

config.simulcast.setLayers(simulcastLayers);

ImageLocalStageStream cameraStream = new ImageLocalStageStream(frontCamera, config);

// Other Stage implementation code
```

Pour connaître les valeurs maximales, les limites et les erreurs qui peuvent être déclenchées lors de la configuration de couches individuelles, consultez la documentation de référence du kit SDK.

### Configuration de l’encodage en couches (abonné)
<a name="android-layered-encoding-simulcast-configure-subscriber"></a>

En tant qu’abonné, il n’est pas nécessaire d’activer l’encodage en couches. Si un diffuseur de publication envoie des couches de diffusion simultanée, le serveur s’adapte dynamiquement entre les couches pour choisir la qualité optimale en fonction de l’appareil de l’abonné et des conditions du réseau.

Sinon, pour choisir les couches explicites que le diffuseur de publication envoie, il existe plusieurs options, décrites ci-dessous.

### Option 1 : préférence pour la qualité de la couche initiale
<a name="android-layered-encoding-simulcast-layer-quality-preference"></a>

En utilisant la stratégie `subscribeConfigurationForParticipant`, il est possible de choisir la couche initiale que vous voulez recevoir en tant qu’abonné :

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

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

    return config;
}
```

Par défaut, les abonnés reçoivent toujours d’abord la couche de qualité la plus faible, puis la couche de qualité la plus élevée. Cela permet d’optimiser la consommation de bande passante de l’utilisateur final et d’offrir le meilleur temps d’accès à la vidéo, réduisant ainsi les blocages initiaux de la vidéo pour les utilisateurs sur des réseaux plus faibles.

Ces options sont disponibles pour `InitialLayerPreference` :
+ `LOWEST_QUALITY` : le serveur diffuse d’abord la couche de vidéo de qualité la plus faible. Cela permet d’optimiser la consommation de la bande passante ainsi que le temps d’accès au contenu multimédia. La qualité est définie comme la combinaison de la taille, du débit binaire et de la fréquence d’images de la vidéo. Par exemple, une vidéo 720p est de moins bonne qualité qu’une vidéo 1080p.
+ `HIGHEST_QUALITY` : le serveur délivre d’abord la couche de vidéo de la plus haute qualité. Cela permet d’optimiser la qualité, mais peut augmenter le temps d’accès au contenu multimédia. La qualité est définie comme la combinaison de la taille, du débit binaire et de la fréquence d’images de la vidéo. Par exemple, la vidéo 1080p est de meilleure qualité que la vidéo 720p.

**Remarque :** pour que les préférences de couche initiale (l’appel `setInitialLayerPreference`) prennent effet, un nouvel abonnement est nécessaire, car ces mises à jour ne s’appliquent pas à l’abonnement actif.

### Option 2 : couche préférée pour le flux
<a name="android-layered-encoding-simulcast-preferred-layer"></a>

La méthode de stratégie `preferredLayerForStream` vous permet de sélectionner une couche après le démarrage du flux. Cette méthode de stratégie reçoit les informations relatives au participant et au flux, ce qui vous permet de sélectionner une couche participant par participant. Le kit SDK appelle cette méthode en réponse à des événements spécifiques, par exemple lorsque les couches de flux changent, que l’état des participants change ou que l’application hôte actualise la stratégie.

La méthode de stratégie renvoie un objet `RemoteStageStream.Layer`, qui peut être l’un des suivants :
+ Un objet de couche, tel qu’un objet renvoyé par `RemoteStageStream.getLayers`.
+ null, qui indique qu’aucune couche ne doit être sélectionnée et que l’adaptation dynamique est privilégiée.

Par exemple, dans la stratégie suivante, les utilisateurs sélectionneront toujours la couche de vidéo de la plus basse qualité disponible :

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

Pour réinitialiser la sélection des couches et revenir à l’adaptation dynamique, renvoyez null ou undefined dans la stratégie. Dans cet exemple, `appState` est une variable d’espace réservé qui représente l’état de l’application hôte.

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

### Option 3 : aides pour les couches de RemoteStageStream
<a name="android-layered-encoding-simulcast-remotestagestream-helpers"></a>

`RemoteStageStream` possède plusieurs aides qui peuvent être utilisées pour prendre des décisions concernant la sélection des couches et afficher les sélections correspondantes aux utilisateurs finaux :
+ **Événements relatifs aux couches** : tout comme `StageRenderer`, `RemoteStageStream.Listener` possède des événements qui communiquent les modifications apportées aux couches et à l’adaptation de la diffusion simultanée :
  + `void onAdaptionChanged(boolean adaption)`
  + `void onLayersChanged(@NonNull List<Layer> layers)`
  + `void onLayerSelected(@Nullable Layer layer, @NonNull LayerSelectedReason reason)`
+ **Méthodes relatives aux couches** : `RemoteStageStream` possède plusieurs méthodes d’aide qui peuvent être utilisées pour obtenir des informations sur le flux et les couches présentées. Ces méthodes sont disponibles sur le flux distant fourni dans la stratégie `preferredLayerForStream`, ainsi que sur les flux distants exposés via `StageRenderer.onStreamsAdded`.
  + `stream.getLayers`
  + `stream.getSelectedLayer`
  + `stream.getLowestQualityLayer`
  + `stream.getHighestQualityLayer`
  + `stream.getLayersWithConstraints`

Pour plus de détails, consultez la classe `RemoteStageStream` dans la [documentation de référence du kit SDK](https://aws.github.io/amazon-ivs-broadcast-docs/latest/android/) (français non garanti). Pour la raison `LayerSelected`, si le message `UNAVAILABLE` est renvoyé, cela indique que la couche demandée n'a pas pu être sélectionnée. La meilleure sélection est faite à sa place, qui est généralement une couche de qualité inférieure pour maintenir la stabilité du flux.

## Limitations relatives à la configuration vidéo
<a name="android-publish-subscribe-video-limits"></a>

Le kit SDK ne permet pas de forcer l’utilisation du mode portrait ou paysage à l’aide de `StageVideoConfiguration.setSize(BroadcastConfiguration.Vec2 size)`. En orientation portrait, la plus petite dimension est utilisée comme largeur, tandis qu’en orientation paysage, il s’agit de la hauteur. Cela signifie que les deux appels suivants à `setSize` ont le même effet sur la configuration vidéo :

```
StageVideo Configuration config = new StageVideo Configuration();

config.setSize(BroadcastConfiguration.Vec2(720f, 1280f);
config.setSize(BroadcastConfiguration.Vec2(1280f, 720f);
```

## Gestion des problèmes de réseau
<a name="android-publish-subscribe-network-issues"></a>

En cas de perte de la connexion réseau de l’appareil local, le kit SDK essaie de se reconnecter en interne sans aucune action de l’utilisateur. Dans certains cas, le kit SDK échoue et une action de l’utilisateur est requise. Deux erreurs principales sont liées à la perte de la connexion réseau :
+ Code d’erreur 1400, message : « PeerConnection perdue en raison d’une erreur réseau inconnue »
+ Code d’erreur 1300, message : « Le nombre de nouvelles tentatives est épuisé »

Si la première erreur est reçue, mais pas la seconde, cela signifie que le kit SDK est toujours connecté à l’étape et qu’il essaiera de rétablir ses connexions automatiquement. Par mesure de sécurité, vous pouvez appeler `refreshStrategy` sans modifier les valeurs de retour de la méthode stratégique, afin de déclencher une tentative de reconnexion manuelle.

Si la deuxième erreur est reçue, les tentatives de reconnexion du kit SDK ont échoué et l’appareil local n’est plus connecté à l’étape. Dans ce cas, essayez de rejoindre l’étape en appelant `join` une fois votre connexion réseau rétablie.

En général, le fait de rencontrer des erreurs après avoir correctement rejoint une étape indique que le kit SDK n’a pas réussi à rétablir la connexion. Créez un objet `Stage` et essayez de le joindre lorsque les conditions du réseau s’améliorent.

## Utilisation de microphones Bluetooth
<a name="android-publish-subscribe-bluetooth-microphones"></a>

Pour publier à l’aide de microphones Bluetooth, vous devez établir une connexion Bluetooth SCO :

```
Bluetooth.startBluetoothSco(context);
// Now bluetooth microphones can be used
…
// Must also stop bluetooth SCO
Bluetooth.stopBluetoothSco(context);
```

# Problèmes connus et solutions de contournement dans le SDK de diffusion Android IVS \$1 Streaming en temps réel
<a name="broadcast-android-known-issues"></a>

Ce document répertorie les problèmes connus que vous pouvez rencontrer lors de l’utilisation du kit SDK de diffusion Android en temps réel d’Amazon IVS et propose des solutions de contournement potentielles.
+ Lorsqu’un appareil Android se met en veille et en sort, il est possible que l’aperçu soit figé.

  **Solution de contournement :** créez et utilisez une `Stage`.
+ Lorsqu’un participant rejoint la session avec un jeton utilisé par un autre participant, la première connexion est déconnectée sans erreur spécifique.

  **Solution de contournement :** aucune. 
+ Dans de rares cas, le diffuseur de publication publie, alors que l’état de la publication reçu par les abonnés est `inactive`.

  **Solution de contournement :** essayez de quitter la session, puis de la rejoindre. Si le problème persiste, créez un jeton pour le diffuseur de publication.
+ Un problème rare de distorsion audio peut survenir par intermittence pendant une session d’étape, généralement lors d’appels de plus longue durée.

  **Solution de contournement :** le participant dont le son est déformé peut soit quitter la session et la rejoindre, soit annuler la publication et republier son contenu audio pour ainsi corriger le problème.
+ Les microphones externes ne sont pas pris en charge lors de la publication sur une étape.

  **Solution de contournement :** n’utilisez pas de microphone externe connecté via USB pour publier sur une étape.
+ La publication sur une étape avec le partage d’écran en utilisant `createSystemCaptureSources` n’est pas prise en charge.

  **Solution de contournement :** gérez la capture du système manuellement à l’aide de sources d’entrée d’image personnalisées et de sources d’entrée audio personnalisées.
+ Lorsqu’une `ImagePreviewView` est supprimée d’un parent (par exemple, `removeView()` est appelée au niveau du parent), la `ImagePreviewView` est immédiatement lancée. La `ImagePreviewView` n’affiche aucun cadre lorsqu’elle est ajoutée à une autre vue parent.

  **Solution de contournement :** demandez un autre aperçu à l’aide de `getPreview`.
+ Lorsque vous rejoignez une étape avec un Samsung Galaxy S22/\$1 doté d’Android 12, vous pouvez rencontrer une erreur 1401 et l’appareil local ne parvient pas à rejoindre l’étape, ou le fait sans émettre de son.

  **Solution de contournement :** passez à Android 13.
+ Lorsque vous rejoignez une étape avec un Nokia X20 sous Android 13, il se peut que l’appareil photo ne parvienne pas à s’ouvrir et une exception est déclenchée.

  **Solution de contournement :** aucune.
+ Il se peut que les appareils équipés du chipset MediaTek Helio n’affichent pas correctement les vidéos des participants distants.

  **Solution de contournement :** aucune.
+ Sur certains appareils, le système d’exploitation de l’appareil peut choisir un microphone différent de celui sélectionné via le kit SDK. Cela est dû au fait que le kit SDK de diffusion Amazon IVS ne peut pas contrôler la façon dont la route audio `VOICE_COMMUNICATION` est définie, car elle varie en fonction des fabricants d’appareils.

  **Solution de contournement :** aucune.
+ Certains encodeurs vidéo Android ne peuvent pas être configurés avec une taille vidéo inférieure à 176x176. Configurer une taille plus petite entraîne une erreur et empêche la diffusion.

  **Solution :** ne configurez pas la taille vidéo à moins de 176x176.

# Gestion des erreurs dans le SDK de diffusion Android IVS \$1 Streaming en temps réel
<a name="broadcast-android-error-handling"></a>

Cette section donne un aperçu des conditions d’erreur, de la façon dont le SDK de diffusion Android IVS en temps réel les signale à l’application et des actions que l’application doit prendre lorsqu’elles se produisent.

## Erreurs fatales ou non fatales
<a name="broadcast-android-fatal-vs-nonfatal-errors"></a>

L’objet d’erreur possède un champ booléen « est fatal » de `BroadcastException`.

En général, les erreurs fatales sont liées à la connexion au serveur Stages (soit la connexion ne peut pas être établie, soit elle est perdue et ne peut pas être rétablie). L’application doit recréer la scène et la rejoindre, éventuellement avec un nouveau jeton ou lorsque la connectivité de l’appareil sera rétablie.

Les erreurs non fatales sont généralement liées à l’état de publication/d’abonnement et sont gérées par le kit SDK, qui relance l’opération de publication/abonnement.

Vous pouvez vérifier cette propriété :

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

## Erreurs de jonction
<a name="broadcast-android-stage-join-errors"></a>

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

Cela se produit lorsque le jeton d’étape est incorrect.

Le kit SDK génère une exception Java à partir d’un appel à `stage.join`, avec le code d’erreur = 1000 et fatal = vrai.

**Action** : créez un jeton valide et réessayez de vous connecter.

### Jeton expiré
<a name="broadcast-android-stage-join-errors-expired-token"></a>

Cela se produit lorsque le jeton d’étape est expiré.

Le kit SDK génère une exception Java à partir d’un appel à `stage.join`, avec le code d’erreur = 1001 et fatal = vrai.

**Action** : créez un nouveau jeton et réessayez de vous connecter.

### Jeton non valide ou révoqué
<a name="broadcast-android-stage-join-errors-invalid-token"></a>

Cela se produit lorsque le jeton d’étape n’est pas incorrect mais qu’il est rejeté par le serveur Stages. Cela est signalé de façon asynchrone via le moteur de rendu d’étape fourni par l’application.

Le kit SDK appelle `onConnectionStateChanged` avec une exception, avec le code d’erreur = 1026 et fatal = vrai.

**Action** : créez un jeton valide et réessayez de vous connecter.

### Erreurs réseau lors de la connexion initiale
<a name="broadcast-android-stage-join-errors-network-initial-join"></a>

Cela se produit lorsque le kit SDK ne parvient pas à contacter le serveur Stages pour établir une connexion. Cela est signalé de façon asynchrone via le moteur de rendu d’étape fourni par l’application.

Le kit SDK appelle `onConnectionStateChanged` avec une exception, avec le code d’erreur = 1300 et fatal = vrai.

**Action** : attendez que la connectivité de l’appareil soit rétablie et réessayez de vous connecter.

### Erreurs réseau lorsque vous êtes déjà connecté
<a name="broadcast-android-stage-join-errors-network-already-joined"></a>

En cas de perte de la connexion réseau de l’appareil, le kit SDK risque de perdre sa connexion aux serveurs Stage. Cela est signalé de façon asynchrone via le moteur de rendu d’étape fourni par l’application.

Le kit SDK appelle `onConnectionStateChanged` avec une exception, avec le code d’erreur = 1300 et fatal = vrai.

**Action** : attendez que la connectivité de l’appareil soit rétablie et réessayez de vous connecter.

## Erreurs de publication/d’abonnement
<a name="broadcast-android-publish-subscribe-errors"></a>

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

Il existe plusieurs erreurs :
+ MultihostSessionOfferCreationFailPublish (Échec de publication de l’offre de création de sessions multi-hôtes) (1020)
+ MultihostSessionOfferCreationFailSubscribe (Échec de la création de l’offre de session multi-hôtes) (1021)
+ MultihostSessionNoIceCandidates (Session multi-hôtes No Ice Candidats) (1022)
+ MultihostSessionStageAtCapacity (Étape de session multi-hôtes à pleine capacité) (1024)
+ SignallingSessionCannotRead (Impossible de lire la session de signalisation) (1201)
+ SignallingSessionCannotSend ((Impossible d’envoyer la session de signalisation) (1202)
+ SignallingSessionBadResponse (Mauvaise réponse de la session de signalisation) (1203)

Ceux-ci sont signalés de façon asynchrone via le moteur de rendu d’étape fourni par l’application.

Le kit SDK relance l’opération un nombre limité de fois. Lors des nouvelles tentatives, l’état de publication/d’abonnement est `ATTEMPTING_PUBLISH` / `ATTEMPTING_SUBSCRIBE`. Si les nouvelles tentatives réussissent, l’état passe à `PUBLISHED` / `SUBSCRIBED`.

Le kit SDK appelle `onError` avec le code d’erreur approprié et fatal = faux.

**Action** : aucune action n’est nécessaire, car le kit SDK réessaie automatiquement. Le cas échéant, l’application peut actualiser la stratégie pour forcer d’autres tentatives.

### Déjà établi, puis échec
<a name="broadcast-android-publish-subscribe-errors-established"></a>

Une publication ou un abonnement peut échouer une fois qu’il a été établi, probablement en raison d’une erreur réseau. Le code d’erreur pour une « connexion d’appairage perdue en raison d’une erreur réseau » est 1400.

Cela est signalé de façon asynchrone via le moteur de rendu d’étape fourni par l’application.

Le kit SDK relance l’opération de publication/d’abonnement. Lors des nouvelles tentatives, l’état de publication/d’abonnement est `ATTEMPTING_PUBLISH` / `ATTEMPTING_SUBSCRIBE`. Si les nouvelles tentatives réussissent, l’état passe à `PUBLISHED` / `SUBSCRIBED`.

Le kit SDK appelle `onError` avec le code d’erreur = 1400 et fatal = false.

**Action** : aucune action n’est nécessaire, car le kit SDK réessaie automatiquement. Le cas échéant, l’application peut actualiser la stratégie pour forcer d’autres tentatives. En cas de perte totale de connectivité, il est probable que la connexion à Stages échoue également.

# Kit SDK de diffusion IVS : Guide pour iOS \$1 Diffusion en temps réel
<a name="broadcast-ios"></a>

Le SDK de diffusion iOS par streaming en temps réel IVS permet aux participants d’envoyer et de recevoir des vidéos sur iOS.

Le module `AmazonIVSBroadcast` implémente l’interface décrite dans ce document. Les opérations suivantes sont prises en charge :
+ Rejoindre une étape 
+ Publier du contenu multimédia à l’intention des autres participants de l’étape
+ S’abonner à du contenu multimédia d’autres participants de l’étape
+ Gérer et surveiller la vidéo et le son publiés sur l’étape
+ Obtenir des statistiques WebRTC pour chaque connexion d’appairage
+ Toutes les opérations à partir du SDK de diffusion iOS par streaming à faible latence

**Dernière version du SDK de diffusion iOS :** 1.40.0 ([Notes de mise à jour](https://docs.aws.amazon.com/ivs/latest/RealTimeUserGuide/release-notes.html#mar12-26-broadcast-ios-rt)) 

**Documentation de référence :** pour plus d’informations sur les méthodes les plus importantes disponibles dans le kit SDK de diffusion iOS Amazon IVS, consultez la documentation de référence à l’adresse [https://aws.github.io/amazon-ivs-broadcast-docs/1.40.0/ios/](https://aws.github.io/amazon-ivs-broadcast-docs/1.40.0/ios/).

**Exemple de code :** voir l’exemple de référentiel iOS sur GitHub : [https://github.com/aws-samples/amazon-ivs-real-time-streaming-ios-samples](https://github.com/aws-samples/amazon-ivs-real-time-streaming-ios-samples).

**Exigences de la plateforme :** iOS 14\$1

# Démarrez avec le SDK de diffusion iOS IVS \$1 Streaming en temps réel
<a name="broadcast-ios-getting-started"></a>

Ce document explique les étapes nécessaires pour le démarrage avec le SDK de diffusion iOS IVS en temps réel.

## Installer la bibliothèque
<a name="broadcast-ios-install"></a>

Nous vous recommandons d’intégrer le kit SDK de diffusion via Swift Package Manager. (Vous pouvez également ajouter manuellement le cadre à votre projet.)

### Recommandé : intégrer le kit SDK de diffusion (Swift Package Manager)
<a name="broadcast-ios-install-swift"></a>

1. Téléchargez le fichier Package.swift depuis [https://broadcast.live-video.net/1.40.0/Package.swift](https://broadcast.live-video.net/1.40.0/Package.swift).

1. Dans votre projet, créez un nouveau répertoire nommé AmazonIVSBroadcast et ajoutez-le au contrôle de version.

1. Placez le fichier Package.swift téléchargé dans le nouveau répertoire.

1. Dans Xcode, accédez à **Fichier > Ajouter des dépendances de package** et sélectionnez **Ajouter un fichier local...**

1. Accédez au répertoire AmazonIVSBroadcast que vous avez créé, sélectionnez-le, puis sélectionnez **Ajouter un package**.

1. Lorsque vous êtes invité à **choisir les produits du package pour AmazonIVSBroadcast**, sélectionnez **AmazonIVSBroadcastStages** comme **Produit du package** en définissant la cible de votre application dans la section **Ajouter à la cible**.

1. Sélectionnez **Ajouter un package**.

**Important** : le SDK de diffusion par streaming en temps réel IVS inclut toutes les fonctionnalités du kit SDK de diffusion par streaming à faible latence. Il n’est pas possible d’intégrer les deux kits SDK dans le même projet.

### Autre approche : installer manuellement le cadre
<a name="broadcast-ios-install-manual"></a>

1. Téléchargez la dernière version depuis [https://broadcast.live-video.net/1.40.0/AmazonIVSBroadcast-Stages.xcframework.zip](https://broadcast.live-video.net/1.40.0/AmazonIVSBroadcast-Stages.xcframework.zip).

1. Extrayez le contenu de l’archive. `AmazonIVSBroadcast.xcframework` contient le kit SDK pour l’appareil et le simulateur.

1. Intégrez `AmazonIVSBroadcast.xcframework` en le faisant glisser dans la section **Frameworks, Libraries, and Embedded Content** (Cadre, bibliothèques et contenu intégré) de l’onglet **General** (Général) de votre cible d’application.  
![\[La section Frameworks, Libraries, and Embedded Content (Cadre, bibliothèques et contenu intégré) de l’onglet General (Général) de votre cible d’application.\]](http://docs.aws.amazon.com/fr_fr/ivs/latest/RealTimeUserGuide/images/iOS_Broadcast_SDK_Guide_xcframework.png)

## Demander des autorisations
<a name="broadcast-ios-permissions"></a>

Votre appli doit demander l’autorisation d’accéder à la caméra et au micro de l’utilisateur. (Ce n’est pas spécifique à Amazon IVS ; cette autorisation est requise pour toute application devant accéder aux caméras et aux micros.)

Ici, nous vérifions si l’utilisateur a déjà accordé des autorisations et, dans le cas contraire, nous les demandons :

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

Vous devez demander des autorisations pour les types de médias `.video` et `.audio` si vous souhaitez accéder aux caméras et aux microphones, respectivement.

Vous devez également ajouter des entrées pour `NSCameraUsageDescription` et `NSMicrophoneUsageDescription` à votre `Info.plist`. Sinon, votre application se bloquera lorsque vous essayerez de demander des autorisations.

## Désactiver le minuteur d’inactivité de l’application
<a name="broadcast-ios-disable-idle-timer"></a>

Cette action est facultative, mais recommandée. Elle empêche votre appareil de se mettre en veille lors de l’utilisation du kit SDK de diffusion, ce qui pourrait interrompre la diffusion.

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

# Publication et abonnement avec le SDK de diffusion iOS IVS \$1 Streaming en temps réel
<a name="ios-publish-subscribe"></a>

Ce document explique les étapes nécessaires pour la publication et l'abonnement à une étape à l'aide du SDK de diffusion iOS IVS en temps réel.

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

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

### Étape
<a name="ios-publish-subscribe-concepts-stage"></a>

La classe `IVSStage` est le principal point d’interaction entre l’application hôte et le kit SDK. La classe représente l’étape elle-même et est utilisée pour rejoindre et quitter l’étape. La création ou la participation à une étape nécessitent une chaîne de jetons valide et non expirée provenant du plan de contrôle (représentée par `token`). Il est très facile de rejoindre une étape et de la quitter.

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

try stage.join()

stage.leave()
```

La classe `IVSStage` est également l’endroit où vous pouvez joindre le `IVSStageRenderer` et le `IVSErrorDelegate` :

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

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

Le protocole `IVSStageStrategy` permet à l’application hôte de communiquer l’état de l’étape souhaité au kit SDK. Trois fonctions doivent être mises en œuvre :`shouldSubscribeToParticipant`, `shouldPublishParticipant` et `streamsToPublishForParticipant`. Elles sont toutes abordées ci-dessous.

#### Abonnement aux participants
<a name="ios-publish-subscribe-concepts-strategy-participants"></a>

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

Lorsqu’un participant distant rejoint une étape, le kit SDK interroge l’application hôte sur l’état de l’abonnement souhaité pour ce participant. Les options sont `.none`, `.audioOnly` et `.audioVideo`. Lors d’un renvoi d’une valeur pour cette fonction, l’application hôte n’a pas à se soucier de l’état de publication, de l’état actuel de l’abonnement ou de l’état de la connexion de l’étape. Si `.audioVideo` est renvoyé, le kit SDK attend que le participant distant effectue une publication avant de s’abonner, puis il met à jour l’application hôte par le biais du moteur de rendu tout au long du processus.

Voici un exemple d’implémentation :

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

Il s’agit de l’implémentation complète de cette fonction pour une application hôte qui souhaite toujours que tous les participants se voient mutuellement, comme une application de chat vidéo.

Des implémentations plus avancées sont également possibles. Utilisez la propriété `attributes` sur `IVSParticipantInfo` pour vous abonner de manière sélective aux participants en fonction des attributs fournis par le serveur :

```
func stage(_ stage: IVSStage, shouldSubscribeToParticipant participant: IVSParticipantInfo) -> IVSStageSubscribeType {
    switch participant.attributes["role"] {
    case "moderator": return .none
    case "guest": return .audioVideo
    default: return .none
    }
}
```

Elle peut servir à créer une scène où les modérateurs peuvent surveiller tous les invités sans être vus ou entendus. L’application hôte pourrait utiliser une logique métier supplémentaire pour permettre aux modérateurs de se voir mutuellement tout en restant invisible pour les invités.

#### Configuration d’abonnement aux participants
<a name="ios-publish-subscribe-concepts-strategy-participants-config"></a>

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

[Lorsqu’un participant distant est abonné (voir Abonnement aux participants](#ios-publish-subscribe-concepts-strategy-participants)), le kit SDK interroge l’application hôte sur une configuration d’abonnement personnalisée pour ce participant. Cette configuration est facultative et permet à l’application hôte de contrôler certains aspects du comportement de l’abonné. Pour plus d’informations sur les options de configuration, consultez [SubscribeConfiguration](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/interfaces/SubscribeConfiguration) dans la documentation de référence du kit SDK.

Voici un exemple d’implémentation :

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

    try! config.jitterBuffer.setMinDelay(.medium())

    return config
}
```

Cette implémentation met à jour le délai minimum de la mémoire tampon pour tous les participants abonnés à un préréglage de `MEDIUM`.

Comme pour `shouldSubscribeToParticipant`, des implémentations plus avancées sont possibles. `ParticipantInfo` peut être utilisé pour mettre à jour de manière sélective la configuration d’abonnement pour des participants spécifiques.

Nous recommandons d’utiliser les comportements par défaut. Spécifiez une configuration personnalisée uniquement si vous souhaitez modifier un comportement particulier.

#### Publication
<a name="ios-publish-subscribe-concepts-strategy-publishing"></a>

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

Une fois connecté à l’étape, le kit SDK interroge l’application hôte pour savoir si un participant en particulier doit effectuer une publication. Elle n’est invoquée que pour les participants locaux autorisés à publier sur la base du jeton fourni.

Voici un exemple d’implémentation :

```
func stage(_ stage: IVSStage, shouldPublishParticipant participant: IVSParticipantInfo) -> Bool {
    return true
}
```

Il s’agit d’une application de chat vidéo standard dans laquelle les utilisateurs souhaitent toujours publier. Ils peuvent désactiver et réactiver leur son et leur vidéo pour être instantanément masqués ou vus/entendus. (Ils peuvent également utiliser la fonction de publication/d’annulation de la publication, mais c’est beaucoup plus lent. Il est préférable de désactiver ou de réactiver le son dans les cas d’utilisation où il est souvent souhaitable de modifier la visibilité.)

#### Choix des flux à publier
<a name="ios-publish-subscribe-concepts-strategy-streams"></a>

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

Lors de la publication, cette fonction est utilisée pour déterminer les flux audio et vidéo à publier. Ce point est abordé plus en détail dans la section [Publier un flux multimédia](#ios-publish-subscribe-publish-stream).

#### Mise à jour de la stratégie
<a name="ios-publish-subscribe-concepts-strategy-updates"></a>

La stratégie se veut dynamique : les valeurs renvoyées par n’importe laquelle des fonctions ci-dessus peuvent être modifiées à tout moment. Par exemple, si l’application hôte ne souhaite pas publier tant que l’utilisateur final n’a pas appuyé sur un bouton, vous pouvez renvoyer une variable depuis `shouldPublishParticipant` (quelque chose comme `hasUserTappedPublishButton`). Lorsque cette variable change en fonction d’une interaction de l’utilisateur final, appelez `stage.refreshStrategy()` pour signaler au kit SDK qu’il doit interroger la stratégie pour connaître les dernières valeurs, en appliquant uniquement les éléments qui ont changé. Si le kit SDK constate que la valeur `shouldPublishParticipant` a changé, il lance le processus de publication. Si les requêtes du kit SDK et toutes les fonctions renvoient la même valeur qu’auparavant, l’appel `refreshStrategy` n’apportera aucune modification à l’étape.

Si la valeur de retour de `shouldSubscribeToParticipant` passe de `.audioVideo` à `.audioOnly`, le flux vidéo sera supprimé pour tous les participants dont les valeurs renvoyées ont été modifiées, s’il existait déjà un flux vidéo.

En général, l’étape utilise la stratégie pour appliquer au mieux la différence entre les stratégies précédentes et actuelles, sans que l’application hôte n’ait à se soucier de tout l’état requis pour la gérer correctement. Pour cette raison, et pour réduire les frais, envisagez d’appeler `stage.refreshStrategy()`, car cela ne fait rien à moins que la stratégie ne change.

### Moteur de rendu
<a name="ios-publish-subscribe-concepts-renderer"></a>

Le protocole `IVSStageRenderer` communique l’état de l’étape à l’application hôte. Les mises à jour de l’interface utilisateur de l’application hôte peuvent généralement être entièrement optimisées par les événements fournis par le moteur de rendu. Le moteur de rendu fournit les fonctions suivantes :

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

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

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

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

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

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

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

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

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

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

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

Les informations fournies par le moteur de rendu ne devraient pas avoir d’impact sur les valeurs de retour de la stratégie. Par exemple, la valeur de retour de `shouldSubscribeToParticipant` ne devrait pas changer lors de l’appel de `participant:didChangePublishState`. Si l’application hôte souhaite s’abonner à un participant en particulier, elle doit renvoyer le type d’abonnement souhaité, quel que soit l’état de publication de ce participant. Le kit SDK est chargé de s’assurer que l’état souhaité de la stratégie est appliqué au bon moment en fonction de l’état de l’étape.

Notez que seuls les participants à la publication déclenchent `participantDidJoin` et que chaque fois qu’un participant arrête de publier ou quitte la session de l’étape, `participantDidLeave` est déclenché.

## Publier un flux multimédia
<a name="ios-publish-subscribe-publish-stream"></a>

Les appareils locaux tels que les microphones et les caméras intégrés sont découverts via `IVSDeviceDiscovery`. Voici un exemple de sélection de la caméra frontale et du microphone par défaut, puis de leur renvoi en tant que `IVSLocalStageStreams` à publier par le SDK :

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

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

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

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

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

## Afficher et supprimer des participants
<a name="ios-publish-subscribe-participants"></a>

Une fois l’abonnement effectué, vous recevrez un tableau d’objets `IVSStageStream` via la fonction `didAddStreams` du moteur de rendu. Pour avoir un aperçu ou recevoir des statistiques de niveau audio concernant ce participant, vous pouvez accéder à l’objet `IVSDevice` sous-jacent depuis le flux :

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

Lorsqu’un participant arrête de publier ou en cas de désabonnement, la fonction `didRemoveStreams` est appelée avec les flux qui ont été supprimés. Les applications hôte doivent utiliser ce signal pour supprimer le flux vidéo du participant de la hiérarchie des vues.

`didRemoveStreams` est invoqué pour tous les scénarios dans lesquels un flux peut être supprimé, notamment :
+ Le participant distant arrête de publier.
+ Un appareil local se désabonne ou modifie l’abonnement de `.audioVideo` en `.audioOnly`.
+ Le participant distant quitte l’étape.
+ Le participant local quitte l’étape.

Comme `didRemoveStreams` est invoqué pour tous les scénarios, aucune logique métier personnalisée n’est requise pour supprimer des participants de l’interface utilisateur lors des opérations de départ locales ou à distance.

## Désactiver et réactiver le son des flux de médias sociaux
<a name="ios-publish-subscribe-mute-streams"></a>

Les objets `IVSLocalStageStream` ont une fonction `setMuted` qui contrôle si le son du flux est désactivé. Cette fonction peut être appelée sur le flux avant ou après son renvoi par la fonction de stratégie `streamsToPublishForParticipant`.

**Important** : si une nouvelle instance d’objet `IVSLocalStageStream` est renvoyée par `streamsToPublishForParticipant` après un appel à `refreshStrategy`, l’état muet du nouvel objet de flux est appliqué à l’étape. Lorsque vous créez des instances `IVSLocalStageStream`, veillez à ce que l’état muet attendu soit conservé.

## Surveiller l’état muet du contenu multimédia des participants distants
<a name="ios-publish-subscribe-mute-state"></a>

Lorsqu’un participant modifie l’état muet de son flux vidéo ou audio, la fonction `didChangeMutedStreams` du moteur de rendu est invoquée avec un tableau des flux qui ont changé. Utilisez la propriété `isMuted` sur `IVSStageStream` pour mettre à jour votre interface utilisateur en conséquence :

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

## Création d’une configuration d’étape
<a name="ios-publish-subscribe-stage-config"></a>

Pour personnaliser les valeurs de la configuration vidéo d’une étape, utilisez `IVSLocalStageStreamVideoConfiguration` :

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

## Obtenir les statistiques WebRTC
<a name="ios-publish-subscribe-webrtc-stats"></a>

Pour obtenir les dernières statistiques WebRTC relatives à un flux de publication ou d’abonnement, utilisez `requestRTCStats` sur `IVSStageStream`. Lorsqu’une collecte est réalisée, vous recevez des statistiques via le `IVSStageStreamDelegate` que vous pouvez définir sur `IVSStageStream`. Pour collecter en permanence des statistiques WebRTC, appelez cette fonction sur un `Timer`.

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

## Obtenir les attributs des participants
<a name="ios-publish-subscribe-participant-attributes"></a>

Si vous spécifiez des attributs dans la demande de l’opération `CreateParticipantToken`, vous pouvez les voir dans les propriétés `IVSParticipantInfo` :

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

## Messages intégrés
<a name="ios-publish-subscribe-embed-messages"></a>

La méthode `embedMessage` sur IVSImageDevice permet d’insérer des données utiles de métadonnées directement dans les images vidéo lors de la publication. Cela permet une messagerie synchronisée avec les images pour les applications en temps réel. L’intégration de messages est disponible uniquement lors de l’utilisation du SDK pour la publication en temps réel (et non pour la publication à faible latence).

Les messages intégrés ne sont pas garantis d’arriver aux abonnés, car ils sont intégrés directement dans les images vidéo et transmis via UDP, qui ne garantit pas la livraison des paquets. La perte de paquets pendant la transmission peut entraîner la perte de messages, en particulier dans des conditions réseau médiocres. Pour atténuer ce problème, la méthode `embedMessage` inclut un paramètre `repeatCount` qui duplique le message sur plusieurs images consécutives, augmentant ainsi la fiabilité de la livraison. Cette fonctionnalité est disponible uniquement pour les flux vidéo.

### Utilisation de embedMessage
<a name="ios-embed-messages-using-embedmessage"></a>

Les clients de publication peuvent intégrer des données utiles de messages dans leur flux vidéo en utilisant la méthode `embedMessage` sur IVSImageDevice. La taille des données utiles doit être supérieure à 0 Ko et inférieure à 1 Ko. Le nombre de messages intégrés insérés par seconde ne doit pas dépasser 10 Ko.

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

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

### Répétition des données utiles de messages
<a name="ios-embed-messages-repeat-payloads"></a>

Utilisez `repeatCount` pour dupliquer le message sur plusieurs images afin d’améliorer la fiabilité. La valeur doit être comprise entre 0 et 30. Les clients destinataires doivent disposer d'une logique permettant de dédupliquer le message.

```
try imageDevice.embedMessage(messageData, withRepeatCount: 5)

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

### Lecture des messages intégrés
<a name="ios-embed-messages-read-messages"></a>

Consultez la section « Obtenir des informations supplémentaires sur les améliorations (SEI) » ci-dessous pour savoir comment lire les messages intégrés à partir des flux entrants. 

## Obtenir des informations supplémentaires sur les améliorations (SEI)
<a name="ios-publish-subscribe-sei-attributes"></a>

L’unité NAL d’informations supplémentaires sur les améliorations (SEI) est utilisée pour stocker des métadonnées alignées sur l’image à côté de la vidéo. Les clients abonnés peuvent lire les données utiles SEI d’un diffuseur de publication qui publie une vidéo H.264 en inspectant la propriété `embeddedMessages` sur les objets `IVSImageDeviceFrame` provenant de l’objet `IVSImageDevice` du diffuseur de publication. Pour ce faire, acquérez `IVSImageDevice` d’un diffuseur de publication, puis observez chaque image via un rappel fourni à `setOnFrameCallback`, comme le montre l’exemple suivant :

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

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

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

## Poursuivre la session en arrière-plan
<a name="ios-publish-subscribe-background-session"></a>

Lorsque l’application passe en arrière-plan, vous pouvez rester dans l’étape tout en écoutant le son distant, mais il n’est pas possible de continuer à envoyer vos propres images et sons. Vous devrez mettre à jour votre implémentation `IVSStrategy` pour arrêter la publication et vous abonner à `.audioOnly` (ou `.none`, le cas échéant) :

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

Passez ensuite un appel à `stage.refreshStrategy()`.

## Encodage en couches avec Simulcast
<a name="ios-publish-subscribe-layered-encoding-simulcast"></a>

L’encodage en couches avec diffusion simultanée est une fonctionnalité de diffusion en temps réel d’IVS qui permet aux diffuseurs d’envoyer plusieurs couches de vidéo de qualité différente et aux abonnés de configurer ces couches de manière dynamique ou manuelle. Cette fonctionnalité est décrite plus en détail dans le document [Optimisations de la diffusion](real-time-streaming-optimization.md).

### Configuration du codage en couches (diffuseur de publication)
<a name="ios-layered-encoding-simulcast-configure-publisher"></a>

En tant que diffuseur de publication, pour activer l’encodage en couches avec la diffusion simultanée, ajoutez la configuration suivante à votre `IVSLocalStageStream` lors de l’instanciation :

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

let cameraStream = IVSLocalStageStream(device: camera, configuration: config)

// Other Stage implementation code
```

En fonction de la résolution que vous avez définie dans la configuration vidéo, un certain nombre de couches seront encodées et envoyées comme défini dans la section [Couches, qualités et fréquences d’images par défaut](real-time-streaming-optimization.md#real-time-streaming-optimization-default-layers) de la section *Optimisations de la diffusion*.

Vous avez également la possibilité de configurer des couches individuelles depuis la configuration de diffusion simultanée :

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

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

try config.simulcast.setLayers(layers)

let cameraStream = IVSLocalStageStream(device: camera, configuration: config)

// Other Stage implementation code
```

Vous pouvez également créer vos propres configurations de couches personnalisées pour un maximum de trois couches. Si vous fournissez un tableau vide ou ne contenant aucune valeur, les valeurs par défaut décrites ci-dessus sont utilisées. Les couches sont décrites avec les propriétés setter requises suivantes :
+ `setSize: CGSize;`
+ `setMaxBitrate: integer;`
+ `setMinBitrate: integer;`
+ `setTargetFramerate: float;`

À partir des préréglages, vous pouvez remplacer des propriétés individuelles ou créer une toute nouvelle configuration :

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

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

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

try config.simulcast.setLayers(layers)

let cameraStream = IVSLocalStageStream(device: camera, configuration: config)

// Other Stage implementation code
```

Pour connaître les valeurs maximales, les limites et les erreurs qui peuvent être déclenchées lors de la configuration de couches individuelles, consultez la documentation de référence du kit SDK.

### Configuration de l’encodage en couches (abonné)
<a name="ios-layered-encoding-simulcast-configure-subscriber"></a>

En tant qu’abonné, il n’est pas nécessaire d’activer l’encodage en couches. Si un diffuseur de publication envoie des couches de diffusion simultanée, le serveur s’adapte dynamiquement entre les couches pour choisir la qualité optimale en fonction de l’appareil de l’abonné et des conditions du réseau.

Sinon, pour choisir les couches explicites que le diffuseur de publication envoie, il existe plusieurs options, décrites ci-dessous.

### Option 1 : préférence pour la qualité de la couche initiale
<a name="ios-layered-encoding-simulcast-layer-quality-preference"></a>

En utilisant la stratégie `subscribeConfigurationForParticipant`, il est possible de choisir la couche initiale que vous voulez recevoir en tant qu’abonné :

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

    config.simulcast.initialLayerPreference = .lowestQuality

    return config
}
```

Par défaut, les abonnés reçoivent toujours d’abord la couche de qualité la plus faible, puis la couche de qualité la plus élevée. Cela permet d’optimiser la consommation de bande passante de l’utilisateur final et d’offrir le meilleur temps d’accès à la vidéo, réduisant ainsi les blocages initiaux de la vidéo pour les utilisateurs sur des réseaux plus faibles.

Ces options sont disponibles pour `InitialLayerPreference` :
+ `lowestQuality` : le serveur diffuse d’abord la couche de vidéo de qualité la plus faible. Cela permet d’optimiser la consommation de la bande passante ainsi que le temps d’accès au contenu multimédia. La qualité est définie comme la combinaison de la taille, du débit binaire et de la fréquence d’images de la vidéo. Par exemple, une vidéo 720p est de moins bonne qualité qu’une vidéo 1080p.
+ `highestQuality` : le serveur délivre d’abord la couche de vidéo de la plus haute qualité. Cela permet d’optimiser la qualité, mais peut augmenter le temps d’accès au contenu multimédia. La qualité est définie comme la combinaison de la taille, du débit binaire et de la fréquence d’images de la vidéo. Par exemple, la vidéo 1080p est de meilleure qualité que la vidéo 720p.

**Remarque :** pour que les préférences de couche initiale (l’appel `initialLayerPreference`) prennent effet, un nouvel abonnement est nécessaire car ces mises à jour ne s’appliquent pas à l’abonnement actif.

### Option 2 : couche préférée pour le flux
<a name="ios-layered-encoding-simulcast-preferred-layer"></a>

La méthode de stratégie `preferredLayerForStream` vous permet de sélectionner une couche après le démarrage du flux. Cette méthode de stratégie reçoit les informations relatives au participant et au flux, ce qui vous permet de sélectionner une couche participant par participant. Le kit SDK appelle cette méthode en réponse à des événements spécifiques, par exemple lorsque les couches de flux changent, que l’état des participants change ou que l’application hôte actualise la stratégie.

La méthode de stratégie renvoie un objet `IVSRemoteStageStreamLayer`, qui peut être l’un des suivants :
+ Un objet de couche, tel qu’un objet renvoyé par `IVSRemoteStageStream.layers`.
+ null, qui indique qu’aucune couche ne doit être sélectionnée et que l’adaptation dynamique est privilégiée.

Par exemple, dans la stratégie suivante, les utilisateurs sélectionneront toujours la couche de vidéo de la plus basse qualité disponible :

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

Pour réinitialiser la sélection des couches et revenir à l’adaptation dynamique, renvoyez null ou undefined dans la stratégie. Dans cet exemple, `appState` est une variable d’espace réservé qui représente l’état de l’application hôte.

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

### Option 3 : aides pour les couches de RemoteStageStream
<a name="ios-layered-encoding-simulcast-remotestagestream-helpers"></a>

`IVSRemoteStageStream` possède plusieurs aides qui peuvent être utilisées pour prendre des décisions concernant la sélection des couches et afficher les sélections correspondantes aux utilisateurs finaux :
+ **Événements relatifs aux couches** : tout comme `IVSStageRenderer`, `IVSRemoteStageStreamDelegate` possède des événements qui communiquent les modifications apportées aux couches et à l’adaptation de la diffusion simultanée :
  + `func stream(_ stream: IVSRemoteStageStream, didChangeAdaption adaption: Bool)`
  + `func stream(_ stream: IVSRemoteStageStream, didChange layers: [IVSRemoteStageStreamLayer])`
  + `func stream(_ stream: IVSRemoteStageStream, didSelect layer: IVSRemoteStageStreamLayer?, reason: IVSRemoteStageStream.LayerSelectedReason)`
+ **Méthodes relatives aux couches** : `IVSRemoteStageStream` possède plusieurs méthodes d’aide qui peuvent être utilisées pour obtenir des informations sur le flux et les couches présentées. Ces méthodes sont disponibles sur le flux distant fourni dans la stratégie `preferredLayerForStream`, ainsi que sur les flux distants exposés via `func stage(_ stage: IVSStage, participant: IVSParticipantInfo, didAdd streams: [IVSStageStream])`.
  + `stream.layers`
  + `stream.selectedLayer`
  + `stream.lowestQualityLayer`
  + `stream.highestQualityLayer`
  + `stream.layers(with: IVSRemoteStageStreamLayerConstraints)`

Pour plus de détails, consultez la classe `IVSRemoteStageStream` dans la [documentation de référence du kit SDK](https://aws.github.io/amazon-ivs-broadcast-docs/latest/ios/) (français non garanti). Pour la raison `LayerSelected`, si le message `UNAVAILABLE` est renvoyé, cela indique que la couche demandée n'a pas pu être sélectionnée. La meilleure sélection est faite à sa place, qui est généralement une couche de qualité inférieure pour maintenir la stabilité du flux.

## Diffuser la scène sur un canal IVS
<a name="ios-publish-subscribe-broadcast-stage"></a>

Pour diffuser une étape, créez une `IVSBroadcastSession` distincte, puis suivez les instructions habituelles de diffusion avec le kit SDK, décrites ci-dessus. La propriété `device` sur `IVSStageStream` sera soit un `IVSImageDevice` soit un `IVSAudioDevice` comme indiqué dans l’extrait ci-dessus. Ils peuvent être connectés au `IVSBroadcastSession.mixer` pour diffuser l’intégralité de l’étape dans une disposition personnalisable.

Vous pouvez éventuellement composer une scène et la diffuser sur un canal IVS à faible latence, afin de toucher un public plus large. Consultez la section [Activation d’hôtes multiples sur un flux Amazon IVS](https://docs.aws.amazon.com//ivs/latest/LowLatencyUserGuide/multiple-hosts.html) dans le guide de l’utilisateur du streaming à faible latence IVS.

# Comment iOS choisit la résolution de la caméra et la fréquence d’images
<a name="ios-publish-subscribe-resolution-framerate"></a>

La caméra gérée par le kit SDK de diffusion optimise sa résolution et sa fréquence d’images (images par seconde, ou i/s) afin de minimiser sa production de chaleur et sa consommation d’énergie. Cette section explique comment la résolution et la fréquence d’images sont sélectionnées pour aider les applications hôtes à optimiser leur capacité en fonction de leurs cas d’utilisation.

Lorsque vous créez une `IVSLocalStageStream` avec une `IVSCamera`, la caméra est optimisée pour une fréquence d’images de `IVSLocalStageStreamVideoConfiguration.targetFramerate` et une résolution de `IVSLocalStageStreamVideoConfiguration.size`. L’appel de `IVSLocalStageStream.setConfiguration` met à jour la caméra avec de nouvelles valeurs. 

## Aperçu de la caméra
<a name="resolution-framerate-camera-preview"></a>

Si vous créez un aperçu d’une `IVSCamera` sans le joindre à une `IVSBroadcastSession` ou une `IVSStage`, la résolution par défaut est de 1080p et la fréquence d’images de 60 i/s.

## Diffusion d’une étape
<a name="resolution-framerate-broadcast-stage"></a>

Lorsque vous utilisez une `IVSBroadcastSession` pour diffuser une `IVSStage`, le kit SDK essaie d’optimiser la caméra avec une résolution et une fréquence d’images qui répondent aux critères des deux sessions.

Par exemple, si la configuration de diffusion est définie pour avoir une fréquence d’images de 15 i/s et une résolution de 1080p, alors que la scène a une fréquence d’images de 30 i/s et une résolution de 720p, le kit SDK sélectionnera une configuration de caméra avec une fréquence d’images de 30 i/s et une résolution de 1080p. La `IVSBroadcastSession` supprimera une image sur deux de la caméra et `IVSStage` redimensionnera l’image de 1080p à 720p.

Si une application hôte prévoit d’utiliser `IVSBroadcastSession` et `IVSStage` conjointement avec une caméra, nous recommandons que les propriétés `targetFramerate` et `size` des configurations respectives correspondent. En cas de non-concordance, la caméra pourrait se reconfigurer pendant la capture vidéo, ce qui retarderait brièvement la livraison des échantillons vidéo.

Si le fait d’avoir des valeurs identiques ne correspond pas au scénario d’utilisation de l’application hôte, la création préalable de la caméra de meilleure qualité empêchera la caméra de se reconfigurer elle-même lors de l’ajout de la session de moindre qualité. Par exemple, si vous diffusez en 1080p et 30 i/s, puis que vous rejoignez ultérieurement une scène réglée sur 720p et 30 i/s, la caméra ne se reconfigurera pas d’elle-même et la vidéo se poursuivra sans interruption. Cela est dû au fait que 720p est inférieur ou égal à 1080p et 30 i/s sont inférieures ou égales à 30 i/s.

## Fréquences d’images, résolutions et rapports hauteur/largeur arbitraires
<a name="resolution-framerate-arbitrary"></a>

La plupart des appareils photo peuvent correspondre exactement aux formats courants, tels que 720p à 30 i/s ou 1080p à 60 i/s. Toutefois, il n’est pas possible de faire correspondre exactement tous les formats. Le kit SDK de diffusion choisit la configuration de la caméra en fonction des règles suivantes (par ordre de priorité) :

1. La largeur et la hauteur de la résolution sont supérieures ou égales à la résolution souhaitée, mais dans le cadre de cette contrainte, la largeur et la hauteur sont aussi petites que possible.

1. La fréquence d’images est supérieure ou égale à la fréquence d’images souhaitée, mais dans les limites de cette contrainte, la fréquence d’images est aussi faible que possible.

1. Le rapport hauteur/largeur correspond au rapport hauteur/largeur souhaité.

1. S’il existe plusieurs formats correspondants, le format présentant le plus grand champ de vision est utilisé.

Voici deux exemples :
+ L’application hôte essaie de diffuser en 4K à 120 i/s. La caméra sélectionnée prend uniquement en charge la résolution 4K à 60 i/s ou la résolution 1080p à 120 i/s. Le format sélectionné sera 4K à 60 i/s, car la règle de résolution est plus prioritaire que la règle de fréquence d’images.
+ Une résolution irrégulière est demandée, 1910x1070. La caméra utilisera la résolution 1920 x 1080. *Attention : si vous choisissez une résolution telle que 1921 x 1080, l’appareil photo augmentera verticalement jusqu’à la résolution suivante disponible (par exemple, 2592 x 1944), ce qui entraîne une réduction de la bande passante du processeur et de la mémoire*.

## Et pour Android ?
<a name="resolution-framerate-android"></a>

Android n’ajuste pas sa résolution ou sa fréquence d’images à la volée comme le fait iOS. Cela n’a donc aucune incidence sur le kit SDK de diffusion Android.

# Problèmes connus et solutions de contournement dans le SDK de diffusion iOS IVS \$1 Streaming en temps réel
<a name="broadcast-ios-known-issues"></a>

Ce document contient les problèmes connus que vous pouvez rencontrer lors de l'utilisation du SDK de diffusion iOS Amazon IVS en temps réel et suggère des solutions de contournement potentielles.
+ La modification des itinéraires audio Bluetooth peut être imprévisible. Si vous connectez un nouvel appareil au milieu d’une session, iOS peut ou non modifier automatiquement l’itinéraire d’entrée. En outre, il n’est pas possible de choisir entre plusieurs casques Bluetooth connectés en même temps. Cela se produit à la fois lors de sessions de diffusion et d’étape régulières.

  **Solution de contournement :** si vous prévoyez d’utiliser un casque Bluetooth, connectez-le avant de démarrer la diffusion ou l’étape et laissez-le connecté tout au long de la session.
+ Les participants utilisant un iPhone 14, un iPhone 14 Plus, un iPhone 14 Pro ou un iPhone 14 Pro Max peuvent provoquer un problème d’écho audio pour les autres participants.

  **Solution de contournement :** les participants utilisant les appareils concernés peuvent utiliser des écouteurs pour éviter que les autres participants ne soient confrontés à un problème d’écho.
+ Lorsqu’un participant rejoint la session avec un jeton utilisé par un autre participant, la première connexion est déconnectée sans erreur spécifique.

  **Solution de contournement :** aucune.
+ Dans de rares cas, le diffuseur de publication publie, alors que l’état de la publication reçu par les abonnés est `inactive`.

  **Solution de contournement :** essayez de quitter la session, puis de la rejoindre. Si le problème persiste, créez un jeton pour le diffuseur de publication.
+ Lorsqu’un participant publie ou s’abonne, il est possible de recevoir une erreur avec le code 1400 qui indique une déconnexion due à un problème de réseau, même lorsque le réseau est stable.

  **Solution de contournement :** essayez de republier ou de vous réabonner.
+ Un problème rare de distorsion audio peut survenir par intermittence pendant une session d’étape, généralement lors d’appels de plus longue durée.

  **Solution de contournement :** le participant dont le son est déformé peut soit quitter la session et la rejoindre, soit annuler la publication et republier son contenu audio pour ainsi corriger le problème.

# Gestion des erreurs dans le SDK de diffusion iOS IVS \$1 Streaming en temps réel
<a name="broadcast-ios-error-handling"></a>

Cette section donne un aperçu des conditions d’erreur, de la façon dont le SDK de diffusion iOS IVS en temps réel les signale à l’application et des actions que l’application doit prendre lorsqu’elles se produisent.

## Erreurs fatales ou non fatales
<a name="broadcast-ios-fatal-vs-nonfatal-errors"></a>

L’objet d’erreur possède un champ booléen « est fatal » . Il s’agit d’une entrée de dictionnaire sous `IVSBroadcastErrorIsFatalKey` laquelle contient un booléen.

En général, les erreurs fatales sont liées à la connexion au serveur Stages (soit la connexion ne peut pas être établie, soit elle est perdue et ne peut pas être rétablie). L’application doit recréer la scène et la rejoindre, éventuellement avec un nouveau jeton ou lorsque la connectivité de l’appareil sera rétablie.

Les erreurs non fatales sont généralement liées à l’état de publication/d’abonnement et sont gérées par le kit SDK, qui relance l’opération de publication/abonnement.

Vous pouvez vérifier cette propriété :

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

## Erreurs de jonction
<a name="broadcast-ios-stage-join-errors"></a>

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

Cela se produit lorsque le jeton d’étape est incorrect.

Le kit SDK génère une exception Swift avec le code d’erreur = 1000 et IVSBroadcasterReurFatalKey (L’erreur de diffusion IVS est une clé fatale) = OUI.

**Action** : créez un jeton valide et réessayez de vous connecter.

### Jeton expiré
<a name="broadcast-ios-stage-join-errors-expired-token"></a>

Cela se produit lorsque le jeton d’étape est expiré.

Le kit SDK génère une exception Swift avec le code d’erreur = 1001 et IVSBroadcasterReurFatalKey (L’erreur de diffusion IVS est une clé fatale) = OUI.

**Action** : créez un nouveau jeton et réessayez de vous connecter.

### Jeton non valide ou révoqué
<a name="broadcast-ios-stage-join-errors-invalid-token"></a>

Cela se produit lorsque le jeton d’étape n’est pas incorrect mais qu’il est rejeté par le serveur Stages. Cela est signalé de façon asynchrone via le moteur de rendu d’étape fourni par l’application.

Le kit SDK appelle `stage(didChange connectionState, withError error)` avec le code d’erreur = 1026 et IVSBroadcasterReurFatalKey (L’erreur de diffusion IVS est une clé fatale) = OUI.

**Action** : créez un jeton valide et réessayez de vous connecter.

### Erreurs réseau lors de la connexion initiale
<a name="broadcast-ios-stage-join-errors-network-initial-join"></a>

Cela se produit lorsque le kit SDK ne parvient pas à contacter le serveur Stages pour établir une connexion. Cela est signalé de façon asynchrone via le moteur de rendu d’étape fourni par l’application.

Le kit SDK appelle `stage(didChange connectionState, withError error)` avec le code d’erreur = 1300 et IVSBroadcasterReurFatalKey (L’erreur de diffusion IVS est une clé fatale) = OUI.

**Action** : attendez que la connectivité de l’appareil soit rétablie et réessayez de vous connecter.

### Erreurs réseau lorsque vous êtes déjà connecté
<a name="broadcast-ios-stage-join-errors-network-already-joined"></a>

En cas de perte de la connexion réseau de l’appareil, le kit SDK risque de perdre sa connexion aux serveurs Stage. Cela est signalé de façon asynchrone via le moteur de rendu d’étape fourni par l’application.

Le kit SDK appelle `stage(didChange connectionState, withError error)` avec le code d’erreur = 1300 et la valeur IVSBroadcasterReurFatalKey (L’erreur de diffusion IVS est une clé fatale) = OUI.

**Action** : attendez que la connectivité de l’appareil soit rétablie et réessayez de vous connecter.

## Erreurs de publication/d’abonnement
<a name="broadcast-ios-publish-subscribe-errors"></a>

### Initial
<a name="broadcast-ios-publish-subscribe-errors-initial"></a>

Il existe plusieurs erreurs :
+ MultihostSessionOfferCreationFailPublish (Échec de publication de l’offre de création de sessions multi-hôtes) (1020)
+ MultihostSessionOfferCreationFailSubscribe (Échec de la création de l’offre de session multi-hôtes) (1021)
+ MultihostSessionNoIceCandidates (Session multi-hôtes No Ice Candidats) (1022)
+ MultihostSessionStageAtCapacity (Étape de session multi-hôtes à pleine capacité) (1024)
+ SignallingSessionCannotRead (Impossible de lire la session de signalisation) (1201)
+ SignallingSessionCannotSend ((Impossible d’envoyer la session de signalisation) (1202)
+ SignallingSessionBadResponse (Mauvaise réponse de la session de signalisation) (1203)

Ceux-ci sont signalés de façon asynchrone via le moteur de rendu d’étape fourni par l’application.

Le kit SDK relance l’opération un nombre limité de fois. Lors des nouvelles tentatives, l’état de publication/d’abonnement est `ATTEMPTING_PUBLISH` / `ATTEMPTING_SUBSCRIBE`. Si les nouvelles tentatives réussissent, l’état passe à `PUBLISHED` / `SUBSCRIBED`.

Le kit SDK appelle `IVSErrorDelegate:didEmitError` avec le code d’erreur pertinent et `IVSBroadcastErrorIsFatalKey == NO`.

**Action** : aucune action n’est nécessaire, car le kit SDK réessaie automatiquement. Le cas échéant, l’application peut actualiser la stratégie pour forcer d’autres tentatives.

### Déjà établi, puis échec
<a name="broadcast-ios-publish-subscribe-errors-established"></a>

Une publication ou un abonnement peut échouer une fois qu’il a été établi, probablement en raison d’une erreur réseau. Le code d’erreur pour une « connexion d’appairage perdue en raison d’une erreur réseau » est 1400.

Cela est signalé de façon asynchrone via le moteur de rendu d’étape fourni par l’application.

Le kit SDK relance l’opération de publication/d’abonnement. Lors des nouvelles tentatives, l’état de publication/d’abonnement est `ATTEMPTING_PUBLISH` / `ATTEMPTING_SUBSCRIBE`. Si les nouvelles tentatives réussissent, l’état passe à `PUBLISHED` / `SUBSCRIBED`.

Le kit SDK appelle `didEmitError` avec le code d’erreur = 1400 et IVSBroadcasterReurFatalKey (L’erreur de diffusion IVS est une clé fatale) = NON.

**Action** : aucune action n’est nécessaire, car le kit SDK réessaie automatiquement. Le cas échéant, l’application peut actualiser la stratégie pour forcer d’autres tentatives. En cas de perte totale de connectivité, il est probable que la connexion à Stages échoue également.

# Kit SDK de diffusion IVS : appareils mixtes
<a name="broadcast-mixed-devices"></a>

Les appareils mixtes sont des appareils audio et vidéo qui acceptent plusieurs sources d’entrée et génèrent une seule sortie. Les appareils de mixage sont une fonctionnalité puissante qui vous permet de définir et de gérer plusieurs éléments à l’écran (vidéo) et pistes audio. Combinez de la vidéo et de l'audio provenant de sources multiples telles que des caméras, des microphones, des captures d'écran, ainsi que de l'audio et de la vidéo générés par votre application. Vous pouvez utiliser des transitions pour déplacer ces sources dans la vidéo que vous diffusez vers IVS, et ajouter ou supprimer des sources en cours de diffusion.

Les appareils mixtes se déclinent en versions image et audio. Pour créer un appareil d’image mixte, appelez :

`DeviceDiscovery.createMixedImageDevice()` sur Android

`IVSDeviceDiscovery.createMixedImageDevice()` sur iOS

L’appareil renvoyé peut être associé à un `BroadcastSession` (diffusion à faible latence) ou `Stage` (diffusion en temps réel), comme tout autre appareil.

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

![\[Terminologie des appareils mixtes de diffusion IVS.\]](http://docs.aws.amazon.com/fr_fr/ivs/latest/RealTimeUserGuide/images/Broadcast_SDK_Mixer_Glossary.png)



| Terme | Description | 
| --- | --- | 
| Appareil | Un composant matériel ou logiciel qui produit une entrée audio ou image. Les microphones, les caméras, les casques Bluetooth et les périphériques virtuels tels que les captures d'écran ou les entrées d'images personnalisées sont des exemples d'appareils. | 
| Appareil mixte | Un `Device` qui peut être associé à un `BroadcastSession` comme n’importe quel autre `Device`, mais avec des API supplémentaires qui permettent d’ajouter des objets `Source`. Les appareils mixtes disposent de mixeurs internes qui composent l’audio ou les images, produisant un flux audio et image unique. Les appareils mixtes sont disponibles en version audio ou image.  | 
| Configuration d’un appareil mixte | Un objet de configuration pour l’appareil mixte. Pour les appareils d’image mixtes, cela permet de configurer des propriétés telles que les dimensions et la fréquence d’images. Pour les appareils d’audio mixtes, cela permet de configurer le nombre de canaux. | 
|  Source | Un conteneur qui définit la position d'un élément visuel à l'écran et les propriétés d'une piste audio dans le mixage audio. Un appareil mixte peut être configuré avec zéro ou plusieurs sources. Les sources reçoivent une configuration qui affecte la manière dont les médias de la source sont utilisés. L’image ci-dessus montre quatre sources d’image : [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/fr_fr/ivs/latest/RealTimeUserGuide/broadcast-mixed-devices.html)  | 
| Configuration de la source |  Un objet de configuration pour la source entrant dans un appareil mixte. Les objets de configuration complets sont décrits ci-dessous.   | 
| Transition | Pour déplacer un slot vers une nouvelle position ou modifier certaines de ses propriétés, utilisez `MixedDevice.transitionToConfiguration()`. Cette méthode nécessite : [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/fr_fr/ivs/latest/RealTimeUserGuide/broadcast-mixed-devices.html) | 

## Appareil d’audio mixte
<a name="broadcast-mixed-audio-device"></a>

### Configuration
<a name="broadcast-mixed-audio-device-configuration"></a>

`MixedAudioDeviceConfiguration` sur Android

`IVSMixedAudioDeviceConfiguration` sur iOS


| Nom | Type | Description | 
| --- | --- | --- | 
| `channels` | Entier | Nombre de canaux de sortie du mixage audio. Valeurs valides : 1, 2. 1 est un audio mono ; 2, un audio stéréo. Par défaut : 2. | 

### Configuration de la source
<a name="broadcast-mixed-audio-device-source-configuration"></a>

`MixedAudioDeviceSourceConfiguration` sur Android

`IVSMixedAudioDeviceSourceConfiguration` sur iOS


| Nom | Type | Description | 
| --- | --- | --- | 
| `gain` | Float | Gain audio. Il s'agit d'un multiplicateur, donc toute valeur supérieure à 1 augmente le gain ; toute valeur inférieure à 1 le diminue. Valeurs valides : 0 à 2. Par défaut : 1.  | 

## Appareil d’image mixte
<a name="broadcast-mixed-image-device"></a>

### Configuration
<a name="broadcast-mixed-image-device-configuration"></a>

`MixedImageDeviceConfiguration` sur Android

`IVSMixedImageDeviceConfiguration` sur iOS


| Nom | Type | Description | 
| --- | --- | --- | 
| `size` | Vec2 | Taille du canevas de la vidéo. | 
| `targetFramerate` | Entier | Nombre d’images cibles par seconde pour l’appareil mixte. En moyenne, cette valeur doit être respectée, mais le système peut sauter des images dans certaines circonstances (par exemple, en cas de charge élevée du processeur ou du processeur graphique). | 
| `transparencyEnabled` | Booléen | Cela permet le mélange à l’aide de la propriété `alpha` sur les configurations de source d’image. Définir cette valeur sur `true` augmente la consommation de mémoire et de CPU. Valeur par défaut : `false`. | 

### Configuration de la source
<a name="broadcast-mixed-image-device-source-configuration"></a>

`MixedImageDeviceSourceConfiguration` sur Android

`IVSMixedImageDeviceSourceConfiguration` sur iOS


| Nom | Type | Description | 
| --- | --- | --- | 
| `alpha` | Float | Alpha du slot. Il est multiplicative avec toutes les valeurs alpha de l'image. Valeurs valides : 0-1. 0 correspond à une transparence totale et 1 à une opacité totale. Par défaut : 1. | 
| `aspect` | AspectMode | Mode de rapport d'aspect pour toute image rendue dans le slot. Valeurs valides : [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/fr_fr/ivs/latest/RealTimeUserGuide/broadcast-mixed-devices.html) Valeur par défaut : `Fit`  | 
| `fillColor` | Vec4 | Couleur de remplissage à utiliser avec `aspect Fit` lorsque les rapports d’aspect du slot et de l’image ne correspondent pas. Le format est (rouge, vert, bleu, alpha). Valeur valide (pour chaque canal) : 0 à 1. Par défaut : (0, 0, 0, 0). | 
| `position` | Vec2 | Position du slot (en pixels) par rapport à l'angle supérieur gauche du canevas. L'origine du slot est également en haut à gauche. | 
| `size` | Vec2 | Taille du slot, en pixels. La définition de cette valeur définit également `matchCanvasSize` sur `false`. Par défaut : (0, 0) ; cependant, étant donné que `matchCanvasSize` la valeur par défaut est `true`, la taille rendue du slot correspond à la taille du canevas, et non (0, 0). | 
| `zIndex` | Float | Ordre relatif des slots. Les slots avec des valeurs plus `zIndex` élevées sont dessinées au dessus des slots avec des valeurs `zIndex` inférieures. | 

## Création et configuration d’un appareil d’image mixte
<a name="broadcast-mixed-image-device-creating-configuring"></a>

![\[Configuration d'une séance de diffusion pour le mixage.\]](http://docs.aws.amazon.com/fr_fr/ivs/latest/RealTimeUserGuide/images/Broadcast_SDK_Mixer_Configuring.png)


Ici, nous créons une scène similaire à celle du début de ce guide, avec trois éléments à l'écran :
+ Slot en bas à gauche pour une caméra.
+ Slot en bas à droite pour une superposition de logo.
+ Slot en haut à droite pour un film.

Notez que l'origine du canevas est le coin supérieur gauche et c'est la même chose pour les slots. Par conséquent, le positionnement d'un slot à (0, 0) le place dans le coin supérieur gauche avec l'ensemble du slot visible.

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

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

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

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

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

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

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

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

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

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

## Suppression de sources
<a name="broadcast-mixed-devices-removing-sources"></a>

Pour supprimer une source, appelez `MixedDevice.remove` avec l’objet `Source` que vous voulez supprimer.

## animations avec Transitions
<a name="broadcast-mixed-devices-animations-transitions"></a>

La méthode de transition remplace la configuration d’une source par une nouvelle configuration. Ce remplacement peut être animé au fil du temps en définissant une durée supérieure à 0, en secondes. 

### Quelles propriétés peuvent être animées  ?
<a name="broadcast-mixed-devices-animations-properties"></a>

Toutes les propriétés de la structure du slot ne peuvent être animées. Toutes les propriétés basées sur des types Float peuvent être animées ; d’autres propriétés prennent effet au démarrage ou à la fin de l’animation.


| Nom | Peut-on l’animer ? | Point d'impact | 
| --- | --- | --- | 
| `Audio.gain` | Oui | Interpolé | 
| `Image.alpha` | Oui | Interpolé | 
| `Image.aspect` | Non | Fin | 
| `Image.fillColor` | Oui | Interpolé | 
| `Image.position` | Oui | Interpolé | 
| `Image.size` | Oui | Interpolé | 
| `Image.zIndex` Remarque : Le `zIndex` déplace les plans 2D dans l'espace 3D, de sorte que la transition se produit lorsque les deux plans se croisent à un moment donné au milieu de l'animation. Cela peut être calculé, mais cela dépend du début et de la fin des valeurs `zIndex`. Pour une transition plus fluide, combinez cela avec `alpha`.  | Oui | Je ne sais pas | 

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

Vous trouverez ci-dessous des exemples de prise de contrôle de la caméra en plein écran à l’aide de la configuration définie ci-dessus dans [Création et configuration d’un appareil d’image mixte](#broadcast-mixed-image-device-creating-configuring). Il est animé pendant 0,5 seconde.

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

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

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

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

## Mise en miroir de la diffusion
<a name="broadcast-mixed-devices-mirroring"></a>


| Pour mettre en miroir un périphérique d'image connecté lors de la diffusion dans cette direction... | Utilisez une valeur négative pour … | 
| --- | --- | 
| Horizontalement | La largeur du slot | 
| Verticalement | La hauteur du slot | 
| Aussi bien horizontalement que verticalement | La largeur et la hauteur de la fente | 

La position devra être ajustée selon la même valeur, afin de placer la fente dans la bonne position lors de la mise en miroir.

Vous trouverez ci-dessous des exemples de mise en miroir de la diffusion horizontalement et verticalement.

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

Mise en miroir horizontale :

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

Mise en miroir horizontale verticale :

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

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

Mise en miroir horizontale :

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

Mise en miroir horizontale verticale :

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

Remarque : cette mise en miroir est différente de la méthode `setMirrored` utilisée sur `ImagePreviewView` (Android) et `IVSImagePreviewView` (iOS). Cette méthode n'affecte que la vue d'aperçu locale sur l'appareil et n'a aucun impact sur la diffusion.

# Kit SDK de diffusion IVS : échange de jetons \$1 Diffusion en temps réel
<a name="broadcast-mobile-token-exchange"></a>

L’échange de jetons vous permet de mettre à niveau ou de rétrograder les fonctionnalités des jetons des participants et de mettre à jour les attributs des jetons dans le SDK de diffusion mobile, sans que les participants aient besoin de se reconnecter. Cela est utile pour des scénarios comme le co-hébergement, où les participants peuvent commencer par des fonctionnalités d’abonnement uniquement, puis avoir besoin de fonctionnalités de publication.

Limites:
+ L’échange de jetons ne fonctionne qu’avec les jetons créés sur votre serveur avec une [paire de clés](https://docs.aws.amazon.com//ivs/latest/RealTimeUserGuide/getting-started-distribute-tokens.html#getting-started-distribute-tokens-self-signed). Cela ne fonctionne pas avec les jetons créés via l’[API CreateParticipantToken](https://docs.aws.amazon.com/ivs/latest/RealTimeAPIReference/API_CreateParticipantToken.html). 
+ Si vous utilisez l’échange de jetons pour modifier les attributs qui régissent les dispositions de montage côté serveur (comme featuredParticipantAttribute et participantOrderAttribute), la disposition d’une composition active ne sera pas mise à jour tant que le participant ne se reconnectera pas. 

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

L’échange de jetons est simple : appelez l’API `exchangeToken` sur l’objet `Stage`/`IVSStage` et fournissez le nouveau jeton. Si les `capabilities` du nouveau jeton sont différentes de celles du jeton précédent, les capacités du nouveau jeton sont immédiatement évaluées. Par exemple, si le jeton précédent n’avait pas la capacité `publish` et que le nouveau la possède, les fonctions de stratégie de publication sont invoquées, ce qui permet à l’application hôte de décider si elle souhaite publier immédiatement avec la nouvelle fonctionnalité ou attendre. Il en va de même pour les fonctionnalités supprimées : si le jeton précédent possédait la capacité `publish` et le nouveau jeton non, le participant annule immédiatement la publication sans invoquer les fonctions de stratégie d’étape pour la publication.

Lors de l’échange d’un jeton, le jeton précédent et le nouveau jeton doivent avoir les mêmes valeurs pour les champs de données utiles suivants : 
+ `topic`
+ `resource`
+ `jti`
+ `whip_url`
+ `events_url`

Ces champs sont immuables. L’échange d’un jeton qui modifie un champ immuable entraîne le rejet immédiat de l’échange par le SDK.

Les autres champs peuvent être modifiés, notamment :
+ `attributes`
+ `capabilities`
+ `user`
+ `_id`
+ `iat`
+ `exp`

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



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

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



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

## Réception des mises à jour
<a name="broadcast-mobile-token-exchange-receiving-updates"></a>

Une fonction dans `StageRenderer`/`IVSStageRenderer` reçoit des mises à jour concernant des participants distants déjà publiés qui échangent leurs jetons pour mettre à jour leur `userId` ou leurs `attributes`. Les participants distants qui ne publient pas encore verront leur `userId` et leurs `attributes` mis à jour exposés via les fonctions de rendu `onParticipantJoined`/`participantDidJoin` existantes s’ils finissent par être publiés.

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



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

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



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

## Visibilité des mises à jour
<a name="broadcast-mobile-token-exchange-visibility"></a>

Lorsqu’un participant échange un jeton pour mettre à jour son `userId` ou ses `attributes`, la visibilité de ces modifications dépend de son état de publication actuel : 
+ **Si le participant ne publie *pas* :** la mise à jour est traitée en mode silencieux. S’il finit par publier, tous les SDK recevront le `userId` et les `attributes` mis à jour dans le cadre de l’événement de publication initial.
+ **Si le participant *publie déjà* :** la mise à jour est diffusée immédiatement. Toutefois, seuls les SDK mobiles v1.37.0 et ultérieurs reçoivent la notification. Les participants qui utilisent le SDK Web, les anciens SDK mobiles et le montage côté serveur ne voient pas la modification tant que le participant n’a pas dépublié et republié.

Le tableau suivant précise la matrice de prise en charge :


| État du participant | Obervateur : SDK mobile 1.37.0 et versions ultérieures | Observateur : anciens SDK mobiles, SDK Web, montage côté serveur | 
| --- | --- | --- | 
| Pas de publication (commence ensuite) | ✅ Visible (lors de la publication lors d’un événement Le participant a rejoint) | ✅ Visible (lors de la publication lors d’un événement Le participant a rejoint) | 
| Publie déjà (ne republie jamais) | ✅ Visible (immédiatement via l’événement de mise à jour des métadonnées du participant) | ❌ Non visible | 
| Publie déjà (dépublie et republie) | ✅ Visible (immédiatement via l’événement de mise à jour des métadonnées du participant) | ⚠️ Visible après (lors de la republication lors d’un événement Le participant a rejoint) | 

# Kit SDK de diffusion IVS : Sources d’images personnalisées \$1 Diffusion en temps réel
<a name="broadcast-custom-image-sources"></a>

Les sources d'entrée d'image personnalisées permettent à une application de fournir sa propre entrée d'image au SDK de diffusion, au lieu de se limiter aux caméras prédéfinies. Une source d'image personnalisée peut être aussi simple qu'un filigrane semi-transparent ou une scène statique « Je reviens tout de suite », ou elle peut permettre à l'application d'effectuer un traitement personnalisé supplémentaire, comme l'ajout de filtres de beauté à la caméra.

Lorsque vous utilisez une source d'entrée d'image personnalisée pour un contrôle personnalisé de la caméra (par exemple, l'utilisation de bibliothèques de filtres de beauté nécessitant un accès à la caméra), le kit SDK de diffusion n'est plus responsable de la gestion de la caméra. Au lieu de cela, l'application est chargée de gérer correctement le cycle de vie de la caméra. Consultez la documentation officielle de la plateforme sur la façon dont votre application doit gérer la caméra.

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

Après avoir créé une session `DeviceDiscovery`, créez une source d'entrée d'image :

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

Cette méthode renvoie un `CustomImageSource`, qui est une source basée sur un Android [Surface](https://developer.android.com/reference/android/view/Surface) standard. La sous-classe `SurfaceSource` permet le redimensionnement et la rotation. Vous pouvez également créer un `ImagePreviewView` pour afficher un aperçu de son contenu.

Pour récupérer la sous-jacente  `Surface`:

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

Cette `Surface` peut être utilisée comme tampon de sortie pour les producteurs d'images tels que Camera2, OpenGL ES et d'autres bibliothèques. Le cas d'utilisation le plus simple consiste à dessiner directement un bitmap statique ou une couleur dans le canevas de la surface. Cependant, de nombreuses bibliothèques (telles que les bibliothèques de filtres de beauté) fournissent une méthode qui permet à une application de spécifier une `Surface` externe pour le rendu. Vous pouvez utiliser une telle méthode pour transmettre cette `Surface` à la bibliothèque de filtres, ce qui permet à cette dernière de produire des images traitées pour la séance de diffusion.

Cette `CustomImageSource` peut être encapsulée dans un `LocalStageStream` et renvoyée par la `StageStrategy` pour être publiée vers une `Stage`.

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

Après avoir créé une session `DeviceDiscovery`, créez une source d'entrée d'image :

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

Cette méthode renvoie une `IVSCustomImageSource`, une source d'image qui permet à l'application de soumettre `CMSampleBuffers` manuellement. Pour connaître les formats de pixels pris en charge, consultez la référence du kit SDK de diffusion iOS. Un lien vers la version la plus récente se trouve dans les [notes de mise à jour Amazon IVS](release-notes.md) pour la dernière version du kit SDK de diffusion.

Les exemples soumis à la source personnalisée seront diffusés dans la scène :

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

Pour diffuser des vidéos en streaming, utilisez cette méthode dans un rappel. Par exemple, si vous utilisez la caméra, chaque fois qu'un nouvel exemple de tampon est reçu d'une `AVCaptureSession`, l'application peut transférer l'exemple de tampon vers la source d'image personnalisée. Si vous le souhaitez, l'application peut appliquer un traitement supplémentaire (comme un filtre de beauté) avant de soumettre l'exemple à la source d'image personnalisée.

La `IVSCustomImageSource` peut être encapsulée dans un `IVSLocalStageStream` et renvoyé par la `IVSStageStrategy` pour être publiée vers une `Stage`.

# Kit SDK de diffusion IVS : Sources audio personnalisées \$1 Diffusion en temps réel
<a name="broadcast-custom-audio-sources"></a>

**Remarque :** Ce guide s'applique uniquement au SDK de diffusion Android pour le streaming en temps réel IVS. Les informations relatives aux SDK iOS et Web seront publiées à l’avenir.

Les sources d’entrée audio personnalisées permettent à une application de fournir sa propre entrée audio au SDK de diffusion, au lieu de se limiter au microphone intégré de l’appareil. Une source audio personnalisée permet aux applications de diffuser de l’audio traité avec des effets, de mixer plusieurs flux audio ou de les intégrer à des bibliothèques de traitement audio tierces.

Lorsque vous utilisez une source d’entrée audio personnalisée, le SDK de diffusion n’est plus responsable de la gestion directe du microphone. Au lieu de cela, votre application est chargée de capturer, traiter et envoyer les données audio à la source personnalisée.

Le flux de travail des sources audio personnalisées suit ces étapes :

1. Entrée audio : créez une source audio personnalisée avec le format audio spécifié (fréquence d’échantillonnage, canaux, format). 

1. Votre traitement : capturez ou générez des données audio à partir de votre pipeline de traitement audio.

1. Source audio personnalisée : soumettez des tampons audio à la source personnalisée avec `appendBuffer()`.

1. Étape : encapsulez dans `LocalStageStream` et publiez sur l’étape via votre `StageStrategy`. 

1. Participants : les participants à l’étape reçoivent le son traité en temps réel.

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

### Création d’une source audio personnalisée
<a name="custom-audio-sources-android-creating-a-custom-audio-source"></a>

Après avoir créé une session `DeviceDiscovery`, créez une source d’entrée audio personnalisée :

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

Cette méthode renvoie une `CustomAudioSource`, qui accepte les données audio PCM brutes. La source audio personnalisée doit être configurée avec le même format audio que celui produit par votre pipeline de traitement audio.

#### Formats audio pris en charge
<a name="custom-audio-sources-android-submitting-audio-data-supportedi-audio-formats"></a>


| Paramètre | Options | Description | 
| --- | --- | --- | 
| Canaux | 1 (mono), 2 (stéréo) | Nombre de canaux audio. | 
| Fréquence d’échantillonnage | RATE\$116000, RATE\$144100, RATE\$148000 | Fréquence d’échantillonnage audio en Hz. Une valeur de 48 kHz est recommandée pour une haute qualité. | 
| Format | INT16, FLOAT32 | Format d’échantillonnage audio. INT16 est un PCM à virgule fixe 16 bits, FLOAT32 est un PCM à virgule flottante 32 bits. Les formats entrelacés et planaires sont disponibles. | 

### Envoi des données audio
<a name="custom-audio-sources-android-submitting-audio-data"></a>

Pour envoyer des données audio à la source personnalisée, utilisez la méthode `appendBuffer()` :

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

**Considérations importantes:**
+ Les données audio doivent être au format spécifié lors de la création de la source personnalisée.
+ Les horodatages doivent augmenter de façon monotone et être fournis par votre source audio pour une lecture audio fluide.
+ Envoyez des données audio régulièrement pour éviter les interruptions dans le flux.
+ La méthode renvoie le nombre d’échantillons traités (0 indique un échec). 

### Publication sur une étape
<a name="custom-audio-sources-android-publishing-to-a-stage"></a>

Encapsulez la `CustomAudioSource` dans un `AudioLocalStageStream` et retournez-le dans votre `StageStrategy` :

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

### Exemple complet : intégration du traitement audio
<a name="custom-audio-sources-android-complete-example"></a>

Voici un exemple complet illustrant l’intégration avec un SDK de traitement audio :

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

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

### Bonnes pratiques
<a name="custom-audio-sources-android-best-practices"></a>

#### Cohérence du format audio
<a name="custom-audio-sources-android-best-practices-audio-format-consistency"></a>

Assurez-vous que le format audio que vous envoyez correspond au format spécifié lors de la création de la source personnalisée :

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

#### Gestion de la mémoire tampon
<a name="custom-audio-sources-android-best-practices-buffer-managemetn"></a>

Utilisez des `ByteBuffers` directs et réutilisez-les pour minimiser le travail du récupérateur de mémoire : 

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

#### Timing et synchronisation
<a name="custom-audio-sources-android-best-practices-timing-and-synchronization"></a>

Vous devez utiliser les horodatages fournis par votre source audio pour une lecture audio fluide. Si votre source audio ne fournit pas son propre horodatage, créez votre propre horodatage d’époque et calculez manuellement la durée entre chaque échantillon en utilisant le nombre d’images et la taille d’image. 

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

#### Gestion des erreurs
<a name="custom-audio-sources-android-best-practices-error-handling"></a>

Vérifiez toujours la valeur de retour de `appendBuffer()` : 

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

# Kit SDK de diffusion IVS : Filtres de caméra tiers \$1 Diffusion en temps réel
<a name="broadcast-3p-camera-filters"></a>

Ce guide part du principe que vous connaissez déjà les sources [d’images personnalisées](broadcast-custom-image-sources.md) ainsi que l’intégration du [SDK de diffusion en temps réel IVS](broadcast.md) dans votre application.

Les filtres de caméra permettent aux créateurs de contenu en direct d’améliorer ou de modifier l’apparence de leur visage ou de leur arrière-plan. Cela peut augmenter l’engagement des spectateurs, attirer de nouveaux spectateurs et améliorer l’expérience de diffusion en direct.

# Intégration de filtres de caméra tiers
<a name="broadcast-3p-camera-filters-integrating"></a>

Vous pouvez intégrer des SDK de filtres de caméra tiers au SDK de diffusion IVS en transmettant la sortie du SDK de filtres à une source [d’entrée d’image personnalisée](broadcast-custom-image-sources.md). Une source d’entrée d’image personnalisée permet à une application de fournir sa propre entrée d’image au SDK de diffusion. Le SDK d’un fournisseur de filtres tiers peut gérer le cycle de vie de la caméra pour traiter les images provenant de la caméra, appliquer un effet de filtre et le produire dans un format pouvant être transmis à une source d’image personnalisée.

![\[Intégrer des SDK de filtres de caméra tiers au SDK de diffusion IVS en transmettant la sortie du SDK de filtres à une source d’entrée d’image personnalisée.\]](http://docs.aws.amazon.com/fr_fr/ivs/latest/RealTimeUserGuide/images/3P_Camera_Filters_Integrating.png)


Consultez la documentation de votre fournisseur de filtres tiers pour connaître les méthodes intégrées qui permettent de convertir une image de caméra, avec l’effet de filtre, appliquée à un format pouvant être transmis à une source [d’entrée d’image personnalisée](broadcast-custom-image-sources.md). Le processus varie selon la version du SDK de diffusion IVS utilisée :
+ **Web** : le fournisseur de filtres doit pouvoir restituer sa sortie dans un élément du canevas. La méthode [captureStream](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/captureStream) peut ensuite être utilisée pour renvoyer un MediaStream du contenu du canevas. Le MediaStream peut ensuite être converti en instance d’un [LocalStageStream](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/classes/LocalStageStream) et diffusé sur une scène.
+ **Android** : le SDK du fournisseur de filtres peut soit restituer une image sur un appareil Android `Surface` fourni par le SDK de diffusion IVS, soit convertir l’image en bitmap. Si vous utilisez un bitmap, il peut ensuite être rendu sur le `Surface` sous-jacent fourni par la source d’image personnalisée. Pour ce faire, vous devez le déverrouiller et l’écrire sur un canevas.
+ **iOS** : le SDK d’un fournisseur de filtres tiers doit fournir un cadre de caméra auquel un effet de filtre est appliqué sous forme de `CMSampleBuffer`. Consultez la documentation du SDK de votre fournisseur de filtres tiers pour savoir comment obtenir une `CMSampleBuffer` comme sortie finale après le traitement d’une image de caméra.

# Utilisation de BytePlus avec le SDK de diffusion IVS
<a name="broadcast-3p-camera-filters-integrating-byteplus"></a>

Ce document explique comment utiliser le SDK BytePlus Effects avec le SDK de diffusion IVS.

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

### Installation et configuration du SDK BytePlus Effects
<a name="integrating-byteplus-android-install-effects-sdk"></a>

Consultez le [Guide d’accès à BytePlus Android](https://docs.byteplus.com/en/effects/docs/android-v4101-access-guide) pour plus de détails sur l’installation, l’initialisation et la configuration du SDK BytePlus Effects.

### Configurer la source d’image personnalisée
<a name="integrating-byteplus-android-setup-image-source"></a>

Une fois le SDK initialisé, alimentez les images de caméra traitées avec un effet de filtre appliqué à une source d’entrée d’image personnalisée. Pour ce faire, créez une instance d’un objet `DeviceDiscovery` et créez une source d’image personnalisée. Notez que lorsque vous utilisez une source d’entrée d’image personnalisée pour le contrôle personnalisé de la caméra, le SDK de diffusion n’est plus responsable de la gestion de la caméra. Au lieu de cela, l’application est chargée de gérer correctement le cycle de vie de la caméra.

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

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

### Convertir la sortie en bitmap ainsi que le flux en source d’entrée d’image personnalisée
<a name="integrating-byteplus-android-convert-to-bitmap"></a>

Pour que les images de caméra avec un effet de filtre appliqué depuis le SDK BytePlus Effect soient transférées directement vers le SDK de diffusion IVS, vous devez convertir la sortie d’une texture du SDK BytePlus Effects en bitmap. Lorsqu’une image est traitée, la méthode `onDrawFrame()` est invoquée par le SDK. La méthode `onDrawFrame()` est une méthode publique de l’interface [GLSurfaceView.renderer](https://developer.android.com/reference/android/opengl/GLSurfaceView.Renderer) d’Android. Dans l’exemple d’application Android fourni par BytePlus, cette méthode est appelée sur chaque image de caméra ; la méthode produit une texture. Parallèlement, vous pouvez compléter la méthode `onDrawFrame()` par une logique qui permet de convertir cette texture en bitmap et de l’envoyer à une source d’entrée d’image personnalisée. Comme indiqué dans l’exemple de code suivant, utilisez la méthode `transferTextureToBitmap` fournie par le SDK BytePlus pour réaliser cette conversion. Cette méthode est fournie par la bibliothèque [com.bytedance.labcv.core.util.ImageUtil](https://docs.byteplus.com/en/effects/docs/android-v4101-access-guide#Appendix:%20convert%20input%20texture%20to%202D%20texture%20with%20upright%20face) du SDK BytePlus Effects, comme indiqué dans l’exemple de code suivant. Par la suite, vous pouvez effectuer le rendu sur l’Android sous-jacent `Surface` d’un `CustomImageSource` en écrivant le bitmap obtenu sur le canevas d’une Surface. De nombreuses invocations successives de `onDrawFrame()` donnent lieu à une séquence de bitmaps et, une fois combinées, créent un flux vidéo.

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

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


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

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

# Utilisation de DeepAR avec le SDK de diffusion IVS
<a name="broadcast-3p-camera-filters-integrating-deepar"></a>

Ce document explique comment utiliser le SDK DeepAR avec le SDK de diffusion IVS.

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

Consultez le [Guide d’intégration Android de DeepAR](https://docs.deepar.ai/deepar-sdk/integrations/video-calling/amazon-ivs/android/) pour obtenir plus de détails sur la façon d’intégrer le SDK DeepAR au SDK de diffusion Android IVS.

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

Consultez le [Guide d’intégration iOS de DeepAR](https://docs.deepar.ai/deepar-sdk/integrations/video-calling/amazon-ivs/ios/) pour obtenir plus de détails sur la façon d’intégrer le SDK DeepAR au SDK de diffusion iOS IVS.

# Utilisation de Snap avec le SDK de diffusion IVS
<a name="broadcast-3p-camera-filters-integrating-snap"></a>

Ce document explique comment utiliser le SDK Camera Kit de Snap avec le SDK de diffusion IVS.

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

Cette section suppose que vous connaissez déjà la [diffusion et l’abonnement à des vidéos à l’aide du SDK de diffusion Web](getting-started-pub-sub-web.md).

Pour intégrer le SDK Camera Kit de Snap au SDK de diffusion Web en temps réel IVS, vous devez effectuer les actions suivantes :

1. Installez le SDK Camera Kit et le Webpack. (Notre exemple utilise Webpack comme bundler, mais vous pouvez utiliser n’importe quel bundler de votre choix.)

1. Créez vos `index.html`.

1. Ajoutez des éléments de configuration.

1. Créez vos `index.css`.

1. Affichez et configurez les participants.

1. Affichez les caméras et les microphones connectés.

1. Créez une session Camera Kit.

1. Récupérez les objectifs et remplissez le sélecteur d’objectifs.

1. Affichez le résultat d’une session Camera Kit sur un canevas.

1. Créez une fonction pour remplir la liste déroulante Lens.

1. Fournissez à Camera Kit une source multimédia pour le rendu et la diffusion d’un `LocalStageStream`.

1. Créez vos `package.json`.

1. Créez un fichier de configuration Webpack.

1. Configurez un serveur HTTPS et effectuez des tests.

Chacune de ces étapes est décrite ci-dessous.

### Installez le SDK et le Webpack du Camera Kit
<a name="integrating-snap-web-install-camera-kit"></a>

Dans cet exemple, nous utilisons Webpack comme bundler, mais vous pouvez utiliser n’importe quel bundler.

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

### Créez le fichier index.html
<a name="integrating-snap-web-create-index"></a>

Puis, créez le modèle de code HTML et importez le SDK de diffusion Web sous forme de balise de script. Dans le code suivant, veillez à remplacer `<SDK version>` par la version du SDK de diffusion que vous utilisez.

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

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

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

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

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

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

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

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

  <!-- Display Remote Participants -->

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

### Ajout d’éléments de configuration
<a name="integrating-snap-web-add-setup-elements"></a>

Créez le HTML pour la sélection d’une caméra, d’un microphone et d’un objectif et la spécification d’un jeton de participant :

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

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

Ajoutez du code HTML supplémentaire en dessous pour afficher les flux de caméra des participants locaux et distants :

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

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

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

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

Chargez une logique supplémentaire, notamment des méthodes d’assistance pour configurer la caméra et le fichier JavaScript fourni. (Plus loin dans cette section, vous allez créer ces fichiers JavaScript et les regrouper en un seul fichier ; l’objectif est de pouvoir importer Camera Kit en tant que module. Le fichier JavaScript fourni contiendra la logique de configuration du Camera Kit, d’application d’un objectif et de diffusion du flux de caméra avec un objectif appliqué à une scène.) Ajoutez des balises de fermeture pour les éléments `body` et `html` afin de compléter la création de `index.html`.

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

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

### Créer le fichier index.css
<a name="integrating-snap-web-create-index-css"></a>

Créez un fichier source CSS pour styliser la page. Nous ne reviendrons pas sur ce code afin de nous concentrer sur la logique de gestion d’une scène et d’intégration avec le kit SDK de Snap.

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

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

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

hr {
  margin: 1rem 0;
}

table {
  display: table;
}

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

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

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

.content {
  flex: 1 0 auto;
}

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

.local-container {
  position: relative;
}

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

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

.hidden {
  display: none;
}

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

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

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

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

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

### Affichage et configuration des participants
<a name="integrating-snap-web-setup-participants"></a>

Ensuite, créez `helpers.js`. Il contient des méthodes d’assistance que vous utiliserez pour afficher et configurer les participants :

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

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

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

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

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

  return videoEl;
}

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

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

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

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

  return participantContainer;
}
```

### Affichage des caméras et des microphones connectés
<a name="integrating-snap-web-display-cameras-microphones"></a>

Ensuite, créez `media-devices.js`. Il contient des méthodes d’assistance pour afficher les caméras et les microphones connectés à votre appareil :

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

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

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

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

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

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

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

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

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

  return { videoDevices, audioDevices };
}

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

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

### Création d’une session Camera Kit
<a name="integrating-snap-web-camera-kit-session"></a>

Créez `stages.js`. Il contient la logique permettant d’appliquer un objectif au flux de caméra et de diffuser le flux sur une scène. Nous vous recommandons de copier et de coller le bloc de code suivant dans `stages.js`. Vous pouvez ensuite revoir le code morceau par morceau pour comprendre ce qui se passe dans les sections suivantes.

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

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

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

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

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

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

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

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

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

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

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

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

  availableLenses = lenses;
  populateLensSelector(lenses);

  const snapStream = liveRenderTarget.captureStream();

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  stage = new Stage(token, strategy);

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

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

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

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

      let streamsToDisplay = streams;

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

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

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

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

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

  joining = false;
  connected = false;

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

init();
```

Pour la première partie de ce fichier, nous importons le SDK de diffusion et le SDK Web Camera Kit et initialisons les variables que nous utiliserons avec chaque SDK. Nous créons une session Camera Kit en appelant `createSession` après avoir démarré le [SDK Web Camera Kit](https://kit.snapchat.com/reference/CameraKit/web/0.7.0/index.html#bootstrapping-the-sdk). Notez qu’un objet d’élément de canevas est transmis à une session ; cela indique à Camera Kit qu’il doit s’afficher dans ce canevas.

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

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

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

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

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

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

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

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

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

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

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

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

### Récupérer les objectifs et remplir le sélecteur d’objectifs
<a name="integrating-snap-web-fetch-apply-lens"></a>

Pour récupérer vos objectifs, remplacez l’espace réservé à l’ID du groupe d’objectifs par le vôtre, qui peut être trouvé sur le [portail des développeurs de Camera Kit](https://camera-kit.snapchat.com/). Remplissez la liste déroulante de sélection des objectifs à l’aide de la fonction `populateLensSelector()` que nous créerons plus tard.

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

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

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

### Affichage de la sortie d’une session Camera Kit sur un canevas
<a name="integrating-snap-web-render-output-to-canvas"></a>

Vous pouvez utiliser la méthode [CaptureStream](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/captureStream) pour renvoyer une partie du contenu `MediaStream` du canevas. Avec un objectif appliqué, le canevas contiendra un flux vidéo provenant de la caméra. Ajoutez également des écouteurs d’événements pour les boutons qui permettent de désactiver la caméra et le microphone, ainsi que des écouteurs d’événements pour rejoindre et quitter une scène. Dans l’écouteur d’événements pour rejoindre une scène, nous faisons passer une session Camera Kit avec le `MediaStream` depuis le canevas, elle peut ainsi être diffusée sur une scène.

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

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

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

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

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

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

### Créer une fonction pour remplir la liste déroulante de l’objectif
<a name="integrating-snap-web-populate-lens-dropdown"></a>

Créez la fonction suivante pour remplir le sélecteur **Lens** avec les objectifs récupérés précédemment. Le sélecteur **Lens** est un élément de l’interface utilisateur de la page qui vous permet de choisir parmi une liste d’objectifs à appliquer au flux de la caméra. Créez également la fonction de rappel `handleLensChange` pour appliquer l’objectif spécifié lorsqu’il est sélectionné dans la liste déroulante **Lens**.

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

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

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

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

### Fournissez à Camera Kit une source multimédia pour le rendu et la diffusion d’un LocalStageStream
<a name="integrating-snap-web-publish-localstagestream"></a>

Pour diffuser un flux vidéo auquel un objectif est appliqué, créez une fonction appelée `setCameraKitSource` pour transmettre le `MediaStream` capturé précédemment sur le canevas. La `MediaStream` du canvas ne fonctionne pas pour le moment, car nous n’avons pas encore intégré notre flux de caméra local. Nous pouvons intégrer notre flux de caméra local en appelant la méthode d’assistance `getCamera` et en l’affectant à `localCamera`. Nous pouvons ensuite transmettre notre flux de caméra local (via `localCamera`) ainsi que l’objet de session à `setCameraKitSource`. La fonction `setCameraKitSource` convertit notre flux de caméra local en une [source multimédia pour CameraKit en appelant `createMediaStreamSource`](https://docs.snap.com/camera-kit/integrate-sdk/web/web-configuration#creating-a-camerakitsource). La source multimédia pour `CameraKit` est ensuite [transformée](https://docs.snap.com/camera-kit/integrate-sdk/web/web-configuration#2d-transforms) pour refléter la caméra frontale. L’effet d’objectif est ensuite appliqué à la source multimédia et rendu sur le canevas de sortie en appelant `session.play()`.

Avec l’objectif appliqué au `MediaStream` capturé depuis le canevas, nous pouvons ensuite procéder à sa diffusion sur une scène. Pour ce faire, nous créons un `LocalStageStream` avec les pistes vidéo du `MediaStream`. Une instance de `LocalStageStream` peut ensuite être transmise à un `StageStrategy` pour être diffusée.

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

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

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

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

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

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

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

Le code restant ci-dessous concerne la création et la gestion de notre scène :

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

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

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

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

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

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

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

      let streamsToDisplay = streams;

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

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

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

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

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

  joining = false;
  connected = false;

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

init();
```

### Créer le fichier package.json
<a name="integrating-snap-web-package-json"></a>

Créez `package.json` et ajoutez la configuration JSON suivante. Ce fichier définit nos dépendances et inclut une commande de script pour générer une solution groupée de notre code.

#### Configuration JSON
<a name="integrating-snap-web-package-json-code"></a>

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

### Création d’un fichier de configuration Webpack
<a name="integrating-snap-web-webpack-config"></a>

Créez `webpack.config.js` et ajoutez le code suivant. Ceci regroupe le code que nous avons créé jusqu’à présent afin que nous puissions utiliser l’instruction d’importation pour utiliser Camera Kit.

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

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

Enfin, exécutez `npm run build` pour regrouper votre JavaScript tel que défini dans le fichier de configuration du Webpack. À des fins de test, vous pouvez ensuite servir du HTML et du JavaScript à partir de votre ordinateur local. Dans cet exemple, nous utilisons le module `http.server` de Python. 

### Configurer un serveur HTTPS et effectuer des tests
<a name="integrating-snap-web-https-server-test"></a>

Pour tester notre code, nous devons configurer un serveur HTTPS. L’utilisation d’un serveur HTTPS pour le développement local et le test de l’intégration de votre application web avec le kit SDK de la caméra Snap permettra d’éviter les problèmes CORS (Cross-Origin Resource Sharing).

Ouvrez un terminal et accédez au répertoire dans lequel vous avez créé tout le code jusqu’à présent. Exécutez la commande suivante pour générer un certificat SSL/TLS auto-signé et une clé privée :

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

Cette commande crée deux fichiers : `key.pem` (la clé privée) et `cert.pem` (le certificat auto-signé). Créez un nouveau fichier Python nommé `https_server.py` et ajoutez le code suivant :

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

```
import http.server
import ssl

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

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

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

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

Ouvrez un terminal, accédez au répertoire dans lequel vous avez créé le fichier `https_server.py` et exécutez la commande suivante :

```
python3 https_server.py
```

Ceci démarre le serveur HTTPS sur https://localhost:4443, en servant les fichiers du répertoire actuel. Assurez-vous que les fichiers `cert.pem` et `key.pem` se trouvent dans le même répertoire que le fichier `https_server.py`.

Ouvrez votre navigateur et rendez-vous sur https://localhost:4443. Comme il s’agit d’un certificat SSL/TLS auto-signé, votre navigateur web ne lui fera pas confiance et vous recevrez un avertissement. Comme il s’agit uniquement d’un test, vous pouvez ignorer l’avertissement. Vous devriez alors voir l’effet de réalité augmentée du filtre que vous avez choisi s’appliquer à l’image de votre caméra sur l’écran.

Notez que cette configuration utilisant les modules `http.server` et `ssl` intégrés à Python convient au développement local et aux tests, mais n’est pas recommandée pour un environnement de production. Le certificat SSL/TLS auto-signé utilisé dans cette configuration n’est pas reconnu par les navigateurs Web et les autres clients, ce qui signifie que les utilisateurs rencontreront des avertissements de sécurité lorsqu’ils accèderont au serveur. De plus, bien que nous utilisions les modules intégrés http.server et ssl de Python dans cet exemple, vous pouvez choisir d’utiliser une autre solution de serveur HTTPS.

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

Pour intégrer le SDK Camera Kit de Snap au SDK de diffusion Android IVS, vous devez installer le SDK Camera Kit, initialiser une session Camera Kit, appliquer un objectif puis transmettre la sortie de la session Camera Kit à la source d’entrée d’image personnalisée.

Pour installer le SDK Camera Kit, ajoutez ce qui suit au fichier `build.gradle` de votre module. Remplacez `$cameraKitVersion` par la [dernière version du SDK Camera Kit](https://docs.snap.com/camera-kit/integrate-sdk/mobile/changelog-mobile).

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

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

Initialisez et obtenez un `cameraKitSession`. Camera Kit fournit également un encapsuleur pratique pour les API [CameraX](https://developer.android.com/media/camera/camerax) d’Android, ainsi, vous n’avez pas à écrire de logique compliquée pour utiliser CameraX avec Camera Kit. Vous pouvez utiliser l’objet `CameraXImageProcessorSource` comme [source](https://snapchat.github.io/camera-kit-reference/api/android/latest/-camera-kit/com.snap.camerakit/-source/index.html) pour [ImageProcessor](https://snapchat.github.io/camera-kit-reference/api/android/latest/-camera-kit/com.snap.camerakit/-image-processor/index.html), cela vous permet de démarrer le flux d’images en mode caméra.

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

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

        setContentView(R.layout.activity_main);

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

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

### Récupération et application des lentilles
<a name="integrating-snap-android-fetch-apply-lenses"></a>

Vous pouvez configurer les objectifs et les organiser dans le carrousel du [Portail des développeurs Camera Kit](https://camera-kit.snapchat.com/) :

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

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

Pour diffuser, envoyez des images traitées vers le `Surface` sous-jacent d’une source d’image personnalisée. Utilisez un objet `DeviceDiscovery` et créez une `CustomImageSource` pour renvoyer une `SurfaceSource`. Vous pouvez ensuite afficher le résultat d’une session `CameraKit` sur la `Surface` sous-jacente, fournie par une `SurfaceSource`.

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

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

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

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

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

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

# Utilisation du remplacement de l’arrière-plan avec le SDK de diffusion IVS
<a name="broadcast-3p-camera-filters-background-replacement"></a>

Le remplacement de l’arrière-plan est un type de filtre de caméra permettant aux créateurs de flux en direct de modifier leur arrière-plan. Comme le montre le diagramme suivant, le remplacement de votre arrière-plan implique les conditions suivantes :

1. Obtenir une image de caméra à partir du flux de caméra en direct.

1. La segmenter en composants de premier plan et d’arrière-plan à l’aide de Google ML Kit.

1. Combiner le masque de segmentation obtenu avec une image d’arrière-plan personnalisée.

1. Le transmettre à une source d’image personnalisée pour diffusion.

![\[Flux de travail pour mettre en œuvre le remplacement de l’arrière-plan.\]](http://docs.aws.amazon.com/fr_fr/ivs/latest/RealTimeUserGuide/images/3P_Camera_Filters_Background_Replacement.png)


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

Cette section suppose que vous connaissez déjà la [diffusion et l’abonnement à des vidéos à l’aide du SDK de diffusion Web](https://docs.aws.amazon.com//ivs/latest/RealTimeUserGuide/getting-started-pub-sub-web.html).

Pour remplacer l’arrière-plan d’un flux en direct par une image personnalisée, utilisez le [modèle de segmentation selfie](https://developers.google.com/mediapipe/solutions/vision/image_segmenter#selfie-model) avec le [segmenteur d’image MediaPipe](https://developers.google.com/mediapipe/solutions/vision/image_segmenter). Il s’agit d’un modèle de machine learning qui identifie les pixels de l’image vidéo situés au premier plan ou à l’arrière-plan. Vous pouvez ensuite utiliser les résultats du modèle pour remplacer l’arrière-plan d’une diffusion en direct. Pour ce faire, copiez les pixels de premier plan du flux vidéo vers une image personnalisée représentant le nouvel arrière-plan.

Pour intégrer le remplacement en arrière-plan au SDK de diffusion Web IVS en temps réel, vous devez effectuer les actions suivantes :

1. Installez MediaPipe et Webpack. (Notre exemple utilise Webpack comme bundler, mais vous pouvez utiliser n’importe quel bundler de votre choix.)

1. Créez vos `index.html`.

1. Ajoutez des éléments multimédias.

1. Ajoutez une balise de script.

1. Créez vos `app.js`.

1. Chargez une image d’arrière-plan personnalisée.

1. Créez une instance de `ImageSegmenter`.

1. Affichez le flux vidéo sur un canevas.

1. Créez une logique de remplacement de l’arrière-plan.

1. Créez un fichier de configuration Webpack.

1. Regroupez votre fichier JavaScript.

### Installation de MediaPipe et de Webpack
<a name="background-replacement-web-install-mediapipe-webpack"></a>

Pour commencer, installez les packages npm `@mediapipe/tasks-vision` et `webpack`. L’exemple ci-dessous utilise Webpack comme un bundler JavaScript, mais vous pouvez utiliser un autre bundler si vous préférez.

#### JavaScript
<a name="background-replacement-web-install-mediapipe-webpack-code"></a>

```
npm i @mediapipe/tasks-vision webpack webpack-cli
```

Assurez-vous également de mettre à jour votre script `package.json` pour spécifier `webpack` comme script de compilation :

#### JavaScript
<a name="background-replacement-web-update-package-json-code"></a>

```
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack"
  },
```

### Créez le fichier index.html
<a name="background-replacement-web-create-index"></a>

Puis, créez le modèle de code HTML et importez le SDK de diffusion Web sous forme de balise de script. Dans le code suivant, veillez à remplacer `<SDK version>` par la version du SDK de diffusion que vous utilisez.

#### JavaScript
<a name="background-replacement-web-create-index-code"></a>

```
<!DOCTYPE html>
<html lang="en">

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

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

<body>

</body>
</html>
```

### Ajouter des éléments multimédias
<a name="background-replacement-web-add-media-elements"></a>

Ajoutez ensuite un élément vidéo ainsi que deux éléments de canevas dans la balise body. L’élément vidéo contiendra le flux de votre caméra en direct et servira d’entrée dans le segmenteur d’image MediaPipe. Le premier élément du canevas sert à afficher un aperçu du flux qui sera diffusé. Le deuxième élément du canevas sert à afficher l’image personnalisée qui sera utilisée comme arrière-plan. Comme le second canevas qui contient l’image personnalisée sert uniquement de source pour copier des pixels par programmation vers le canevas final, il est masqué.

#### JavaScript
<a name="background-replacement-web-add-media-elements-code"></a>

```
<div class="row local-container">
      <video id="webcam" autoplay style="display: none"></video>
    </div>
    <div class="row local-container">
      <canvas id="canvas" width="640px" height="480px"></canvas>

      <div class="column" id="local-media"></div>
      <div class="static-controls hidden" id="local-controls">
        <button class="button" id="mic-control">Mute Mic</button>
        <button class="button" id="camera-control">Mute Camera</button>
      </div>
    </div>
    <div class="row local-container">
      <canvas id="background" width="640px" height="480px" style="display: none"></canvas>
    </div>
```

### Ajout d’une balise de script
<a name="background-replacement-web-add-script-tag"></a>

Ajoutez une balise de script pour charger un fichier JavaScript intégré qui contiendra le code permettant de remplacer l’arrière-plan et de le diffuser sur une scène :

```
<script src="./dist/bundle.js"></script>
```

### Crée le fichier app.js
<a name="background-replacement-web-create-appjs"></a>

Créez ensuite un fichier JavaScript pour obtenir les objets des éléments du canevas ainsi que de la vidéo créés dans la page HTML. Importez les modules `ImageSegmenter` et `FilesetResolver`. Le module `ImageSegmenter` sert à effectuer la tâche de segmentation.

#### JavaScript
<a name="create-appjs-import-imagesegmenter-fileresolver-code"></a>

```
const canvasElement = document.getElementById("canvas");
const background = document.getElementById("background");
const canvasCtx = canvasElement.getContext("2d");
const backgroundCtx = background.getContext("2d");
const video = document.getElementById("webcam");

import { ImageSegmenter, FilesetResolver } from "@mediapipe/tasks-vision";
```

Créez ensuite une fonction appelée `init()` pour récupérer le MediaStream depuis la caméra de l’utilisateur, invoquez ensuite une fonction de rappel chaque fois qu’une image de caméra finit de se charger. Ajoutez des écouteurs d’événements pour les boutons qui permettent de rejoindre et de quitter une scène.

Notez que lorsque vous rejoignez une scène, nous transmettons une variable nommée `segmentationStream`. Il s’agit d’un flux vidéo capturé à partir d’un élément du canevas. Il contient une image de premier plan superposée à l’image personnalisée représentant l’arrière-plan. Plus tard, ce flux personnalisé sera utilisé pour créer une instance d’un `LocalStageStream`, qui pourra être diffusé sur une scène.

#### JavaScript
<a name="create-appjs-create-init-code"></a>

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

  cameraButton.addEventListener("click", () => {
    const isMuted = !cameraStageStream.isMuted;
    cameraStageStream.setMuted(isMuted);
    cameraButton.innerText = isMuted ? "Show Camera" : "Hide Camera";
  });

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

  localCamera = await getCamera(videoDevicesList.value);
  const segmentationStream = canvasElement.captureStream();

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

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

### Chargement d’une image d’arrière-plan personnalisée
<a name="background-replacement-web-background-image"></a>

Au bas de la fonction `init`, ajoutez du code pour appeler une fonction nommée `initBackgroundCanvas`. Cette dernière charge une image personnalisée à partir d’un fichier local et effectue un rendu sur un canevas. Nous définirons cette fonction à l’étape suivante. Assignez le `MediaStream` extrait par la caméra de l’utilisateur à l’objet vidéo. Plus tard, cet objet vidéo sera transmis au segmenteur d’image. Définissez également une fonction nommée `renderVideoToCanvas` comme fonction de rappel à invoquer chaque fois que le chargement d’une image vidéo est terminé. Nous définirons cette fonction lors d’une étape ultérieure.

#### JavaScript
<a name="background-replacement-web-load-background-image-code"></a>

```
initBackgroundCanvas();

  video.srcObject = localCamera;
  video.addEventListener("loadeddata", renderVideoToCanvas);
```

Implémentons la fonction `initBackgroundCanvas` qui charge une image à partir d’un fichier local. Dans cet exemple, nous utilisons l’image d’une plage comme arrière-plan personnalisé. Le canevas qui contient l’image personnalisée sera masqué, car vous le fusionnerez avec les pixels de premier plan de l’élément du canevas qui contient le flux de caméra.

#### JavaScript
<a name="background-replacement-web-implement-initBackgroundCanvas-code"></a>

```
const initBackgroundCanvas = () => {
  let img = new Image();
  img.src = "beach.jpg";

  img.onload = () => {
    backgroundCtx.clearRect(0, 0, canvas.width, canvas.height);
    backgroundCtx.drawImage(img, 0, 0);
  };
};
```

### Créez une instance d’ImageSegmenter
<a name="background-replacement-web-imagesegmenter"></a>

Créez ensuite une instance de `ImageSegmenter`, qui segmentera l’image et renverra le résultat sous forme de masque. Lors de la création d’une instance d’un `ImageSegmenter`, vous utiliserez le [modèle de segmentation selfie](https://developers.google.com/mediapipe/solutions/vision/image_segmenter#selfie-model).

#### JavaScript
<a name="background-replacement-web-imagesegmenter-code"></a>

```
const createImageSegmenter = async () => {
  const audio = await FilesetResolver.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.2/wasm");

  imageSegmenter = await ImageSegmenter.createFromOptions(audio, {
    baseOptions: {
      modelAssetPath: "https://storage.googleapis.com/mediapipe-models/image_segmenter/selfie_segmenter/float16/latest/selfie_segmenter.tflite",
      delegate: "GPU",
    },
    runningMode: "VIDEO",
    outputCategoryMask: true,
  });
};
```

### Affichage du flux vidéo sur un canevas
<a name="background-replacement-web-render-video-to-canvas"></a>

Créez ensuite la fonction servant à afficher le flux vidéo vers l’autre élément du canevas. Afin de pouvoir en extraire les pixels de premier plan à l’aide de l’API Canvas 2D, nous devons afficher le flux vidéo sur un canevas. Ce faisant, nous allons également transmettre une image vidéo à notre instance de `ImageSegmenter`. Pour ce faire, nous utilisons la méthode [SegmentForVideo](https://developers.google.com/mediapipe/api/solutions/js/tasks-vision.imagesegmenter#imagesegmentersegmentforvideo) pour segmenter le premier plan par rapport à l’arrière-plan de l’image vidéo. Lorsque la méthode [SegmentForVideo](https://developers.google.com/mediapipe/api/solutions/js/tasks-vision.imagesegmenter#imagesegmentersegmentforvideo) revient, elle invoque notre fonction de rappel personnalisée `replaceBackground` afin de remplacer l’arrière-plan.

#### JavaScript
<a name="background-replacement-web-render-video-to-canvas-code"></a>

```
const renderVideoToCanvas = async () => {
  if (video.currentTime === lastWebcamTime) {
    window.requestAnimationFrame(renderVideoToCanvas);
    return;
  }
  lastWebcamTime = video.currentTime;
  canvasCtx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);

  if (imageSegmenter === undefined) {
    return;
  }

  let startTimeMs = performance.now();

  imageSegmenter.segmentForVideo(video, startTimeMs, replaceBackground);
};
```

### Création d’une logique de remplacement de l’arrière-plan
<a name="background-replacement-web-logic"></a>

Créez la fonction `replaceBackground`. Cette dernière fusionne l’image d’arrière-plan personnalisée avec le premier plan du flux de caméra pour remplacer l’arrière-plan. Pour commencer, la fonction extrait les données de pixels sous-jacentes de l’image d’arrière-plan personnalisée et du flux vidéo à partir des deux éléments du canevas créés précédemment. Elle parcourt ensuite le masque fourni par `ImageSegmenter`, qui indique quels pixels se trouvent au premier plan. Au fur et à mesure que la fonction parcourt le masque, elle copie de manière sélective les pixels qui contiennent le flux de caméra de l’utilisateur vers les données de pixels d’arrière-plan correspondantes. Après cela, elle convertit les données finales en pixels avec le premier plan copié sur l’arrière-plan et les dessine sur un canevas.

#### JavaScript
<a name="background-replacement-web-logic-create-replacebackground-code"></a>

```
function replaceBackground(result) {
  let imageData = canvasCtx.getImageData(0, 0, video.videoWidth, video.videoHeight).data;
  let backgroundData = backgroundCtx.getImageData(0, 0, video.videoWidth, video.videoHeight).data;
  const mask = result.categoryMask.getAsFloat32Array();
  let j = 0;

  for (let i = 0; i < mask.length; ++i) {
    const maskVal = Math.round(mask[i] * 255.0);

    j += 4;
  // Only copy pixels on to the background image if the mask indicates they are in the foreground
    if (maskVal < 255) {
      backgroundData[j] = imageData[j];
      backgroundData[j + 1] = imageData[j + 1];
      backgroundData[j + 2] = imageData[j + 2];
      backgroundData[j + 3] = imageData[j + 3];
    }
  }

 // Convert the pixel data to a format suitable to be drawn to a canvas
  const uint8Array = new Uint8ClampedArray(backgroundData.buffer);
  const dataNew = new ImageData(uint8Array, video.videoWidth, video.videoHeight);
  canvasCtx.putImageData(dataNew, 0, 0);
  window.requestAnimationFrame(renderVideoToCanvas);
}
```

À titre de référence, voici le fichier `app.js` complet contenant toute la logique ci-dessus :

#### JavaScript
<a name="background-replacement-web-logic-app-js-code"></a>

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

// All helpers are expose on 'media-devices.js' and 'dom.js'
const { setupParticipant } = window;

const { Stage, LocalStageStream, SubscribeType, StageEvents, ConnectionState, StreamType } = IVSBroadcastClient;
const canvasElement = document.getElementById("canvas");
const background = document.getElementById("background");
const canvasCtx = canvasElement.getContext("2d");
const backgroundCtx = background.getContext("2d");
const video = document.getElementById("webcam");

import { ImageSegmenter, FilesetResolver } from "@mediapipe/tasks-vision";

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

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

// Stage management
let stage;
let joining = false;
let connected = false;
let localCamera;
let localMic;
let cameraStageStream;
let micStageStream;
let imageSegmenter;
let lastWebcamTime = -1;

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

  cameraButton.addEventListener("click", () => {
    const isMuted = !cameraStageStream.isMuted;
    cameraStageStream.setMuted(isMuted);
    cameraButton.innerText = isMuted ? "Show Camera" : "Hide Camera";
  });

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

  localCamera = await getCamera(videoDevicesList.value);
  const segmentationStream = canvasElement.captureStream();

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

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

  initBackgroundCanvas();

  video.srcObject = localCamera;
  video.addEventListener("loadeddata", renderVideoToCanvas);
};

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

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

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

  // Retrieve the User Media currently set on the page
  localMic = await getMic(audioDevicesList.value);

  cameraStageStream = new LocalStageStream(segmentationStream.getVideoTracks()[0]);
  micStageStream = new LocalStageStream(localMic.getAudioTracks()[0]);

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

  stage = new Stage(token, strategy);

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

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

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

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

    let streamsToDisplay = streams;

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

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

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

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

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

  joining = false;
  connected = false;

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

function replaceBackground(result) {
  let imageData = canvasCtx.getImageData(0, 0, video.videoWidth, video.videoHeight).data;
  let backgroundData = backgroundCtx.getImageData(0, 0, video.videoWidth, video.videoHeight).data;
  const mask = result.categoryMask.getAsFloat32Array();
  let j = 0;

  for (let i = 0; i < mask.length; ++i) {
    const maskVal = Math.round(mask[i] * 255.0);

    j += 4;
    if (maskVal < 255) {
      backgroundData[j] = imageData[j];
      backgroundData[j + 1] = imageData[j + 1];
      backgroundData[j + 2] = imageData[j + 2];
      backgroundData[j + 3] = imageData[j + 3];
    }
  }
  const uint8Array = new Uint8ClampedArray(backgroundData.buffer);
  const dataNew = new ImageData(uint8Array, video.videoWidth, video.videoHeight);
  canvasCtx.putImageData(dataNew, 0, 0);
  window.requestAnimationFrame(renderVideoToCanvas);
}

const createImageSegmenter = async () => {
  const audio = await FilesetResolver.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.2/wasm");

  imageSegmenter = await ImageSegmenter.createFromOptions(audio, {
    baseOptions: {
      modelAssetPath: "https://storage.googleapis.com/mediapipe-models/image_segmenter/selfie_segmenter/float16/latest/selfie_segmenter.tflite",
      delegate: "GPU",
    },
    runningMode: "VIDEO",
    outputCategoryMask: true,
  });
};

const renderVideoToCanvas = async () => {
  if (video.currentTime === lastWebcamTime) {
    window.requestAnimationFrame(renderVideoToCanvas);
    return;
  }
  lastWebcamTime = video.currentTime;
  canvasCtx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);

  if (imageSegmenter === undefined) {
    return;
  }

  let startTimeMs = performance.now();

  imageSegmenter.segmentForVideo(video, startTimeMs, replaceBackground);
};

const initBackgroundCanvas = () => {
  let img = new Image();
  img.src = "beach.jpg";

  img.onload = () => {
    backgroundCtx.clearRect(0, 0, canvas.width, canvas.height);
    backgroundCtx.drawImage(img, 0, 0);
  };
};

createImageSegmenter();
init();
```

### Création d’un fichier de configuration Webpack
<a name="background-replacement-web-webpack-config"></a>

Ajoutez cette configuration à votre fichier de configuration Webpack pour regrouper `app.js`, afin que les appels d’importation fonctionnent :

#### JavaScript
<a name="background-replacement-web-webpack-config-code"></a>

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

### Regroupement de vos fichiers JavaScript
<a name="background-replacement-web-bundle-javascript"></a>

```
npm run build
```

Démarrez un serveur HTTP simple à partir du répertoire contenant `index.html` puis ouvrez `localhost:8000` pour voir le résultat :

```
python3 -m http.server -d ./
```

## Android
<a name="background-replacement-android"></a>

Pour remplacer l’arrière-plan de votre diffusion en direct, utilisez l’API de segmentation des selfies de [Google ML Kit](https://developers.google.com/ml-kit/vision/selfie-segmentation). L’API de segmentation des selfies accepte une image de caméra en entrée et renvoie un masque qui fournit un score de confiance pour chaque pixel de l’image. Cela permet de faire la différence entre le premier plan et l’arrière-plan. Sur la base du score de confiance, vous pouvez ensuite récupérer la couleur de pixel correspondante à partir de l’image d’arrière-plan ou de celle de premier plan. Ce processus se poursuit jusqu’à ce que tous les scores de confiance du masque aient été examinés. Le résultat donne un nouveau tableau de couleurs de pixels qui contient des pixels de premier plan combinés à des pixels de l’image d’arrière-plan.

Pour intégrer le remplacement en arrière-plan au SDK de diffusion Android IVS en temps réel, vous devez effectuer les actions suivantes :

1. Installez les bibliothèques CameraX et le Google ML Kit.

1. Initialisez les variables standard.

1. Créez une source d’image personnalisée.

1. Gérez les cadres des caméras.

1. Transférez les images de la caméra au Google ML Kit.

1. Superposez le premier plan du cadre de la caméra sur votre arrière-plan personnalisé.

1. Transférez la nouvelle image à une source d’image personnalisée.

### Installation des bibliothèques CameraX et de Google ML Kit
<a name="background-replacement-android-install-camerax-googleml"></a>

Pour extraire des images du flux de caméra en direct, utilisez la bibliothèque CameraX d’Android. Pour installer la bibliothèque CameraX et le Google ML Kit, ajoutez ce qui suit au fichier de votre module `build.gradle`. Remplacez `${camerax_version}` et `${google_ml_kit_version}` par la dernière version des bibliothèques [CameraX](https://developer.android.com/jetpack/androidx/releases/camera) et [Google ML Kit](https://developers.google.com/ml-kit/vision/selfie-segmentation/android), respectivement. 

#### Java
<a name="background-replacement-android-install-camerax-googleml-code"></a>

```
implementation "com.google.mlkit:segmentation-selfie:${google_ml_kit_version}"
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-lifecycle:${camerax_version}"
```

Importez les bibliothèques suivantes :

#### Java
<a name="background-replacement-android-import-libraries-code"></a>

```
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageProxy
import androidx.camera.lifecycle.ProcessCameraProvider
import com.google.mlkit.vision.segmentation.selfie.SelfieSegmenterOptions
```

### Initialisation des variables standard
<a name="background-replacement-android-initialize-variables"></a>

Initialisez une instance de `ImageAnalysis` ainsi qu’une instance de `ExecutorService` :

#### Java
<a name="background-replacement-android-initialize-imageanalysis-executorservice-code"></a>

```
private lateinit var binding: ActivityMainBinding
private lateinit var cameraExecutor: ExecutorService
private var analysisUseCase: ImageAnalysis? = null
```

Initialisez une instance du segmenteur dans [STREAM\$1MODE](https://developers.google.com/ml-kit/vision/selfie-segmentation/android#detector_mode) :

#### Java
<a name="background-replacement-android-initialize-segmenter-code"></a>

```
private val options =
        SelfieSegmenterOptions.Builder()
            .setDetectorMode(SelfieSegmenterOptions.STREAM_MODE)
            .build()

private val segmenter = Segmentation.getClient(options)
```

### Création d’une source d’image personnalisée
<a name="background-replacement-android-create-image-source"></a>

Dans le cadre de votre activité `onCreate`, créez une instance d’un objet `DeviceDiscovery` puis créez une source d’image personnalisée. La `Surface` fournie par la source d’image personnalisée recevra l’image finale, le premier plan étant superposé à une image d’arrière-plan personnalisée. Vous allez ensuite créer une instance d’un `ImageLocalStageStream` à l’aide de la source d’image personnalisée. L’instance d’un `ImageLocalStageStream` (nommée `filterStream` dans cet exemple) pourra ensuite être diffusée sur une scène. Consultez le [Guide du SDK de diffusion Android IVS](broadcast-android.md) pour obtenir des instructions sur la configuration d’une scène. Enfin, créez également un fil qui sera utilisé pour gérer la caméra.

#### Java
<a name="background-replacement-android-create-image-source-code"></a>

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

cameraExecutor = Executors.newSingleThreadExecutor()
```

### Gestion des cadres des caméras
<a name="background-replacement-android-camera-frames"></a>

Créez ensuite une fonction pour initialiser la caméra. Cette fonction utilise la bibliothèque CameraX pour extraire des images du flux de caméra en direct. Tout d’abord, vous devez créer une instance d’un `ProcessCameraProvider` appelée `cameraProviderFuture`. Cet objet représente le résultat futur de l’obtention d’un fournisseur de caméras. Ensuite, chargez une image de votre projet sous forme de bitmap. Cet exemple utilise l’image d’une plage comme arrière-plan, mais il peut s’agir de l’image de votre choix.

Vous ajoutez ensuite un écouteur à `cameraProviderFuture`. Cet écouteur est averti lorsque la caméra est disponible ou si une erreur survient lors du processus d’obtention d’un fournisseur de caméra.

#### Java
<a name="background-replacement-android-initialize-camera-code"></a>

```
private fun startCamera(surface: Surface) {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
        val imageResource = R.drawable.beach
        val bgBitmap: Bitmap = BitmapFactory.decodeResource(resources, imageResource)
        var resultBitmap: Bitmap;


        cameraProviderFuture.addListener({
            val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

            
                if (mediaImage != null) {
                    val inputImage =
                        InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)

                            resultBitmap = overlayForeground(mask, maskWidth, maskHeight, inputBitmap, backgroundPixels)
                            canvas = surface.lockCanvas(null);
                            canvas.drawBitmap(resultBitmap, 0f, 0f, null)

                            surface.unlockCanvasAndPost(canvas);

                        }
                        .addOnFailureListener { exception ->
                            Log.d("App", exception.message!!)
                        }
                        .addOnCompleteListener {
                            imageProxy.close()
                        }

                }
            };

            val cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA

            try {
                // Unbind use cases before rebinding
                cameraProvider.unbindAll()

                // Bind use cases to camera
                cameraProvider.bindToLifecycle(this, cameraSelector, analysisUseCase)

            } catch(exc: Exception) {
                Log.e(TAG, "Use case binding failed", exc)
            }

        }, ContextCompat.getMainExecutor(this))
    }
```

Dans l’écouteur, créez `ImageAnalysis.Builder` pour accéder à chaque image individuelle depuis le flux de caméra en direct. Définissez la stratégie de pression de retour sur `STRATEGY_KEEP_ONLY_LATEST`. Cela garantit qu’une seule image de caméra n’est traitée à la fois. Convertissez chaque image de caméra en image bitmap. Cela permet d’extraire ses pixels pour les combiner ultérieurement avec l’image d’arrière-plan personnalisée.

#### Java
<a name="background-replacement-android-create-imageanalysisbuilder-code"></a>

```
val imageAnalyzer = ImageAnalysis.Builder()
analysisUseCase = imageAnalyzer
    .setTargetResolution(Size(360, 640))
    .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
    .build()

analysisUseCase?.setAnalyzer(cameraExecutor) { imageProxy: ImageProxy ->
    val mediaImage = imageProxy.image
    val tempBitmap = imageProxy.toBitmap();
    val inputBitmap = tempBitmap.rotate(imageProxy.imageInfo.rotationDegrees.toFloat())
```

### Transfert des images de la caméra au Google ML Kit
<a name="background-replacement-android-frames-to-mlkit"></a>

Ensuite, créez une `InputImage` et transmettez-la à l’instance du segmenteur pour traitement. Une `InputImage` peut être créée à partir d’un `ImageProxy` fourni par l’instance de `ImageAnalysis`. Une fois qu’une `InputImage` est fournie au segmenteur, elle renvoie un masque avec des scores de confiance qui indiquent la probabilité qu’un pixel soit au premier plan ou en arrière-plan. Ce masque fournit également des données concernant la largeur et la hauteur. Ces données serviront à créer un nouveau tableau contenant les pixels d’arrière-plan de l’image d’arrière-plan personnalisée chargée précédemment.

#### Java
<a name="background-replacement-android-frames-to-mlkit-code"></a>

```
if (mediaImage != null) {
        val inputImage =
            InputImage.fromMediaImag


segmenter.process(inputImage)
    .addOnSuccessListener { segmentationMask ->
        val mask = segmentationMask.buffer
        val maskWidth = segmentationMask.width
        val maskHeight = segmentationMask.height
        val backgroundPixels = IntArray(maskWidth * maskHeight)
        bgBitmap.getPixels(backgroundPixels, 0, maskWidth, 0, 0, maskWidth, maskHeight)
```

### Superposition du premier plan du cadre de la caméra sur votre arrière-plan personnalisé
<a name="background-replacement-android-overlay-frame-foreground"></a>

Avec le masque contenant les scores de confiance, le cadre de la caméra sous forme de bitmap et les pixels de couleur de l’image d’arrière-plan personnalisée, vous disposez du nécessaire pour superposer le premier plan à votre arrière-plan personnalisé. La fonction `overlayForeground` est ensuite appelée avec les paramètres suivants :

#### Java
<a name="background-replacement-android-call-overlayforeground-code"></a>

```
resultBitmap = overlayForeground(mask, maskWidth, maskHeight, inputBitmap, backgroundPixels)
```

Cette fonction parcourt le masque et vérifie les valeurs de confiance. Elle permet de déterminer s’il faut obtenir la couleur de pixel correspondante à partir de l’image d’arrière-plan ou du cadre de la caméra. Si la valeur de confiance indique qu’un pixel du masque se trouve très probablement en arrière-plan, la fonction obtiendra la couleur de pixel correspondante à partir de l’image d’arrière-plan. Dans le cas contraire, elle obtiendra la couleur de pixel correspondante du cadre de la caméra pour créer le premier plan. Une fois que la fonction a fini d’itérer dans le masque, un nouveau bitmap sera créé à l’aide du nouveau tableau de pixels de couleur puis sera renvoyé. Ce nouveau bitmap contient le premier plan superposé à l’arrière-plan personnalisé.

#### Java
<a name="background-replacement-android-run-overlayforeground-code"></a>

```
private fun overlayForeground(
        byteBuffer: ByteBuffer,
        maskWidth: Int,
        maskHeight: Int,
        cameraBitmap: Bitmap,
        backgroundPixels: IntArray
    ): Bitmap {
        @ColorInt val colors = IntArray(maskWidth * maskHeight)
        val cameraPixels = IntArray(maskWidth * maskHeight)

        cameraBitmap.getPixels(cameraPixels, 0, maskWidth, 0, 0, maskWidth, maskHeight)

        for (i in 0 until maskWidth * maskHeight) {
            val backgroundLikelihood: Float = 1 - byteBuffer.getFloat()

            // Apply the virtual background to the color if it's not part of the foreground
            if (backgroundLikelihood > 0.9) {
                // Get the corresponding pixel color from the background image
                // Set the color in the mask based on the background image pixel color
                colors[i] = backgroundPixels.get(i)
            } else {
                // Get the corresponding pixel color from the camera frame
                // Set the color in the mask based on the camera image pixel color
                colors[i] = cameraPixels.get(i)
            }
        }

        return Bitmap.createBitmap(
            colors, maskWidth, maskHeight, Bitmap.Config.ARGB_8888
        )
    }
```

### Transférez la nouvelle image à une source d’image personnalisée
<a name="background-replacement-android-custom-image-source"></a>

Vous pouvez ensuite écrire le nouveau bitmap dans une `Surface` fournie par une source d’image personnalisée. Cela diffusera le bitmap sur votre scène.

#### Java
<a name="background-replacement-android-custom-image-source-code"></a>

```
resultBitmap = overlayForeground(mask, inputBitmap, mutableBitmap, bgBitmap)
canvas = surface.lockCanvas(null);
canvas.drawBitmap(resultBitmap, 0f, 0f, null)
```

Voici la fonction complète qui permet d’obtenir les images de la caméra, les transmettre au segmenteur et les superposer à l’arrière-plan :

#### Java
<a name="background-replacement-android-custom-image-source-startcamera-code"></a>

```
@androidx.annotation.OptIn(androidx.camera.core.ExperimentalGetImage::class)
    private fun startCamera(surface: Surface) {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
        val imageResource = R.drawable.clouds
        val bgBitmap: Bitmap = BitmapFactory.decodeResource(resources, imageResource)
        var resultBitmap: Bitmap;

        cameraProviderFuture.addListener({
            // Used to bind the lifecycle of cameras to the lifecycle owner
            val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

            val imageAnalyzer = ImageAnalysis.Builder()
            analysisUseCase = imageAnalyzer
                .setTargetResolution(Size(720, 1280))
                .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
                .build()

            analysisUseCase!!.setAnalyzer(cameraExecutor) { imageProxy: ImageProxy ->
                val mediaImage = imageProxy.image
                val tempBitmap = imageProxy.toBitmap();
                val inputBitmap = tempBitmap.rotate(imageProxy.imageInfo.rotationDegrees.toFloat())

                if (mediaImage != null) {
                    val inputImage =
                        InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)

                    segmenter.process(inputImage)
                        .addOnSuccessListener { segmentationMask ->
                            val mask = segmentationMask.buffer
                            val maskWidth = segmentationMask.width
                            val maskHeight = segmentationMask.height
                            val backgroundPixels = IntArray(maskWidth * maskHeight)
                            bgBitmap.getPixels(backgroundPixels, 0, maskWidth, 0, 0, maskWidth, maskHeight)

                            resultBitmap = overlayForeground(mask, maskWidth, maskHeight, inputBitmap, backgroundPixels)
                            canvas = surface.lockCanvas(null);
                            canvas.drawBitmap(resultBitmap, 0f, 0f, null)

                            surface.unlockCanvasAndPost(canvas);

                        }
                        .addOnFailureListener { exception ->
                            Log.d("App", exception.message!!)
                        }
                        .addOnCompleteListener {
                            imageProxy.close()
                        }

                }
            };

            val cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA

            try {
                // Unbind use cases before rebinding
                cameraProvider.unbindAll()

                // Bind use cases to camera
                cameraProvider.bindToLifecycle(this, cameraSelector, analysisUseCase)

            } catch(exc: Exception) {
                Log.e(TAG, "Use case binding failed", exc)
            }

        }, ContextCompat.getMainExecutor(this))
    }
```

# Kit SDK de diffusion IVS : Modes audio mobiles \$1 Diffusion en temps réel
<a name="broadcast-mobile-audio-modes"></a>

La qualité audio est un élément important de toute expérience multimédia en équipe réelle. Il n’existe pas de configuration audio universelle adaptée à tous les cas d’utilisation. Pour garantir à vos utilisateurs la meilleure expérience possible lorsqu’ils écoutent un flux IVS en temps réel, nos SDK mobiles offrent plusieurs configurations audio prédéfinies, ainsi que des personnalisations plus puissantes selon les besoins.

## Introduction
<a name="broadcast-mobile-audio-modes-introduction"></a>

Les SDK de diffusion mobile IVS fournissent une classe `StageAudioManager`. Cette classe est conçue pour être le point de contact unique qui permet de contrôler les modes audio sous-jacents sur les deux plateformes. Sur Android, elle contrôle l’[AudioManager](https://developer.android.com/reference/android/media/AudioManager), notamment le mode audio, la source audio, le type de contenu, l’utilisation et les appareils de communication. Sur iOS, elle contrôle l’application [AVAudioSession](https://developer.apple.com/documentation/avfaudio/avaudiosession) et détermine si [voiceProcessing](https://developer.apple.com/documentation/avfaudio/avaudioionode/3152101-voiceprocessingenabled?language=objc) est activé.

**Important** : N’interagissez pas avec `AVAudioSession` ou `AudioManager` directement à l’aide du SDK de diffusion en temps réel IVS ou pendant qu’il est actif. Cela pourrait entraîner des pertes de son, ou ce dernier pourrait être enregistré ou lu sur le mauvais appareil.

Avant de créer votre premier `DeviceDiscovery` ou votre premier objet `Stage`, vous devez configurer la classe `StageAudioManager`.

------
#### [ Android (Kotlin) ]

```
StageAudioManager.getInstance(context).setPreset(StageAudioManager.UseCasePreset.VIDEO_CHAT) // The default value

val deviceDiscovery = DeviceDiscovery(context)
val stage = Stage(context, token, this)

// Other Stage implementation code
```

------
#### [ iOS (Swift) ]

```
IVSStageAudioManager.sharedInstance().setPreset(.videoChat) // The default value

let deviceDiscovery = IVSDeviceDiscovery()
let stage = try? IVSStage(token: token, strategy: self)

// Other Stage implementation code
```

------

Si rien n’est défini sur `StageAudioManager` avant l’initialisation d’une instance `DeviceDiscovery` ou `Stage`, le préréglage `VideoChat` s’applique automatiquement.

## Préréglages de mode audio
<a name="broadcast-mobile-audio-modes-presets"></a>

Le SDK de diffusion en temps réel fournit trois préréglages, chacun est adapté aux cas d’utilisation courants, comme décrit ci-dessous. Pour chaque préréglage, nous traitons cinq catégories clés qui différencient les préréglages les uns des autres.

La catégorie **Variateur de volume** se réfère au type de volume (volume multimédia ou volume d’appel) qui est utilisé ou modifié via les boutons de volume physiques de l’appareil. À noter que cela affecte le volume lors du changement de mode audio. Par exemple, si le volume de l’appareil est réglé au maximum en utilisant le préréglage Chat vidéo. le passage au préréglage Abonnement seul entraîne un niveau de volume différent de celui du système d’exploitation, ce qui peut entraîner un changement de volume important sur l’appareil.

### Chat vidéo
<a name="audio-modes-presets-video-chat"></a>

Il s’agit du préréglage par défaut. Il permet à l’appareil local d’avoir une conversation en temps réel avec les autres participants.

**Problème connu sur iOS** : si vous utilisez ce préréglage sans connecter de microphone, le son passe par l’écouteur plutôt que par le haut-parleur de l’appareil. Utilisez ce préréglage uniquement en combinaison avec un microphone.


| Catégorie | Android | iOS | 
| --- | --- | --- | 
| Annulation de l’écho | Activé | Activé | 
| Variateur de volume | Volume d’appel | Volume d’appel | 
| Sélection du microphone | Limité en fonction du système d’exploitation. Les microphones USB peuvent ne pas être disponibles. | Limité en fonction du système d’exploitation. Les microphones USB et Bluetooth peuvent ne pas être disponibles. Les casques Bluetooth qui gèrent à la fois les entrées et les sorties devraient fonctionner, comme les AirPods. | 
| Sortie audio | Tout périphérique de sortie devrait fonctionner. | Limité en fonction du système d’exploitation. Les casques filaires peuvent ne pas être disponibles. | 
| Qualité audio | Moyenne/basse. Cela ressemblera à un appel téléphonique, et non à une lecture multimédia. | Moyenne/basse. Cela ressemblera à un appel téléphonique, et non à une lecture multimédia. | 

### S’abonner uniquement
<a name="audio-modes-presets-subscribe-only"></a>

Ce préréglage est conçu pour que vous puissiez prévoir de vous abonner à d’autres participants à la diffusion, mais que vous ne diffusez pas vous-même. Il privilégie la qualité audio et prend en charge tous les périphériques de sortie disponibles.


| Catégorie | Android | iOS | 
| --- | --- | --- | 
| Annulation de l’écho | Désactivé | Désactivé | 
| Variateur de volume | Volume multimédia | Volume multimédia | 
| Sélection du microphone | N/A, ce préréglage n’est pas conçu pour la diffusion. | N/A, ce préréglage n’est pas conçu pour la diffusion. | 
| Sortie audio | Tout périphérique de sortie devrait fonctionner. | Tout périphérique de sortie devrait fonctionner. | 
| Qualité audio | Élevée. Tout type de média doit apparaître clairement, y compris la musique. | Élevée. Tout type de média doit apparaître clairement, y compris la musique. | 

### Studio
<a name="audio-modes-presets-studio"></a>

Ce préréglage est conçu pour fournir un abonnement de haute qualité tout en conservant la possibilité de diffuser. Le matériel d’enregistrement et de lecture doit assurer l’annulation de l’écho. Ici, un cas d’utilisation serait de se servir d’un microphone USB et d’un casque filaire. Le SDK conservera un son de la plus haute qualité tout en s’appuyant sur la séparation physique de ces appareils pour éviter qu’ils ne génèrent de l’écho.


| Catégorie | Android | iOS | 
| --- | --- | --- | 
| Annulation de l’écho | L’annulation de l’écho de la plateforme est désactivée, mais l’annulation de l’écho du logiciel peut toujours se produire si `StageAudioConfiguration.enableEchoCancellation` a la valeur true. | Désactivé | 
| Variateur de volume | Volume multimédia dans la plupart des cas. Volume des appels lorsqu’un microphone Bluetooth est connecté.  | Volume multimédia | 
| Sélection du microphone | N’importe quel microphone devrait fonctionner. | N’importe quel microphone devrait fonctionner. | 
| Sortie audio | Tout périphérique de sortie devrait fonctionner. | Tout périphérique de sortie devrait fonctionner. | 
| Qualité audio | Élevée. Les deux parties doivent être en mesure d’envoyer de la musique et de l’entendre clairement de l’autre extrémité. Lorsqu’un casque Bluetooth est connecté, la qualité audio baisse en raison de l’activation du mode Bluetooth SCO. | Élevée. Les deux parties doivent être en mesure d’envoyer de la musique et de l’entendre clairement de l’autre extrémité. Lorsqu’un casque Bluetooth est connecté, la qualité audio peut baisser en raison de l’activation du mode Bluetooth SCO, en fonction du casque.  | 

## Cas d’utilisation avancés
<a name="broadcast-mobile-audio-modes-advanced-use-cases"></a>

Outre les préréglages, les SDK de diffusion en temps réel iOS et Android permettent de configurer les modes audio de la plateforme sous-jacente :
+ Sur Android, définissez les paramètres [AudioSource](https://developer.android.com/reference/android/media/MediaRecorder.AudioSource), [Usage](https://developer.android.com/reference/android/media/AudioAttributes#USAGE_ALARM) et [ContentType](https://developer.android.com/reference/android/media/AudioAttributes#CONTENT_TYPE_MOVIE).
+ Sur iOS, utilisez [AVAudioSession.Category](https://developer.apple.com/documentation/avfaudio/avaudiosession/category), [AVAudioSession.CategoryOptions](https://developer.apple.com/documentation/avfaudio/avaudiosession/categoryoptions), [AVAudioSession.mode](https://developer.apple.com/documentation/avfaudio/avaudiosession/mode) et la possibilité de choisir si le [traitement vocal](https://developer.apple.com/documentation/avfaudio/avaudioionode/3152101-voiceprocessingenabled?language=objc) est activé ou non lors de la diffusion.

Remarque : lors de l’utilisation de ces méthodes audio du kit SDK, il est possible de mal configurer la session audio sous-jacente. Par exemple, utiliser l’option `.allowBluetooth` sur iOS en combinaison avec la catégorie `.playback` crée une configuration audio invalide, empêchant le kit SDK d’enregistrer ou de lire de l’audio. Ces méthodes doivent être utilisées uniquement lorsqu’une application a des exigences spécifiques pour la session audio qui ont été validées.

------
#### [ Android (Kotlin) ]

```
// This would act similar to the Subscribe Only preset, but it uses a different ContentType.
StageAudioManager.getInstance(context)
    .setConfiguration(StageAudioManager.Source.GENERIC,
                      StageAudioManager.ContentType.MOVIE,
                      StageAudioManager.Usage.MEDIA);

val stage = Stage(context, token, this)

// Other Stage implementation code
```

------
#### [ iOS (Swift) ]

```
// This would act similar to the Subscribe Only preset, but it uses a different mode and options.
IVSStageAudioManager.sharedInstance()
    .setCategory(.playback,
                 options: [.duckOthers, .mixWithOthers],
                 mode: .default)

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

// Other Stage implementation code
```

------

### Annulation de l’écho sur iOS
<a name="advanced-use-cases-ios_echo_cancellation"></a>

L’annulation de l’écho sur iOS peut être contrôlée indépendamment via `IVSStageAudioManager` ainsi que par sa méthode `echoCancellationEnabled`. Cette méthode permet de contrôler si le [traitement vocal](https://developer.apple.com/documentation/avfaudio/avaudioionode/3152101-voiceprocessingenabled?language=objc) est activé sur les nœuds d’entrée et de sortie de `AVAudioEngine` utilisés par le kit SDK. Il est important de bien comprendre l’effet de la modification manuelle de cette propriété :
+ La propriété `AVAudioEngine` est prise en compte uniquement si le microphone du kit SDK est actif ; cela est nécessaire, car iOS exige que le traitement vocal soit activé à la fois sur les nœuds d’entrée et de sortie simultanément. En général, cela se fait en utilisant le microphone retourné par `IVSDeviceDiscovery` pour créer un `IVSLocalStageStream` à publier. Alternativement, le microphone peut être activé sans être utilisé pour publier, en lui associant un `IVSAudioDeviceStatsCallback`. Cette approche alternative est utile si une annulation d’écho est nécessaire lors de l’utilisation d’une source audio personnalisée basée sur un microphone, au lieu du microphone du kit SDK IVS.
+ L’activation de la propriété `AVAudioEngine` nécessite un mode `.videoChat` ou `.voiceChat`. Demander un mode différent perturbe le cadre audio sous-jacent d’iOS, ce qui peut entraîner une perte audio.
+ L’activation de `AVAudioEngine` active automatiquement l’option `.allowBluetooth `.

Les comportements peuvent varier selon l’appareil et la version d’iOS.

### Sources audio personnalisées pour iOS
<a name="advanced-use-cases-ios_custom_audio_sources"></a>

Des sources audio personnalisées peuvent être utilisées avec le kit SDK en utilisant `IVSDeviceDiscovery.createAudioSource`. Lors de la connexion à une scène, le kit SDK de diffusion en temps réel IVS gère toujours une instance `AVAudioEngine` interne pour la lecture audio, même si le microphone du kit SDK n’est pas utilisé. En conséquence, les valeurs fournies à `IVSStageAudioManager` doivent être compatibles avec l’audio provenant de la source audio personnalisée.

Si la source audio personnalisée utilisée pour diffuser enregistre à partir du microphone, mais est gérée par l’application hôte, l’annulation d’écho du kit SDK ci-dessus ne fonctionnera pas, sauf si le microphone géré par le kit SDK est activé. Pour contourner cette exigence, consultez la section [Annulation de l’écho sur iOS](#advanced-use-cases-ios_echo_cancellation).

### Diffusion via Bluetooth sur Android
<a name="advanced-use-cases-bluetooth-android"></a>

Sur Android, le SDK revient automatiquement au préréglage `VIDEO_CHAT` lorsque les conditions suivantes sont remplies :
+ La configuration attribuée n’utilise pas la valeur d’utilisation `VOICE_COMMUNICATION`.
+ Un microphone Bluetooth est connecté à l’appareil.
+ Le participant local diffuse sur une scène.

Il s’agit d’une limitation du système d’exploitation Android en ce qui concerne la manière dont les casques Bluetooth sont utilisés pour l’enregistrement audio.

## Intégration avec d’autres SDK
<a name="broadcast-mobile-audio-modes-integrating-other-sdks"></a>

Comme iOS et Android ne prennent en charge qu’un seul mode audio actif par application, des conflits peuvent survenir si votre application utilise plusieurs SDK qui nécessitent le contrôle du mode audio. Lorsque vous êtes confronté à de tels conflits, il existe des stratégies de résolution courantes à essayer ; elles sont expliquées ci-dessous.

### Faites correspondre les valeurs du mode audio
<a name="integrating-other-sdks-match-values"></a>

À l’aide des options de configuration audio avancées du SDK IVS ou des fonctionnalités de l’autre SDK, faites en sorte que les deux SDK s’alignent sur les valeurs sous-jacentes.

### Agora
<a name="integrating-other-sdks-agora"></a>

#### iOS
<a name="integrating-other-sdks-agora-ios"></a>

Sur iOS, le fait de demander au SDK Agora de garder la `AVAudioSession` active l’empêchera de se désactiver pendant que le SDK de diffusion en temps réel IVS l’utilise.

```
myRtcEngine.SetParameters("{\"che.audio.keep.audiosession\":true}");
```

#### Android
<a name="integrating-other-sdks-agora-android"></a>

Évitez d’appeler `setEnableSpeakerphone` sur `RtcEngine` ; appelez `enableLocalAudio(false)` pendant la diffusion grâce au SDK de diffusion en temps réel IVS. Vous pouvez appeler `enableLocalAudio(true)` à nouveau lorsque le SDK IVS ne diffuse pas.