

# SDK di trasmissione IVS I Streaming in tempo reale
<a name="broadcast"></a>

L'SDK di trasmissione dello streaming in tempo reale di Amazon Interactive Video Services (IVS) è rivolto agli sviluppatori che creano applicazioni con Amazon IVS. Questo SDK è progettato per trarre vantaggio dall'architettura di Amazon IVS e, così come Amazon IVS, vedrà l'introduzione di miglioramenti continui e nuove funzionalità. Essendo un SDK di trasmissione nativo, è progettato per ridurre al minimo l'impatto sulle prestazioni dell'applicazione e dei dispositivi utilizzati dagli utenti per accedere all'applicazione.

Tieni presente che l'SDK di trasmissione viene utilizzato sia per l'invio che per la ricezione di video, ovvero viene utilizzato lo stesso SDK sia per gli host che per gli spettatori. Non è necessario un SDK per lettori separati.

L'applicazione può avvalersi delle funzionalità principali dell'SDK di trasmissione Amazon IVS:
+ **Streaming di alta qualità** - L'SDK di trasmissione supporta lo streaming di alta qualità. Cattura video dalla tua fotocamera e codificali fino a 720p.
+ **Regolazioni automatiche del bitrate** - Gli utenti di smartphone sono mobili, quindi le loro condizioni di rete possono cambiare nel corso della trasmissione. L'SDK di trasmissione di Amazon IVS regola automaticamente il bitrate video per adattarsi alle mutevoli condizioni di rete.
+ **Supporto per l'orientamento verticale e orizzontale** - Indipendentemente dal modo in cui gli utenti tengono in mano i dispositivi, l'immagine viene visualizzata e ridimensionata correttamente. L'SDK di trasmissione supporta ogni dimensione del riquadro, sia in verticale che in orizzontale. Gestisce automaticamente le sue proporzioni quando gli utenti ruotano il dispositivo e cambiano l'orientamento configurato.
+ **Streaming sicuro** - Le trasmissioni dell'utente sono crittografate tramite TLS, in modo che possano mantenere protetti i propri flussi.
+ **Dispositivi audio esterni** - L'SDK di trasmissione Amazon IVS supporta collegamenti audio con cavo, USB e microfoni esterni Bluetooth SCO.

## Requisiti della piattaforma
<a name="broadcast-platform-requirements"></a>

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


| Piattaforma | Versioni supportate | 
| --- | --- | 
| Android |  9.0\$1: i clienti possono creare con la versione 6.0 o successiva ma non saranno in grado di utilizzare la funzionalità di streaming in tempo reale.  | 
| iOS |  14\$1  | 

IVS supporta un minimo di 4 versioni principali di iOS e 6 versioni principali di Android. Il nostro supporto per le versioni correnti potrebbe estendersi oltre questi minimi. I clienti verranno avvisati tramite note di rilascio dell'SDK con almeno 3 mesi di anticipo se una versione principale non è più supportata.

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


| Browser | Piattaforme supportate | Versioni supportate | 
| --- | --- | --- | 
| Chrome | Windows, macOS | Due versioni principali (versione corrente e precedente più recente) | 
| Firefox | Windows, macOS | Due versioni principali (versione corrente e precedente più recente) | 
| Edge | Windows 8.1\$1 | Due versioni principali (versione corrente e precedente più recente) Esclude Edge Legacy | 
| Safari | macOS | Due versioni principali (versione corrente e precedente più recente) | 

### Browser per dispositivi mobili (iOS e Android)
<a name="browser-mobile"></a>


| Browser | Piattaforme supportate | Versioni supportate | 
| --- | --- | --- | 
| Chrome | iOS, Android | Due versioni principali (versione corrente e precedente più recente) | 
| Firefox | Android | Due versioni principali (versione corrente e precedente più recente) | 
| Safari | iOS | Due versioni principali (versione corrente e precedente più recente) | 

#### Limiti noti
<a name="browser-mobile-limitations"></a>
+ Su tutti i browser Web dei dispositivi mobili, consigliamo di eseguire la pubblicazione/sottoscrizione con non più di tre publisher simultanei, a causa di vincoli di prestazioni che causano artefatti video e schermate nere. Se hai bisogno di più publisher, configura la [pubblicazione e la sottoscrizione solo audio](web-publish-subscribe.md#web-publish-subscribe-concepts-strategy-updates)
+ Non è consigliabile comporre una fase e trasmetterla su un canale su Android Mobile Web, a causa di considerazioni relative alle prestazioni e ai potenziali arresti anomali. Se è richiesta la funzionalità di trasmissione, integra l'[SDK di trasmissione per lo streaming in tempo reale IVS per Android](broadcast-android.md).

## Viste Web
<a name="broadcast-webviews"></a>

L'SDK di trasmissione Web non fornisce supporto per visualizzazioni Web o ambienti simili al Web (TV, console e così via). Per le implementazioni su dispositivi mobili, consulta la Guida all'SDK di trasmissione in streaming in tempo reale per [Android](broadcast-android.md) e [iOS](broadcast-ios.md).

## Richiesta di accesso al dispositivo
<a name="broadcast-device-access"></a>

L'SDK di trasmissione richiede l'accesso alle fotocamere e ai microfoni del dispositivo, sia quelli integrati nel dispositivo che quelli collegati tramite Bluetooth, USB o ingresso audio.

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

L'SDK di trasmissione viene continuamente migliorato. Consultare le [Note di rilascio di Amazon IVS](release-notes.md)per le versioni disponibili e i problemi risolti. Se necessario, prima di contattare il supporto, aggiornare la versione dell'SDK di trasmissione e verificare se il problema è stato risolto.

### Controllo delle versioni
<a name="broadcast-support-versioning"></a>

Gli SDK di trasmissione di Amazon IVS utilizzano il [controllo semantico delle versioni](https://semver.org/).

Per questa discussione, supponiamo che:
+ La versione più recente sia la 4.1.3.
+ L'ultima versione della versione principale precedente sia 3.2.4.
+ La versione più recente della versione 1.x sia la 1.5.6.

Le nuove funzionalità compatibili con le versioni precedenti vengono aggiunte come versioni secondarie dell'ultima versione. In questo caso, il set successivo di nuove funzionalità verrà aggiunto come versione 4.2.0.

Le correzioni di bug minori compatibili con le versioni precedenti vengono aggiunte come versioni di patch dell'ultima versione. Nel nostro caso, il set di correzioni minori di bug successivo sarà aggiunto come versione 4.1.4.

Le correzioni di bug principali compatibili con le versioni precedenti sono gestite in modo diverso, ovvero vengono aggiunte alle diverse versioni:
+ Rilascio della patch dell'ultima versione. Nel nostro caso, questa è la versione 4.1.4.
+ Rilascio della patch della versione secondaria precedente. Nel nostro caso, questa è la versione 3.2.5.
+ Rilascio di patch dell'ultima versione 1.x. Nel nostro caso, questa è la versione 1.5.7.

Le correzioni di bug principali sono definite dal team di prodotti Amazon IVS. Esempi tipici sono gli aggiornamenti critici della sicurezza e alcune altre correzioni necessarie per i clienti.

**Nota:** negli esempi precedenti, le versioni rilasciate vengono incrementate senza saltare alcun numero (ad esempio, da 4.1.3 a 4.1.4). In realtà, uno o più numeri di patch possono rimanere interni e non essere rilasciati, quindi la versione rilasciata potrebbe aumentare da 4.1.3 a, ad esempio, 4.1.6.

# SDK di trasmissione IVS: Guida per web I Streaming in tempo reale
<a name="broadcast-web"></a>

L'SDK di trasmissione Web in tempo reale di IVS offre agli sviluppatori gli strumenti per creare esperienze interattive e in tempo reale sul Web. Questo SDK è rivolto agli sviluppatori che creano applicazioni Web con Amazon IVS.

L'SDK per la trasmissione Web consente ai partecipanti di inviare e ricevere video. L'SDK  supporta le seguenti operazioni:
+ Partecipa a uno stage
+ Pubblica contenuti multimediali per gli altri partecipanti allo stage
+ Iscriviti ai contenuti multimediali degli altri partecipanti allo stage
+ Gestisci e monitora video e audio pubblicati sullo stage
+ Ottieni statistiche WebRTC per ogni connessione peer
+ Tutte le operazioni dell'SDK di trasmissione Web in streaming a bassa latenza di IVS

**Ultima versione dell'SDK di trasmissione Web:** 1.33.0 ([Note di rilascio](https://docs.aws.amazon.com/ivs/latest/RealTimeUserGuide/release-notes.html#mar12-26-broadcast-web-rt)) 

**Documentazione di riferimento:** per informazioni sui metodi più importanti disponibili nell'SDK di trasmissione Web di Amazon IVS, consulta la documentazione di riferimento all'indirizzo [https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference). Assicurati che sia selezionata la versione più recente dell'SDK.

**Codice di esempio**: gli esempi seguenti sono un buon punto di partenza per iniziare a utilizzare rapidamente l'SDK:
+ [Simple Playback](https://codepen.io/amazon-ivs/pen/RNwVBRK)
+ [Pubblicazione e sottoscrizione semplice](https://codepen.io/amazon-ivs/pen/ZEqgrpo)
+ [Demo completa sulla collaborazione React in tempo reale](https://github.com/aws-samples/amazon-ivs-real-time-collaboration-web-demo/tree/main)

**Requisiti della piattaforma:** consulta [SDK di trasmissione Amazon IVS](https://docs.aws.amazon.com//ivs/latest/RealTimeUserGuide/broadcast.html) per un elenco delle piattaforme supportate

**Nota:** la pubblicazione da un browser è comoda per gli utenti finali perché non richiede l'installazione di software aggiuntivo. Tuttavia, la pubblicazione basata su browser è soggetta ai vincoli e alla variabilità degli ambienti dei browser. Se è necessario dare priorità alla stabilità (ad esempio, per lo streaming di eventi), in genere consigliamo di pubblicare da una fonte diversa dal browser (ad esempio, OBS Studio o altri codificatori dedicati), che spesso hanno accesso diretto alle risorse di sistema ed evitano le limitazioni del browser. Per ulteriori informazioni sulle opzioni di pubblicazione diverse dal browser, consulta la documentazione di [Acquisizione dei flussi](rt-stream-ingest.md).

# Guida introduttiva a SDK di trasmissione Web IVS \$1 Streaming in tempo reale
<a name="broadcast-web-getting-started"></a>

Questo documento illustra i passaggi necessari per iniziare a utilizzare l'SDK di trasmissione IVS per lo streaming Web in tempo reale.

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

Gli elementi costitutivi per il tempo reale si trovano in un namespace diverso da quello dei moduli di trasmissione principali.

### Utilizzo di un tag di script
<a name="broadcast-web-getting-started-imports-script"></a>

L'SDK di trasmissione Web è distribuito come libreria JavaScript e può essere recuperato all'indirizzo [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).

Le classi e le enumerazioni definite negli esempi seguenti sono individuabili sull'oggetto globale `IVSBroadcastClient`:

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

### Utilizzo di npm
<a name="broadcast-web-getting-started-imports-npm"></a>

Per installare il pacchetto della `npm`: 

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

Le classi, le enumerazioni e i tipi possono essere importati anche dal modulo del pacchetto:

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

### Support per il rendering lato server
<a name="broadcast-web-getting-started-imports-server-side-rendering"></a>

La libreria delle fasi dell'SDK di trasmissione per web non può essere caricata in un contesto lato server, poiché fa riferimento alle primitive del browser necessarie per il funzionamento della libreria quando viene caricata. Per ovviare a questo problema, carica la libreria dinamicamente, come mostrato nella demo di [Trasmissione sul web utilizzando Next e React](https://github.com/aws-samples/amazon-ivs-broadcast-web-demo/blob/main/hooks/useBroadcastSDK.js#L26-L31).

## Richiedere autorizzazioni
<a name="broadcast-web-request-permissions"></a>

L'app deve richiedere l'autorizzazione per accedere alla fotocamera e al microfono dell'utente e tale autorizzazione deve utilizzare HTTPS. (Questo non riguarda solo Amazon IVS, ma qualsiasi sito Web che abbia bisogno di accedere alle fotocamere e ai microfoni.)

Ecco un esempio di funzione che mostra come richiedere e ottenere le autorizzazioni per dispositivi audio e video:

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

Per ulteriori informazioni, consulta l'[API delle autorizzazioni](https://developer.mozilla.org/en-US/docs/Web/API/Permissions_API) e [MediaDevices.getUserMedia()](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia).

## Elenco dei dispositivi disponibili
<a name="broadcast-web-request-list-devices"></a>

Per vedere quali dispositivi sono disponibili per l'acquisizione, interroga il metodo [MediaDevices.enumerateDevices()](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/enumerateDevices) del browser:

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

## Recupero di un MediaStream da un dispositivo
<a name="broadcast-web-retrieve-mediastream"></a>

Dopo aver acquisito l'elenco dei dispositivi disponibili, puoi recuperare un flusso da qualsiasi numero di dispositivi. Ad esempio, puoi utilizzare il metodo `getUserMedia()` per recuperare un flusso da una videocamera.

Se desideri specificare da quale dispositivo catturare lo streaming, puoi impostare esplicitamente il `deviceId` nella sezione `audio` o `video` dei vincoli del supporto. In alternativa, puoi omettere `deviceId` e fare in modo che gli utenti selezionino i propri dispositivi dal prompt del browser.

È inoltre possibile specificare una risoluzione ideale della fotocamera utilizzando i vincoli `width` e `height`. (Ulteriori informazioni su questi vincoli sono disponibili [qui](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints#properties_of_video_tracks).) L'SDK applica automaticamente i limiti di larghezza e altezza che corrispondono alla risoluzione massima di trasmissione; tuttavia, è una buona idea applicarli anche tu stesso in modo da essere certi che le proporzioni dell'aspetto della sorgente non vengano modificate dopo aver aggiunto la sorgente all'SDK.

Per lo streaming in tempo reale, assicurati che i contenuti multimediali siano limitati alla risoluzione di 720p. In particolare, i valori dei vincoli `getUserMedia` e `getDisplayMedia` per larghezza e altezza non devono superare 921600 (1280x720) se moltiplicati tra loro. 

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

# Pubblicazione e sottoscrizione con l'SDK di trasmissione Web IVS \$1 Streaming in tempo reale
<a name="web-publish-subscribe"></a>

Questo documento illustra i passaggi necessari per pubblicare e sottoscrivere una fase utilizzando l'SDK di trasmissione web IVS per lo streaming in tempo reale.

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

La funzionalità in tempo reale si basa su tre concetti fondamentali: [fase](#web-publish-subscribe-concepts-stage), [strategia](#web-publish-subscribe-concepts-strategy) ed [eventi](#web-publish-subscribe-concepts-events). L'obiettivo di progettazione è ridurre al minimo la quantità di logica lato client necessaria per creare un prodotto funzionante.

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

La classe `Stage` è il principale punto di interazione tra l'applicazione host e l'SDK. Rappresenta lo stage stesso e serve per entrare e uscire dallo stage. La creazione e la partecipazione a uno stage richiedono una stringa di token valida e non scaduta dal piano di controllo (control-plane) (rappresentata come `token`). Entrare e uscire da uno stage è semplice:

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

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

stage.leave();
```

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

L'interfaccia `StageStrategy` consente all'applicazione host di comunicare lo stato desiderato dello stage all'SDK. È necessario implementare tre funzioni: `shouldSubscribeToParticipant`, `shouldPublishParticipant` e `stageStreamsToPublish`. Sono tutte analizzate di seguito.

Per utilizzare una strategia definita, passala al costruttore `Stage`. Quello che segue è un esempio completo di applicazione che utilizza una strategia per pubblicare la webcam di un partecipante sullo stage ed eseguire la sottoscrizione a tutti i partecipanti. Lo scopo di ciascuna funzione strategica necessaria è spiegato in modo dettagliato nelle sezioni seguenti.

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

#### Sottoscrizione ai partecipanti
<a name="web-publish-subscribe-concepts-strategy-participants"></a>

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

Quando un partecipante remoto partecipa allo stage, l'SDK interroga l'applicazione host sullo stato della sottoscrizione desiderato per quel partecipante. Le opzioni sono `NONE`, `AUDIO_ONLY` e `AUDIO_VIDEO`. Quando si restituisce un valore per questa funzione, l'applicazione host non deve preoccuparsi dello stato di pubblicazione, dello stato della sottoscrizione corrente o dello stato della connessione allo stage. Se viene restituito `AUDIO_VIDEO`, l'SDK attende che il partecipante remoto effettui la pubblicazione prima della sottoscrizione e aggiorna l'applicazione host emettendo eventi durante tutto il processo.

Di seguito è riportata un'implementazione di esempio:

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

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

Questa è l'implementazione completa di questa funzione per un'applicazione host che vuole sempre che tutti i partecipanti si vedano tra loro, ad esempio un'applicazione di chat video.

Sono possibili anche implementazioni più avanzate. Ad esempio, supponiamo che l'applicazione fornisca un attributo `role` durante la creazione del token con CreateParticipantToken. L'applicazione può utilizzare la proprietà `attributes` su `StageParticipantInfo` per abbonarsi selettivamente ai partecipanti in base agli attributi forniti dal server:

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

Questa può essere usata per creare uno stage in cui i moderatori possano monitorare tutti gli ospiti senza essere visti o ascoltati. L'applicazione host potrebbe utilizzare una logica aziendale aggiuntiva per consentire ai moderatori di vedersi ma rimanendo invisibili agli ospiti.

#### Configurazione dell'abbonamento ai partecipanti
<a name="web-publish-subscribe-concepts-strategy-participants-config"></a>

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

Se un partecipante moto viene abbonato (consulta la sezione [Abbonamento ai partecipanti](#web-publish-subscribe-concepts-strategy-participants)), l'SDK interroga l'applicazione host su una configurazione di abbonamento personalizzata per tale partecipante. Questa configurazione è facoltativa e consente all'applicazione host di controllare determinati aspetti del comportamento dell'abbonato. Per informazioni sui valori che è possibile configurare, consulta la sezione [SubscribeConfiguration](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/interfaces/SubscribeConfiguration) nella documentazione di riferimento dell'SDK.

Di seguito è riportata un'implementazione di esempio:

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

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

Questa implementazione aggiorna il ritardo minimo del jitter-buffer per tutti i partecipanti abbonati a un valore predefinito di `MEDIUM`.

Come per `shouldSubscribeToParticipant`, sono possibili anche implementazioni più avanzate. Il valore `ParticipantInfo` dato può essere utilizzato per aggiornare selettivamente la configurazione di abbonamento per partecipanti specifici.

Consigliamo di utilizzare i valori predefiniti. Specifica la configurazione personalizzata solo se desideri modificare un comportamento particolare.

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

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

Una volta connesso allo stage, l'SDK interroga l'applicazione host per vedere se un dato partecipante deve eseguire una pubblicazione. Viene richiamata solo per i partecipanti locali che hanno il permesso di pubblicare in base al token fornito.

Di seguito è riportata un'implementazione di esempio:

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

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

Si tratta di un'applicazione di chat video standard in cui gli utenti vogliono sempre pubblicare. Possono disattivare e riattivare l'audio e il video per essere nascosti o visti/ascoltati immediatamente. Possono anche usare il comando di pubblicazione/annullamento della pubblicazione, ma è molto più lento. È preferibile disattivare/riattivare l'audio nei casi d'uso in cui è consigliabile modificare spesso la visibilità.

#### Scelta dei flussi da pubblicare
<a name="web-publish-subscribe-concepts-strategy-streams"></a>

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

Durante la pubblicazione, serve a determinare quali flussi audio e video devono essere pubblicati. Questo argomento verrà trattato dettagliatamente più avanti in [Pubblicazione di un flusso multimediale](#web-publish-subscribe-publish-stream).

#### Aggiornamento della strategia
<a name="web-publish-subscribe-concepts-strategy-updates"></a>

La strategia è pensata per essere dinamica: è possibile modificare i valori restituiti da una qualsiasi delle funzioni precedenti in qualsiasi momento. Ad esempio, se l'applicazione host non desidera pubblicare finché l'utente finale non tocca un pulsante, è possibile restituire una variabile da `shouldPublishParticipant` (del tipo `hasUserTappedPublishButton`). Quando quella variabile cambia in base a un'interazione da parte dell'utente finale, chiama `stage.refreshStrategy()` per segnalare all'SDK che dovrebbe eseguire una query sulla strategia per i valori più recenti, applicando solo quanto modificato. Se l'SDK rileva che il valore `shouldPublishParticipant` è cambiato, avvia il processo di pubblicazione. Se le query dell'SDK e tutte le funzioni restituiscono lo stesso valore di prima, la chiamata `refreshStrategy` non modifica lo stage.

Se il valore restituito di `shouldSubscribeToParticipant` cambia da `AUDIO_VIDEO` a `AUDIO_ONLY`, il flusso video viene rimosso per tutti i partecipanti con valori restituiti modificati, se in precedenza esisteva un flusso video.

In genere, lo stage utilizza la strategia per applicare in modo più efficiente la differenza tra le strategie precedenti e quelle attuali, senza che l'applicazione host debba preoccuparsi di tutto lo stato necessario per gestirla correttamente. Per questo motivo, considera la chiamata a `stage.refreshStrategy()` come un'operazione a basso costo, perché non viene eseguita a meno che la strategia non cambi.

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

Un'istanza `Stage` è un emettitore di eventi. Usando `stage.on()`, lo stato dello stage viene comunicato all'applicazione host. Gli aggiornamenti all'interfaccia utente dell'applicazione host in genere possono essere supportati interamente dagli eventi. Gli eventi sono i seguenti:

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

Per la maggior parte di questi metodi, viene fornito il `ParticipantInfo` corrispondente.

Non è previsto che le informazioni fornite dagli eventi influiscano sui valori restituiti della strategia. Ad esempio, non è previsto che il valore restituito di `shouldSubscribeToParticipant` cambi quando viene chiamato `STAGE_PARTICIPANT_PUBLISH_STATE_CHANGED`. Se l'applicazione host desidera effettuare la sottoscrizione a un particolare partecipante, deve restituire il tipo di abbonamento desiderato indipendentemente dallo stato di pubblicazione di quel partecipante. L'SDK è responsabile di garantire che lo stato desiderato della strategia venga applicato al momento giusto in base allo stato dello stage.

## Pubblicazione di un flusso multimediale
<a name="web-publish-subscribe-publish-stream"></a>

I dispositivi locali come microfoni e fotocamere vengono recuperati utilizzando gli stessi passaggi descritti sopra in [Recupero di un MediaStream da un dispositivo](broadcast-web-getting-started.md#broadcast-web-retrieve-mediastream). Nell'esempio utilizziamo `MediaStream` per creare un elenco di oggetti `LocalStageStream` utilizzati per la pubblicazione dall'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
    }
}
```

## Pubblicazione di una condivisione dello schermo
<a name="web-publish-subscribe-publish-screenshare"></a>

Spesso le applicazioni devono pubblicare una condivisione dello schermo in aggiunta alla webcam dell'utente. La pubblicazione di una condivisione dello schermo richiede la creazione di un token aggiuntivo per la fase, in particolare per la pubblicazione dei contenuti multimediali della condivisione dello schermo. Utilizza `getDisplayMedia` e limita la risoluzione a un massimo di 720p. Dopodiché, i passaggi sono simili a quelli per la pubblicazione di una videocamera sulla fase.

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

## Visualizzazione e rimozione dei partecipanti
<a name="web-publish-subscribe-participants"></a>

Una volta completata la sottoscrizione, riceverai una serie di oggetti `StageStream` tramite l'evento `STAGE_PARTICIPANT_STREAMS_ADDED`. L'evento fornisce anche informazioni sui partecipanti per aiutarti a visualizzare i flussi multimediali:

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

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

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

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

Quando un partecipante interrompe la pubblicazione o annulla l'iscrizione a un flusso, la funzione `STAGE_PARTICIPANT_STREAMS_REMOVED` viene chiamata con i flussi che sono stati rimossi. Le applicazioni host devono utilizzarlo come segnale per rimuovere il flusso video del partecipante dal DOM.

`STAGE_PARTICIPANT_STREAMS_REMOVED` viene richiamato per tutti gli scenari in cui un flusso potrebbe essere rimosso, tra cui:
+ Il partecipante remoto interrompe la pubblicazione.
+ Un dispositivo locale annulla l'iscrizione o modifica l'abbonamento da `AUDIO_VIDEO` a `AUDIO_ONLY`.
+ Il partecipante remoto lascia lo stage.
+ Il partecipante locale lascia lo stage.

Poiché `STAGE_PARTICIPANT_STREAMS_REMOVED` viene richiamato per tutti gli scenari, non è richiesta alcuna logica aziendale personalizzata per la rimozione dei partecipanti dall'interfaccia utente durante le operazioni di abbandono remote o locali.

## Disattivazione e riattivazione dell'audio dei flussi multimediali
<a name="web-publish-subscribe-mute-streams"></a>

Gli oggetti `LocalStageStream` hanno una funzione `setMuted` che controlla se l'audio del flusso è disattivato. Questa funzione può essere richiamata sul flusso prima o dopo la restituzione dalla funzione della strategia `stageStreamsToPublish`.

**Importante**: se una nuova istanza di oggetto `LocalStageStream` viene restituita da `stageStreamsToPublish` dopo una chiamata a `refreshStrategy`, lo stato di silenziamento del nuovo oggetto di flusso viene applicato allo stage. Fai attenzione quando crei nuove istanze `LocalStageStream` per assicurarti che lo stato di silenziamento previsto venga mantenuto.

## Monitoraggio dello stato di silenziamento dei contenuti multimediali dei partecipanti remoti
<a name="web-publish-subscribe-mute-state"></a>

Quando i partecipanti modificano lo stato di silenziamento del video o dell'audio, l'evento `STAGE_STREAM_MUTE_CHANGED` viene attivato con un elenco di flussi che sono stati modificati. Usa la proprietà `isMuted` su `StageStream` per aggiornare l'interfaccia utente di conseguenza:

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

Inoltre, puoi consultare [StageParticipantInfo](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference#stageparticipantinfo) per informazioni sullo stato dell'eventuale disattivazione dell'audio o del video:

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

## Ottenimento delle statistiche WebRTC
<a name="web-publish-subscribe-webrtc-stats"></a>

Il metodo `requestQualityStats()` fornisce l'accesso a statistiche WebRTC dettagliate per gli stream locali e remoti. È disponibile su entrambi gli oggetti LocalStageStream e RemoteStageStream. Restituisce parametri di qualità completi, tra cui la qualità della rete, le statistiche sui pacchetti, le informazioni sul bitrate e i parametri relativi ai frame.

Si tratta di un metodo asincrono con il quale è possibile recuperare le statistiche tramite attesa o concatenando una promessa. Restituisce `undefined` quando le statistiche non sono disponibili, ad esempio, quando lo stream non è attivo o le statistiche interne non sono disponibili. Se le statistiche sono disponibili e a seconda dello stream (remoto o locale, video o audio), il metodo restituisce un oggetto [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) o [RemoteAudioStats](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/interfaces/RemoteAudioStats).

Notare che per i flussi video con simulcast, l'array contiene più oggetti stat (uno per livello).

**Best practice**
+ Frequenza di polling: chiamare `requestQualityStats()` a intervalli ragionevoli (1-5 secondi) per evitare un impatto sulle prestazioni
+ Gestione degli errori: controllare sempre se il valore restituito è `undefined` prima dell'elaborazione
+ Gestione della memoria: cancellare intervalli/timeout quando gli stream non sono più necessari
+ Qualità della rete: utilizzare `networkQuality` per il feedback degli utenti sui possibili danni causati dalla rete. Per i dettagli, consulta [NetworkQuality](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/enumerations/NetworkQuality).

**Esempio di utilizzo**

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

## Ottimizzazione dei contenuti multimediali
<a name="web-publish-subscribe-optimizing-media"></a>

Si consiglia di limitare le chiamate `getUserMedia` e `getDisplayMedia` secondo i seguenti vincoli per ottenere prestazioni ottimali:

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

È possibile limitare ulteriormente i contenuti multimediali tramite opzioni aggiuntive passate al costruttore `LocalStageStream`:

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

Nel codice qui sopra:
+ `minBitrate` imposta un bitrate minimo che dovrebbe essere utilizzato dal browser. Tuttavia, un flusso video a bassa complessità può spingere il codificatore a scendere al di sotto di questo bitrate.
+ `maxBitrate` imposta un bitrate massimo che il browser non dovrebbe superare per questo flusso.
+ `maxFramerate` imposta una frequenza di rate massima che il browser non dovrebbe superare per questo flusso.
+ L'opzione `simulcast` può essere utilizzata solo sui browser basati su Chromium. Consente l'invio di tre livelli di rendering del flusso.
  + Ciò consente al server di scegliere quale rendering inviare agli altri partecipanti in base alle loro limitazioni di rete.
  + Quando `simulcast` viene specificato insieme a un valore di `maxBitrate` e/o `maxFramerate`, si prevede che il livello di rendering più alto venga configurato tenendo conto di questi valori, a condizione che `maxBitrate` non scenda al di sotto del valore predefinito interno `maxBitrate` del secondo livello più alto dell'SDK di 900 kbps.
  + Se viene specificato un valore troppo basso di `maxBitrate` rispetto al valore predefinito del secondo livello più alto, `simulcast` sarà disabilitato.
  + `simulcast` non può essere attivato e disattivato senza ripubblicare il file multimediale tramite una sequenza in cui `shouldPublishParticipant` restituisce `false`, richiama `refreshStrategy`, `shouldPublishParticipant` restituisce `true` e richiama di nuovo `refreshStrategy`.

## Ottieni gli attributi dei partecipanti
<a name="web-publish-subscribe-participant-attributes"></a>

Se specifichi gli attributi nella richiesta dell'operazione `CreateParticipantToken`, puoi visualizzare gli attributi nelle proprietà `StageParticipantInfo`:

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

## Supplemental Enhancement Information (SEI)
<a name="web-publish-subscribe-sei-attributes"></a>

L'unità NAL Supplemental Enhancement Information (SEI) viene utilizzata per archiviare i metadati allineati al fotogramma insieme al video. È utilizzabile per la pubblicazione e l'abbonamento a flussi video H.264. Non è garantito che i payload SEI arrivino agli abbonati, specialmente in condizioni di rete non ottimali. Poiché il payload SEI memorizza i dati direttamente all'interno della struttura del frame H.264, questa funzionalità non può essere utilizzata per flussi di solo audio.

### Inserimento di payload SEI
<a name="sei-attributes-inserting-sei-payloads"></a>

I client di pubblicazione possono inserire payload SEI in un flusso di fase in corso di pubblicazione configurando il LocalStageStream del proprio video in modo da abilitare `inBandMessaging` e, successivamente, richiamando il metodo `insertSeiMessage`. L'abilitazione di `inBandMessaging` aumenta l'utilizzo della memoria SDK.

I payload devono essere del tipo [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer). La dimensione del payload deve essere maggiore di 0 KB e inferiore a 1 KB. Il numero di messaggi SEI inseriti al secondo non deve superare i 10 KB al secondo.

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

#### Ripetizione dei payload SEI
<a name="sei-attributes-repeating-sei-payloads"></a>

È possibile fornire un `repeatCount` per ripetere l'inserimento dei payload SEI per i N frame successivi inviati, il che potrebbe essere utile per mitigare la perdita intrinseca che può verificarsi a causa del protocollo di trasporto UDP sottostante utilizzato per inviare video. Il valore deve essere compreso tra 0 e 30. I client di ricezione devono disporre di una logica per deduplicare il messaggio.

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

### Leggere i payload SEI
<a name="sei-attributes-reading-sei-payloads"></a>

I clienti abbonati possono leggere i payload SEI di un publisher che pubblica video H.264, se presente, configurando l'elemento `SubscribeConfiguration` degli abbonati per attivare `inBandMessaging` e ascoltare l'evento `StageEvents.STAGE_STREAM_SEI_MESSAGE_RECEIVED`, come illustrato nell'esempio seguente:

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

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

## Codifica a livelli con Simulcast
<a name="web-publish-subscribe-layered-encoding-simulcast"></a>

La codifica a livelli con simulcast è una funzionalità di streaming in tempo reale IVS che consente ai publisher di inviare più livelli di qualità video differenti e agli abbonati di modificare dinamicamente o manualmente tali livelli. La funzionalità è descritta più approfonditamente nel documento [Ottimizzazioni dello streaming](https://docs.aws.amazon.com//ivs/latest/RealTimeUserGuide/real-time-streaming-optimization.html).

### Configurazione della codifica a livelli (Publisher)
<a name="web-layered-encoding-simulcast-configure-publisher"></a>

Per abilitare la codifica a più livelli con simulcast, il publisher deve aggiungere la seguente configurazione a `LocalStageStream` all'istanziazione:

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

A seconda della risoluzione di input del dispositivo videocamera, verrà codificato e inviato un determinato numero di livelli come definito nella sezione [Livelli, qualità e framerate predefiniti](real-time-streaming-optimization.md#real-time-streaming-optimization-default-layers) di *Ottimizzazioni dello streaming*.

Inoltre, puoi facoltativamente configurare singoli livelli dall'interno della configurazione simulcast:

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

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

In alternativa, puoi creare configurazioni di livelli personalizzate, fino a un massimo di tre livelli. Se viene fornita una matrice vuota o non viene specificato alcun valore, vengono utilizzate le impostazioni predefinite sopra descritte. I livelli sono descritti con le seguenti proprietà obbligatorie:
+ `height: number;`
+ `width: number;`
+ `maxBitrateKbps: number;`
+ `maxFramerate: number;`

A partire dai preset, è possibile sovrascrivere le singole proprietà o creare una configurazione completamente nuova:

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

Per i valori massimi, i limiti e gli errori che possono essere attivati durante la configurazione di singoli livelli, consulta la documentazione di riferimento dell'SDK.

### Configurazione della codifica a livelli (Abbonato)
<a name="web-layered-encoding-simulcast-configure-subscriber"></a>

L'abbonato non deve eseguire alcuna operazione per abilitare la codifica a livelli. Se un publisher invia layer simulcast, per impostazione predefinita il server si adatta dinamicamente tra i livelli per scegliere la qualità ottimale in base al dispositivo e alle condizioni di rete dell'abbonato.

In alternativa, per scegliere layer espliciti inviati dal publisher, sono disponibili diverse opzioni, descritte di seguito.

### Opzione 1: preferenza di qualità del livello iniziale
<a name="web-layered-encoding-simulcast-layer-quality-preference"></a>

Usando la strategia `subscribeConfiguration`, è possibile scegliere quale livello iniziale si desidera ricevere come abbonato:

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

Per impostazione predefinita, agli abbonati viene sempre inviato per primo il livello di qualità più bassa; questo livello passa lentamente al livello di qualità più alta. Ciò ottimizza il consumo di larghezza di banda da parte dell'utente finale e offre il tempo ottimale per i video, riducendo i blocchi iniziali del video per gli utenti su reti più deboli.

Queste opzioni sono disponibili per `InitialLayerPreference`:
+ `LOWEST_QUALITY` — Il server fornisce prima il livello video con la qualità più bassa. In questo modo, si ottimizza il consumo di larghezza di banda e il tempo di accesso ai contenuti multimediali. La qualità è definita come combinazione di dimensioni, bitrate e framerate del video. Ad esempio, un video 720p ha una qualità inferiore rispetto a un video 1080p.
+ `HIGHEST_QUALITY` — Il server offre prima il livello video con la qualità più alta. Ciò ottimizza la qualità ma può aumentare il tempo di visualizzazione dei contenuti multimediali. La qualità è definita come combinazione di dimensioni, bitrate e framerate del video. Ad esempio, un video 1080p è di qualità superiore rispetto a un video 720p.

**Nota**: per rendere effettive le preferenze iniziali del livello (la chiamata `initialLayerPreference`), è necessario effettuare un nuovo abbonamento poiché questi aggiornamenti non si applicano all'abbonamento attivo.



### Opzione 2: livello preferito per lo streaming
<a name="web-layered-encoding-simulcast-preferred-layer"></a>

Una volta avviato un flusso, puoi utilizzare il metodo strategico `preferredLayerForStream `. Questo metodo strategico espone il partecipante e le informazioni sul flusso.

Il metodo strategico può essere restituito con quanto segue:
+ L'oggetto del livello direttamente in base a ciò che `RemoteStageStream.getLayers` restituisce 
+ La stringa dell'etichetta dell'oggetto del livello, basata su `StageStreamLayer.label`
+ Non definito o nullo, indicante che non deve essere selezionato alcun livello e che è preferibile l'adattamento dinamico

Ad esempio, la strategia seguente prevede che gli utenti selezionino sempre il livello di video con la qualità più bassa disponibile:

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

Per reimpostare la selezione del livello e tornare all'adattamento dinamico, restituire null o non definito nella strategia. In questo esempio `appState` è una variabile fittizia che rappresenta il possibile stato dell'applicazione.

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

### Opzione 3: helper per livelli RemoteStageStream
<a name="web-layered-encoding-simulcast-remotestagestream-helpers"></a>

`RemoteStageStream` dispone di diversi helper che possono essere usati per prendere decisioni sulla selezione dei livelli e visualizzare le selezioni corrispondenti agli utenti finali:
+ **Eventi dei livelli**: oltre a `StageEvents`, l'oggetto `RemoteStageStream` include eventi che comunicano modifiche di adattamento di livelli e simulcast:
  + `stream.on(RemoteStageStreamEvents.ADAPTION_CHANGED, (isAdapting) => {})`
  + `stream.on(RemoteStageStreamEvents.LAYERS_CHANGED, (layers) => {})`
  + `stream.on(RemoteStageStreamEvents.LAYER_SELECTED, (layer, reason) => {})`
+ **Metodi dei livelli**: `RemoteStageStream` include diversi metodi helper che possono essere usati per ottenere informazioni sul flusso e sui livelli presentati. Questi metodi sono disponibili sul flusso remoto fornito nella strategia `preferredLayerForStream `, nonché sui flussi remoti esposti tramite `StageEvents.STAGE_PARTICIPANT_STREAMS_ADDED`.
  + `stream.getLayers`
  + `stream.getSelectedLayer`
  + `stream.getLowestQualityLayer`
  + `stream.getHighestQualityLayer`

Per i dettagli, consulta la classe `RemoteStageStream` nella [Documentazione di riferimento dell'SDK](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference). Per il motivo `LAYER_SELECTED`, se viene restituito `UNAVAILABLE`, allora il livello richiesto non può essere selezionato. Per mantenere la stabilità del flusso, al suo posto viene effettuata la migliore selezione, che in genere è un livello di qualità inferiore.

## Gestione dei problemi di rete
<a name="web-publish-subscribe-network-issues"></a>

Quando si perde la connessione di rete del dispositivo locale, l'SDK tenta internamente di riconnettersi senza alcuna azione da parte dell'utente. In alcuni casi, l'SDK non funziona ed è necessaria un'azione da parte dell'utente.

In generale, lo stato dello stage può essere gestito tramite l'evento `STAGE_CONNECTION_STATE_CHANGED`:

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

In generale, è possibile ignorare uno stato di errore che si verifica dopo essersi aggiunti correttamente in una fase, poiché l'SDK tenterà di ripristinarlo internamente. Se l'SDK riporta uno stato `ERRORED` e la fase rimane nello stato `CONNECTING` per un periodo di tempo prolungato (ad esempio, 30 secondi o più), probabilmente è avvenuta la disconnessione dalla rete.

## Trasmissione della fase a un canale IVS
<a name="web-publish-subscribe-broadcast-stage"></a>

Per trasmettere uno stage, creare una sessione `IVSBroadcastClient` separata e segui le consuete istruzioni per la trasmissione con l'SDK descritte sopra. L'elenco dei `StageStream` esposti tramite `STAGE_PARTICIPANT_STREAMS_ADDED` può essere utilizzato per recuperare i flussi multimediali dei partecipanti che possono essere applicati alla composizione del flusso di trasmissione, come segue:

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

Facoltativamente, puoi comporre una fase e trasmetterla a un canale IVS a bassa latenza in modo da raggiungere un pubblico più vasto. Consulta [Abilitazione di più host su un flusso Amazon IVS](https://docs.aws.amazon.com//ivs/latest/LowLatencyUserGuide/multiple-hosts.html) nella Guida per l'utente dello streaming a bassa latenza di IVS.

# Problemi noti e soluzioni alternative per l'SDK di trasmissione Web IVS \$1 Streaming in tempo reale
<a name="broadcast-web-known-issues"></a>

Questo documento elenca i problemi noti che potresti riscontrare durante l'utilizzo dello Streaming in tempo reale di Amazon IVS per la trasmissione Web e suggerisce possibili soluzioni alternative.
+ Quando si chiudono le schede o si esce dal browser senza chiamare `stage.leave()`, gli utenti possono comunque apparire nella sessione con un frame bloccato o una schermata nera per un massimo di 10 secondi.

  **Soluzione alternativa:** nessuna.
+ Le sessioni di Safari vengono visualizzate in modo intermittente con una schermata nera agli utenti che si iscrivono dopo l'inizio di una sessione.

  **Soluzione alternativa:** aggiorna il browser e ricollega la sessione.
+ Quando si passa da una rete all'altra, il ripristino di Safari non avviene correttamente.

  **Soluzione alternativa:** aggiorna il browser e ricollega la sessione.
+ La console per sviluppatori ripete un errore `Error: UnintentionalError at StageSocket.onClose`.

  **Soluzione alternativa:** è possibile creare un solo stage per token di partecipazione. Questo errore si verifica quando viene creata più di un'istanza `Stage`con lo stesso token di partecipazione, indipendentemente dal fatto che l'istanza si trovi su uno o più dispositivi.
+ Potresti avere problemi a mantenere uno stato `StageParticipantPublishState.PUBLISHED` e potresti ricevere stati `StageParticipantPublishState.ATTEMPTING_PUBLISH` ripetuti durante l'ascolto dell'evento `StageEvents.STAGE_PARTICIPANT_PUBLISH_STATE_CHANGED`.

  **Soluzione alternativa:** limita la risoluzione video a 720p quando invochi `getUserMedia` o `getDisplayMedia`. In particolare, i valori dei vincoli `getUserMedia` e `getDisplayMedia` per larghezza e altezza non devono superare 921600 (1280x720) se moltiplicati tra loro.
+ Quando `stage.leave()` viene richiamato o un partecipante remoto esce, viene visualizzato un errore 404 DELETE nella console di debug del browser.

  **Soluzione alternativa:** nessuna. Si tratta di un errore innocuo.

## Limiti di Safari
<a name="broadcast-web-safari-limitations"></a>
+ Per negare un prompt di autorizzazione è necessario reimpostare l'autorizzazione nelle impostazioni del sito Web di Safari a livello di sistema operativo.
+ Safari non rileva nativamente tutti i dispositivi con la stessa efficacia di Firefox o Chrome. Ad esempio, OBS Virtual Camera non viene rilevata.

## Limitazioni di Firefox
<a name="broadcast-web-firefox-limitations"></a>
+ Per consentire a Firefox di condividere lo schermo devono essere abilitate le autorizzazioni di sistema. Dopo averle abilitate, perché funzioni correttamente Firefox deve essere riavviato altrimenti, se le autorizzazioni vengono percepite come bloccate, il browser genererà un'eccezione [NotFoundError](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia#exceptions).
+ Manca il metodo `getCapabilities`. Ciò significa che gli utenti non possono ottenere la risoluzione o le proporzioni della traccia multimediale. Consulta questo [thread di bugzilla](https://bugzilla.mozilla.org/show_bug.cgi?id=1179084).
+ Mancano diverse proprietà `AudioContext`, ad esempio latenza e numero di canali. Ciò potrebbe rappresentare un problema per gli utenti esperti che desiderano manipolare le tracce audio.
+ I feed della fotocamera da `getUserMedia` su MacOS sono limitati a un rapporto di aspetto 4:3. Consulta il [thread 1 di bugzilla](https://bugzilla.mozilla.org/show_bug.cgi?id=1193640) e [thread 2 di bugzilla](https://bugzilla.mozilla.org/show_bug.cgi?id=1306034).
+ L'acquisizione audio non è supportata con `getDisplayMedia`. Consulta questo [thread di bugzilla](https://bugzilla.mozilla.org/show_bug.cgi?id=1541425).
+ La frequenza di fotogrammi nell'acquisizione dello schermo non è ottimale (circa 15 fps?). Consulta questo [thread di bugzilla](https://bugzilla.mozilla.org/show_bug.cgi?id=1703522).

## Limitazioni Web per dispositivi mobili
<a name="broadcast-web-mobile-web-limitations"></a>
+ La condivisione dello schermo [getDisplayMedia](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia#browser_compatibility) non è supportata sui dispositivi mobili.

  **Soluzione alternativa:** nessuna.
+ Il partecipante impiega 15-30 secondi per uscire quando chiude un browser senza chiamare `leave()`.

  **Soluzione alternativa**: aggiungi un'interfaccia utente che incoraggi gli utenti a disconnettersi correttamente.
+ L'app in backgrounding interrompe la pubblicazione del video.

  **Soluzione alternativa**: visualizza uno stato dell'interfaccia utente quando il publisher è in pausa.
+ La frequenza dei fotogrammi video diminuisce per circa 5 secondi dopo aver riattivato l'audio di una fotocamera su dispositivi Android.

  **Soluzione alternativa:** nessuna.
+ Il feed video viene allungato in fase di rotazione per iOS 16.0.

  **Soluzione alternativa**: visualizza un'interfaccia utente che descrive questo problema noto del sistema operativo.
+ La commutazione del dispositivo di ingresso audio commuta automaticamente il dispositivo di uscita audio.

  **Soluzione alternativa:** nessuna.
+ Lo sfondo del browser fa sì che il flusso di pubblicazione diventi nero e produca solo audio.

  **Soluzione alternativa:** nessuna. Ciò è dovuto a motivi di sicurezza.

# Gestione degli errori nell'SDK di trasmissione Web IVS \$1 Streaming in tempo reale
<a name="broadcast-web-error-handling"></a>

Questa sezione fornisce una panoramica delle condizioni di errore, del modo in cui l'SDK di trasmissione per web le segnala all'applicazione e di cosa dovrebbe fare un'applicazione quando si verificano tali errori. Gli errori vengono segnalati dall'SDK ai listener dell'evento `StageEvents.ERROR`:

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

## Errori di fase
<a name="web-error-handling-stage-errors"></a>

Se l'SDK rileva un problema da cui non può essere ripristinato viene segnalato uno StageError, la cui risoluzione in genere richiede l'intervento dell'app e/o la riconnessione di rete.

Ogni `StageError` segnalato ha un codice (o `StageErrorCode`), un messaggio (stringa) e una categoria (`StageErrorCategory`). Ciascuno è correlato a una categoria operativa sottostante.

La categoria di operazione dell'errore viene determinata in base al fatto che sia correlata alla connessione alla fase (`JOIN_ERROR`), all'invio di file multimediali alla fase (`PUBLISH_ERROR`) o alla ricezione di un flusso multimediale in entrata dalla fase (`SUBSCRIBE_ERROR`).

La proprietà codice di uno `StageError` riporta il problema specifico:


| Name | Codice | Operazione consigliata | 
| --- | --- | --- | 
| TOKEN\$1MALFORMED | 1 | Crea un token valido e prova a riavviare l'istanza della fase. | 
| TOKEN\$1EXPIRED | 2 | Crea un token non scaduto e prova a riavviare l'istanza della fase. | 
| TIMEOUT | 3 | Timeout dell'operazione. Se la fase esiste e il token è valido, l'errore è probabilmente un problema di rete. In tal caso, attendi il ripristino della connettività del dispositivo. | 
| NON RIUSCITO | 4 | Durante il tentativo di intervento si è verificata una condizione fatale. Verifica i dettagli dell'errore. Se la fase esiste e il token è valido, l'errore è probabilmente un problema di rete. In tal caso, attendi il ripristino della connettività del dispositivo. Per la maggior parte degli errori relativi alla stabilità della rete, l'SDK riproverà internamente per un periodo massimo di 30 secondi prima di emettere un errore FAILED.  | 
| CANCELED (ANNULLATO) | 5 | Controlla il codice dell'applicazione e assicurati che non vi siano invocazioni `join`, `refreshStrategy` o `replaceStrategy` ripetute, che potrebbero causare l'avvio e l'annullamento di operazioni ripetute prima del completamento. | 
| STAGE\$1AT\$1CAPACITY | 6 | Questo errore indica che lo stage o l'account sono al massimo della capacità. Se lo stage ha raggiunto il limite di partecipanti, prova a eseguire nuovamente l'operazione quando lo stage non è più a piena capacità aggiornando la strategia. Se il tuo account ha raggiunto la quota di abbonamenti o publisher simultanei, riduci l'utilizzo o richiedi un aumento della quota tramite la [console AWS Service Quotas.](https://console.aws.amazon.com/servicequotas/)  | 
| CODEC\$1MISMATCH | 7 | Il codec non è supportato dalla fase. Controlla il supporto del codec per il browser e la piattaforma. Per lo streaming in tempo reale IVS, i browser devono supportare il codec H.264 per il video e il codec Opus per l'audio. | 
| TOKEN\$1NOT\$1ALLOWED | 8 | Il token non dispone dell'autorizzazione per l'operazione. Ricrea il token con le autorizzazioni corrette e riprova. | 
| STAGE\$1DELETED | 9 | Nessuno; il tentativo di entrare in uno stage eliminato attiva questo errore. | 
| PARTICIPANT\$1DISCONNESSO | 10 | Nessuno; il tentativo di partecipare con il token di un partecipante disconnesso attiva questo errore. | 

### Esempio di gestione di StageError
<a name="web-error-handling-stage-errors-example"></a>

Utilizza il codice StageError per determinare se l'errore è dovuto a un token scaduto:

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

### Errori di rete quando è già stato effettuato l'accesso
<a name="web-error-handling-stage-errors-network"></a>

Se la connessione di rete del dispositivo si interrompe, l'SDK potrebbe perdere la connessione ai server della fase. È possibile che vengano visualizzati errori nella console perché l'SDK non è più in grado di raggiungere i servizi di backend. I POST su https://broadcast.stats.live-video.net falliranno.

Se stai pubblicando e/o ti stai iscrivendo, vedrai errori nella console relativi ai tentativi di pubblicazione/sottoscrizione.

Internamente, l'SDK proverà a riconnettersi con una strategia di backoff esponenziale.

**Azione**: attendi il ripristino della connettività del dispositivo.

## Stati con errore
<a name="web-error-handling-errored-states"></a>

Ti consigliamo di utilizzare questi stati per la registrazione delle applicazioni e per mostrare agli utenti dei messaggi che li avvisano in merito ai problemi di connettività alla fase per un determinato partecipante.

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

L'SDK segnala `ERRORED` quando una pubblicazione non va a buon fine.

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

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

L'SDK segnala `ERRORED` quando una sottoscrizione fallisce. Ciò può verificarsi a causa delle condizioni della rete o se uno stage ha raggiunto la capacità massima di abbonati.

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

# SDK di trasmissione IVS: Guida per Android I Streaming in tempo reale
<a name="broadcast-android"></a>

L'SDK di trasmissione per lo streaming in tempo reale IVS per Android consente ai partecipanti di inviare e ricevere video su Android.

Il pacchetto `com.amazonaws.ivs.broadcast` implementa l'interfaccia descritta in questo documento. L'SDK supporta le seguenti operazioni:
+ Partecipa a uno stage 
+ Pubblica contenuti multimediali per gli altri partecipanti allo stage
+ Iscriviti ai contenuti multimediali degli altri partecipanti allo stage
+ Gestisci e monitora video e audio pubblicati sullo stage
+ Ottieni statistiche WebRTC per ogni connessione peer
+ Tutte le operazioni dell'SDK di trasmissione per lo streaming a bassa latenza di IVS per Android

**Ultima versione dell'SDK di trasmissione Android:** 1.40.0 ([Note di rilascio](https://docs.aws.amazon.com/ivs/latest/RealTimeUserGuide/release-notes.html#mar12-26-broadcast-android-rt)) 

**Documentazione di riferimento:** per informazioni sui metodi più importanti disponibili nell'SDK di trasmissione di Amazon IVS per Android, consulta la documentazione di riferimento all'indirizzo [https://aws.github.io/amazon-ivs-'-docs/1.40.0/android/](https://aws.github.io/amazon-ivs-broadcast-docs/1.40.0/android/).

**Codice di esempio:** vedere il repository di esempio Android su 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).

**Requisiti della piattaforma:** Android 9.0\$1

# Guida introduttiva all'SDK di trasmissione IVS su Android \$1 Streaming in tempo reale
<a name="broadcast-android-getting-started"></a>

Questo documento illustra i passaggi necessari per iniziare a utilizzare l'SDK di trasmissione IVS per lo streaming in tempo reale su Android.

## Installare la libreria
<a name="broadcast-android-install"></a>

Esistono diversi modi per aggiungere la libreria di trasmissione Amazon IVS per Android al proprio ambiente di sviluppo Android: è possibile utilizzare direttamente Gradle o i cataloghi delle versioni Gradle oppure installare l'SDK manualmente.

**Utilizzo diretto di Gradle**: aggiungi la libreria al file `build.gradle` del modulo, come mostrato qui (per l'ultima versione dell'SDK di trasmissione IVS):

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

**Utilizzo dei cataloghi delle versioni Gradle**: per prima cosa, includi quanto segue nel file `build.gradle` del modulo:

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

Quindi includi quanto segue nel file `libs.version.toml` (per l'ultima versione dell'SDK di trasmissione IVS):

```
[versions]
ivs="1.40.0"

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

**Installazione manuale dell'SDK**: scarica la versione più recente da questo percorso:

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

Assicurati di scaricare `aar` con `-stages` aggiunto.

**Consenti all'SDK di controllare anche il vivavoce**: a prescindere dal metodo di installazione scelto, aggiungi la seguente autorizzazione al tuo manifesto per consentire all'SDK di abilitare e disabilitare il vivavoce:

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

## Utilizzo dell'SDK con i simboli di debug
<a name="broadcast-android-using-debug-symbols-rt"></a>

Pubblichiamo anche una versione dell'SDK di trasmissione per Android che include i simboli di debug. È possibile utilizzare questa versione per migliorare la qualità dei report di debug (tracce dello stack) in Firebase Crashlytics se si verificano arresti anomali nell'SDK di trasmissione IVS, ad esempio `libbroadcastcore.so`. Quando segnali questi arresti anomali al team dell'SDK di IVS, le tracce dello stack di qualità superiore facilitano la risoluzione dei problemi.

Per utilizzare questa versione dell'SDK, inserisci quanto segue nei tuoi file di build di Gradle:

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

Utilizza la riga precedente invece di questa:

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

### Caricamento dei simboli in Firebase Crashlytics
<a name="android-debug-symbols-rt-firebase-crashlytics"></a>

Assicurati che i tuoi file di build Gradle siano configurati per Firebase Crashlytics. Segui le istruzioni di Google qui:

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

Assicurati di includere `com.google.firebase:firebase-crashlytics-ndk` come dipendenza.

Quando crei l'app per il rilascio, il plug-in Firebase Crashlytics dovrebbe caricare i simboli automaticamente. Per caricare i simboli manualmente, esegui uno dei comandi seguenti:

```
gradle uploadCrashlyticsSymbolFileRelease
```

```
./gradlew uploadCrashlyticsSymbolFileRelease
```

Non è un problema se i simboli vengono caricati due volte, automaticamente e manualmente.

### Impedire che .apk Release diventi più grande
<a name="android-debug-symbols-rt-sizing-apk"></a>

Prima di impacchettare il file `.apk` di rilascio, il plug-in Android Gradle tenta automaticamente di rimuovere le informazioni di debug dalle librerie condivise (inclusa la libreria `libbroadcastcore.so` dell'SDK di trasmissione IVS). Tuttavia, a volte ciò non accade. Di conseguenza, il file `.apk` potrebbe diventare più grande e si potrebbe ricevere un messaggio di avviso dal plug-in Android Gradle che indica che non è in grado di rimuovere i simboli di debug e sta impacchettando i file `.so` così come sono. In tal caso, segui questa procedura:
+ Installa un NDK per Android. Va bene qualsiasi versione recente.
+ Aggiungi `ndkVersion <your_installed_ndk_version_number>` al file `build.gradle` dell'applicazione. Fallo anche se l'applicazione non contiene codice nativo.

Per ulteriori informazioni, consulta questo [report sul problema](https://issuetracker.google.com/issues/353554169).

## Richiedere autorizzazioni
<a name="broadcast-android-permissions"></a>

L'app deve richiedere l'autorizzazione per accedere alla fotocamera e al microfono dell'utente. (Questo non riguarda solo Amazon IVS, ma qualsiasi applicazione che abbia bisogno di accedere alle fotocamere e ai microfoni.)

Qui, controlliamo se l'utente ha già concesso le autorizzazioni e, in caso contrario, le chiediamo:

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

Qui, otteniamo la risposta dell'utente:

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

# Pubblicazione e sottoscrizione con l'SDK di trasmissione IVS su Android \$1 Streaming in tempo reale
<a name="android-publish-subscribe"></a>

Questo documento illustra i passaggi necessari per pubblicare e sottoscrivere una fase utilizzando l'SDK di trasmissione IVS per lo streaming in tempo reale su Android.

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

La funzionalità in tempo reale si basa su tre concetti fondamentali: [fase](#android-publish-subscribe-concepts-stage), [strategia](#android-publish-subscribe-concepts-strategy) e [renderer](#android-publish-subscribe-concepts-renderer). L'obiettivo di progettazione è ridurre al minimo la quantità di logica lato client necessaria per creare un prodotto funzionante.

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

La classe `Stage` è il principale punto di interazione tra l'applicazione host e l'SDK. Rappresenta lo stage stesso e serve per entrare e uscire dallo stage. La creazione e la partecipazione a uno stage richiedono una stringa di token valida e non scaduta dal piano di controllo (control-plane) (rappresentata come `token`). Entrare e uscire da uno stage è semplice. 

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

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

stage.leave();
```

La classe `Stage` è anche il luogo in cui può essere collegato lo `StageRenderer`:

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

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

L'interfaccia `Stage.Strategy` consente all'applicazione host di comunicare lo stato desiderato dello stage all'SDK. È necessario implementare tre funzioni: `shouldSubscribeToParticipant`, `shouldPublishFromParticipant` e `stageStreamsToPublishForParticipant`. Sono tutte analizzate di seguito.

#### Sottoscrizione ai partecipanti
<a name="android-publish-subscribe-concepts-strategy-participants"></a>

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

Quando un partecipante remoto partecipa allo stage, l'SDK interroga l'applicazione host sullo stato della sottoscrizione desiderato per quel partecipante. Le opzioni sono `NONE`, `AUDIO_ONLY` e `AUDIO_VIDEO`. Quando si restituisce un valore per questa funzione, l'applicazione host non deve preoccuparsi dello stato di pubblicazione, dello stato della sottoscrizione corrente o dello stato della connessione allo stage. Se viene restituito `AUDIO_VIDEO`, l'SDK attende che il partecipante remoto effettui la pubblicazione prima della sottoscrizione e aggiorna l'applicazione host tramite il renderer durante tutto il processo.

Di seguito è riportata un'implementazione di esempio:

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

Questa è l'implementazione completa di questa funzione per un'applicazione host che vuole sempre che tutti i partecipanti si vedano tra loro, ad esempio un'applicazione di chat video.

Sono possibili anche implementazioni più avanzate. Utilizza la proprietà `userInfo` su `ParticipantInfo` per iscriverti selettivamente ai partecipanti in base agli attributi forniti dal server:

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

Questa può essere usata per creare uno stage in cui i moderatori possano monitorare tutti gli ospiti senza essere visti o ascoltati. L'applicazione host potrebbe utilizzare una logica aziendale aggiuntiva per consentire ai moderati di vedersi, ma rimanendo invisibili agli ospiti.

#### Configurazione dell'abbonamento ai partecipanti
<a name="android-publish-subscribe-concepts-strategy-participants-config"></a>

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

Se un partecipante moto viene abbonato (consulta la sezione [Abbonamento ai partecipanti](#android-publish-subscribe-concepts-strategy-participants)), l'SDK interroga l'applicazione host su una configurazione di abbonamento personalizzata per tale partecipante. Questa configurazione è facoltativa e consente all'applicazione host di controllare determinati aspetti del comportamento dell'abbonato. Per informazioni sui valori che è possibile configurare, consulta la sezione [SubscribeConfiguration](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/interfaces/SubscribeConfiguration) nella documentazione di riferimento dell'SDK.

Di seguito è riportata un'implementazione di esempio:

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

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

    return config;
}
```

Questa implementazione aggiorna il ritardo minimo del jitter-buffer per tutti i partecipanti abbonati a un valore predefinito di `MEDIUM`.

Come per `shouldSubscribeToParticipant`, sono possibili anche implementazioni più avanzate. Il valore `ParticipantInfo` dato può essere utilizzato per aggiornare selettivamente la configurazione di abbonamento per partecipanti specifici.

Consigliamo di utilizzare i valori predefiniti. Specifica la configurazione personalizzata solo se desideri modificare un comportamento particolare.

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

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

Una volta connesso allo stage, l'SDK interroga l'applicazione host per vedere se un dato partecipante deve eseguire una pubblicazione. Viene richiamata solo per i partecipanti locali che hanno il permesso di pubblicare in base al token fornito.

Di seguito è riportata un'implementazione di esempio:

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

Si tratta di un'applicazione di chat video standard in cui gli utenti vogliono sempre pubblicare. Possono disattivare e riattivare l'audio e il video per essere nascosti o visti/ascoltati immediatamente. Possono anche usare il comando di pubblicazione/annullamento della pubblicazione, ma è molto più lento. È preferibile disattivare/riattivare l'audio nei casi d'uso in cui è consigliabile modificare spesso la visibilità.

#### Scelta dei flussi da pubblicare
<a name="android-publish-subscribe-concepts-strategy-streams"></a>

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

Durante la pubblicazione, serve a determinare quali flussi audio e video devono essere pubblicati. Questo argomento verrà trattato dettagliatamente più avanti in [Pubblicazione di un flusso multimediale](#android-publish-subscribe-publish-stream).

#### Aggiornamento della strategia
<a name="android-publish-subscribe-concepts-strategy-updates"></a>

La strategia è pensata per essere dinamica: è possibile modificare i valori restituiti da una qualsiasi delle funzioni precedenti in qualsiasi momento. Ad esempio, se l'applicazione host non desidera pubblicare finché l'utente finale non tocca un pulsante, è possibile restituire una variabile da `shouldPublishFromParticipant` (del tipo `hasUserTappedPublishButton`). Quando quella variabile cambia in base a un'interazione da parte dell'utente finale, chiama `stage.refreshStrategy()` per segnalare all'SDK che dovrebbe eseguire una query sulla strategia per i valori più recenti, applicando solo quanto modificato. Se l'SDK rileva che il valore `shouldPublishFromParticipant` è cambiato, avvierà il processo di pubblicazione. Se le query dell'SDK e tutte le funzioni restituiscono lo stesso valore di prima, la chiamata `refreshStrategy` non apporterà alcuna modifica allo stage.

Se il valore restituito di `shouldSubscribeToParticipant` cambia da `AUDIO_VIDEO` a `AUDIO_ONLY`, il flusso video verrà rimosso per tutti i partecipanti con valori restituiti modificati, se in precedenza esisteva un flusso video.

In genere, lo stage utilizza la strategia per applicare in modo più efficiente la differenza tra le strategie precedenti e quelle attuali, senza che l'applicazione host debba preoccuparsi di tutto lo stato necessario per gestirla correttamente. Per questo motivo, considera la chiamata a `stage.refreshStrategy()` come un'operazione a basso costo, perché non viene eseguita a meno che la strategia non cambi.

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

L'interfaccia `StageRenderer` comunica lo stato desiderato dello stage all'applicazione host. Gli aggiornamenti all'interfaccia utente dell'applicazione host in genere possono essere alimentati interamente dagli eventi forniti dal renderer. Il renderer fornisce le funzioni seguenti:

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

Per la maggior parte di questi metodi, vengono forniti `Stage` e `ParticipantInfo` corrispondenti.

Non è previsto che le informazioni fornite dal renderer influiscano sui valori restituiti della strategia. Ad esempio, non è previsto che il valore restituito di `shouldSubscribeToParticipant` cambi quando viene chiamato `onParticipantPublishStateChanged`. Se l'applicazione host desidera effettuare la sottoscrizione a un particolare partecipante, deve restituire il tipo di abbonamento desiderato indipendentemente dallo stato di pubblicazione di quel partecipante. L'SDK è responsabile di garantire che lo stato desiderato della strategia venga applicato al momento giusto in base allo stato dello stage.

Lo `StageRenderer` può essere collegato alla classe dello stage:

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

Ricorda che solo i partecipanti alla pubblicazione attivano `onParticipantJoined` e ogni volta che un partecipante interrompe la pubblicazione o abbandona la sessione dello stage viene attivato `onParticipantLeft`.

## Pubblicazione di un flusso multimediale
<a name="android-publish-subscribe-publish-stream"></a>

I dispositivi locali, come microfoni e fotocamere integrati, vengono rilevati tramite `DeviceDiscovery`. Ecco un esempio di selezione della fotocamera frontale e del microfono predefinito, che vengono poi restituiti come `LocalStageStreams` per la pubblicazione dall'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;
}
```

## Visualizzazione e rimozione dei partecipanti
<a name="android-publish-subscribe-participants"></a>

Una volta completata la sottoscrizione, riceverai una serie di oggetti `StageStream` tramite la funzione `onStreamsAdded` del renderer. Puoi recuperare l'anteprima da 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);
```

Puoi recuperare le statistiche del livello audio da un `AudioStageStream`:

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

Quando un partecipante interrompe la pubblicazione o annulla l'iscrizione, la funzione `onStreamsRemoved` viene chiamata con i flussi che sono stati rimossi. Le applicazioni host devono utilizzarlo come segnale per rimuovere il flusso video del partecipante dalla gerarchia delle visualizzazioni.

`onStreamsRemoved` viene richiamato per tutti gli scenari in cui un flusso potrebbe essere rimosso, tra cui: 
+ Il partecipante remoto interrompe la pubblicazione.
+ Un dispositivo locale annulla l'iscrizione o modifica l'abbonamento da `AUDIO_VIDEO` a `AUDIO_ONLY`.
+ Il partecipante remoto lascia lo stage.
+ Il partecipante locale lascia lo stage.

Poiché `onStreamsRemoved` viene richiamato per tutti gli scenari, non è richiesta alcuna logica aziendale personalizzata per la rimozione dei partecipanti dall'interfaccia utente durante le operazioni di abbandono remote o locali.

## Disattivazione e riattivazione dell'audio dei flussi multimediali
<a name="android-publish-subscribe-mute-streams"></a>

Gli oggetti `LocalStageStream` hanno una funzione `setMuted` che controlla se l'audio del flusso è disattivato. Questa funzione può essere richiamata sul flusso prima o dopo la restituzione dalla funzione della strategia `streamsToPublishForParticipant`.

**Importante**: se una nuova istanza di oggetto `LocalStageStream` viene restituita da `streamsToPublishForParticipant` dopo una chiamata a `refreshStrategy`, lo stato di silenziamento del nuovo oggetto di flusso viene applicato allo stage. Fai attenzione quando crei nuove istanze `LocalStageStream` per assicurarti che lo stato di silenziamento previsto venga mantenuto.

## Monitoraggio dello stato di silenziamento dei contenuti multimediali dei partecipanti remoti
<a name="android-publish-subscribe-mute-state"></a>

Quando un partecipante modifica lo stato di silenziamento del proprio flusso video o audio, la funzione `onStreamMutedChanged` del renderer viene richiamata con un elenco di flussi che sono stati modificati. Usa il metodo `getMuted` su `StageStream` per aggiornare l'interfaccia utente di conseguenza. 

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

## Ottenimento delle statistiche WebRTC
<a name="android-publish-subscribe-webrtc-stats"></a>

Per ottenere le statistiche WebRTC più recenti per un flusso di pubblicazione o un flusso di iscrizione, usa `requestRTCStats` su `StageStream`. Quando una raccolta è completata, riceverai statistiche tramite il `StageStream.Listener` che può essere impostato su `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());
		}
	}
}
```

## Ottieni gli attributi dei partecipanti
<a name="android-publish-subscribe-participant-attributes"></a>

Se specifichi gli attributi nella richiesta dell'operazione `CreateParticipantToken`, puoi visualizzare gli attributi nelle proprietà `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());
	}
}
```

## Incorpora messaggi
<a name="android-publish-subscribe-embed-messages"></a>

Il metodo `embedMessage` su ImageDevice consente di inserire payload di metadati direttamente nei frame video durante la pubblicazione. Ciò consente la messaggistica sincronizzata con i frame per applicazioni in tempo reale. L’embedding dei messaggi è disponibile solo quando si utilizza l'SDK per la pubblicazione in tempo reale (non per la pubblicazione a bassa latenza).

Non è garantito che i messaggi incorporati arrivino agli abbonati perché sono incorporati direttamente nei frame video e trasmessi tramite UDP, il che non garantisce la consegna dei pacchetti. La perdita di pacchetti durante la trasmissione può causare la perdita di messaggi, soprattutto in condizioni di rete precarie. Per mitigare questo problema, il metodo `embedMessage` include un parametro `repeatCount` che duplica il messaggio su più frame consecutivi, aumentando l'affidabilità della consegna. Questa funzione è disponibile solo per i flussi video.

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

I client di pubblicazione possono incorporare i payload dei messaggi nel loro flusso video utilizzando il metodo `embedMessage` su ImageDevice. La dimensione del payload deve essere maggiore di 0 KB e inferiore a 1 KB. Il numero di messaggi incorporati inseriti al secondo non deve superare i 10 KB al secondo. 

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

### Ripetizione dei payload del messaggio
<a name="android-embed-messages-repeat-payloads"></a>

Utilizza `repeatCount` per duplicare il messaggio su più frame per una maggiore affidabilità. Il valore deve essere compreso tra 0 e 30. I client di ricezione devono disporre di una logica per deduplicare il messaggio.

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

### Lettura dei messaggi incorporati
<a name="android-embed-messages-read-messages"></a>

Vedi «Ottieni informazioni supplementari sul miglioramento (SEI)» di seguito per sapere come leggere i messaggi incorporati dai flussi in entrata.

## Ottenimento di dati Supplemental Enhancement Information (SEI)
<a name="android-publish-subscribe-sei-attributes"></a>

L'unità NAL Supplemental Enhancement Information (SEI) viene utilizzata per archiviare i metadati allineati al fotogramma insieme al video. I clienti abbonati possono leggere i payload SEI di un publisher che pubblica video H.264 ispezionando la proprietà `embeddedMessages` sugli oggetti `ImageDeviceFrame` che provengono da `ImageDevice` del publisher. A tal fine, acquisire `ImageDevice` di un publisher, quindi osservare ogni frame tramite un callback fornito a `setOnFrameCallback`, come mostrato nell'esempio seguente:

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

## Continuazione della sessione in background
<a name="android-publish-subscribe-background-session"></a>

Quando l'app entra in background, potresti voler interrompere la pubblicazione o iscriverti solo all'audio degli altri partecipanti remoti. A tale scopo, aggiorna l'implementazione `Strategy` per interrompere la pubblicazione e iscriviti a `AUDIO_ONLY` (o `NONE`, se applicabile).

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

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

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

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

## Codifica a livelli con Simulcast
<a name="android-publish-subscribe-layered-encoding-simulcast"></a>

La codifica a livelli con simulcast è una funzionalità di streaming in tempo reale IVS che consente ai publisher di inviare più livelli di qualità video differenti e agli abbonati di configurare dinamicamente o manualmente tali livelli. La funzionalità è descritta più approfonditamente nel documento [Ottimizzazioni dello streaming](real-time-streaming-optimization.md).

### Configurazione della codifica a livelli (Publisher)
<a name="android-layered-encoding-simulcast-configure-publisher"></a>

Per abilitare la codifica a livelli con simulcast, il publisher deve aggiungere la seguente configurazione a `LocalStageStream` all’istanziazione:

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

ImageLocalStageStream cameraStream = new ImageLocalStageStream(frontCamera, config);

// Other Stage implementation code
```

A seconda della risoluzione impostata nella configurazione video, un determinato numero di livelli verrà codificato e inviato come definito nella sezione [Livelli, qualità e framerate predefiniti](real-time-streaming-optimization.md#real-time-streaming-optimization-default-layers) di *Ottimizzazioni dello streaming*.

Inoltre, puoi facoltativamente configurare singoli livelli dall'interno della configurazione simulcast: 

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

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

config.simulcast.setLayers(simulcastLayers);

ImageLocalStageStream cameraStream = new ImageLocalStageStream(frontCamera, config);

// Other Stage implementation code
```

In alternativa, puoi creare configurazioni di livelli personalizzate, fino a un massimo di tre livelli. Se viene fornita una matrice vuota o non viene specificato alcun valore, vengono utilizzate le impostazioni predefinite sopra descritte. I livelli sono descritti con i seguenti setter di proprietà obbligatori:
+ `setSize: Vec2;`
+ `setMaxBitrate: integer;`
+ `setMinBitrate: integer;`
+ `setTargetFramerate: integer;`

A partire dai preset, è possibile sovrascrivere le singole proprietà o creare una configurazione completamente nuova:

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

Per i valori massimi, i limiti e gli errori che possono essere attivati durante la configurazione di singoli livelli, consulta la documentazione di riferimento dell'SDK.

### Configurazione della codifica a livelli (Abbonato)
<a name="android-layered-encoding-simulcast-configure-subscriber"></a>

L'abbonato non deve eseguire alcuna operazione per abilitare la codifica a livelli. Se un publisher invia layer simulcast, per impostazione predefinita il server si adatta dinamicamente tra i livelli per scegliere la qualità ottimale in base al dispositivo e alle condizioni di rete dell'abbonato.

In alternativa, per scegliere layer espliciti inviati dal publisher, sono disponibili diverse opzioni, descritte di seguito.

### Opzione 1: preferenza di qualità del livello iniziale
<a name="android-layered-encoding-simulcast-layer-quality-preference"></a>

Usando la strategia `subscribeConfigurationForParticipant`, è possibile scegliere quale livello iniziale si desidera ricevere come abbonato:

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

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

    return config;
}
```

Per impostazione predefinita, agli abbonati viene sempre inviato per primo il livello di qualità più bassa; questo livello passa lentamente al livello di qualità più alta. Ciò ottimizza il consumo di larghezza di banda da parte dell'utente finale e offre il tempo ottimale per i video, riducendo i blocchi iniziali del video per gli utenti su reti più deboli.

Queste opzioni sono disponibili per `InitialLayerPreference`:
+ `LOWEST_QUALITY` — Il server fornisce prima il livello video con la qualità più bassa. In questo modo, si ottimizza il consumo di larghezza di banda e il tempo di accesso ai contenuti multimediali. La qualità è definita come combinazione di dimensioni, bitrate e framerate del video. Ad esempio, un video 720p ha una qualità inferiore rispetto a un video 1080p.
+ `HIGHEST_QUALITY` — Il server offre prima il livello video con la qualità più alta. Ciò ottimizza la qualità ma può aumentare il tempo di visualizzazione dei contenuti multimediali. La qualità è definita come combinazione di dimensioni, bitrate e framerate del video. Ad esempio, un video 1080p è di qualità superiore rispetto a un video 720p.

**Nota**: per rendere effettive le preferenze iniziali del livello (la chiamata `setInitialLayerPreference`), è necessario effettuare un nuovo abbonamento poiché questi aggiornamenti non si applicano all'abbonamento attivo.

### Opzione 2: livello preferito per lo streaming
<a name="android-layered-encoding-simulcast-preferred-layer"></a>

Il metodo strategico `preferredLayerForStream` consente di selezionare un livello dopo l'inizio del flusso. Questo metodo di strategia riceve le informazioni sul partecipante e sul flusso, permettendoti di selezionare un livello singolarmente per ciascun partecipante. L’SDK chiama questo metodo in risposta a eventi specifici, come quando cambiano i livelli del flusso, lo stato del partecipante o quando l’applicazione host aggiorna la strategia.

Il metodo di strategia restituisce un oggetto `RemoteStageStream.Layer`, che può essere uno dei seguenti:
+ Un oggetto di livello, ad esempio uno restituito da `RemoteStageStream.getLayers`.
+ null, indicante che non deve essere selezionato alcun livello e che è preferibile l'adattamento dinamico.

Ad esempio, la strategia seguente prevede che gli utenti selezionino sempre il livello di video con la qualità più bassa disponibile:

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

Per reimpostare la selezione del livello e tornare all'adattamento dinamico, restituire null o non definito nella strategia. In questo esempio, `appState` è una variabile segnaposto che rappresenta lo stato dell’applicazione host.

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

### Opzione 3: helper per livelli RemoteStageStream
<a name="android-layered-encoding-simulcast-remotestagestream-helpers"></a>

`RemoteStageStream` dispone di diversi helper che possono essere usati per prendere decisioni sulla selezione dei livelli e visualizzare le selezioni corrispondenti agli utenti finali:
+ **Eventi dei livelli**: oltre a `StageRenderer`, `RemoteStageStream.Listener` include eventi che comunicano modifiche di adattamento di livelli e simulcast:
  + `void onAdaptionChanged(boolean adaption)`
  + `void onLayersChanged(@NonNull List<Layer> layers)`
  + `void onLayerSelected(@Nullable Layer layer, @NonNull LayerSelectedReason reason)`
+ **Metodi dei livelli**: `RemoteStageStream` include diversi metodi helper che possono essere usati per ottenere informazioni sul flusso e sui livelli presentati. Questi metodi sono disponibili sul flusso remoto fornito nella strategia `preferredLayerForStream`, nonché sui flussi remoti esposti tramite `StageRenderer.onStreamsAdded`.
  + `stream.getLayers`
  + `stream.getSelectedLayer`
  + `stream.getLowestQualityLayer`
  + `stream.getHighestQualityLayer`
  + `stream.getLayersWithConstraints`

Per i dettagli, consulta la classe `RemoteStageStream` nella [Documentazione di riferimento dell'SDK](https://aws.github.io/amazon-ivs-broadcast-docs/latest/android/). Per il motivo `LayerSelected`, se viene restituito `UNAVAILABLE`, allora il livello richiesto non può essere selezionato. Per mantenere la stabilità del flusso, al suo posto viene effettuata la migliore selezione, che in genere è un livello di qualità inferiore.

## Limitazioni alla configurazione video
<a name="android-publish-subscribe-video-limits"></a>

L'SDK non supporta l'uso forzato della modalità verticale o della modalità orizzontale `StageVideoConfiguration.setSize(BroadcastConfiguration.Vec2 size)`. Nell'orientamento verticale, la dimensione più piccola viene utilizzata come larghezza; nell'orientamento orizzontale, l'altezza. Ciò significa che le due chiamate seguenti a `setSize` avranno lo stesso effetto sulla configurazione video:

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

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

## Gestione dei problemi di rete
<a name="android-publish-subscribe-network-issues"></a>

Quando si perde la connessione di rete del dispositivo locale, l'SDK tenta internamente di riconnettersi senza alcuna azione da parte dell'utente. In alcuni casi, l'SDK non funziona ed è necessaria un'azione da parte dell'utente. Esistono due errori principali relativi alla perdita della connessione di rete:
+ Codice di errore 1400, messaggio: "PeerConnection is lost due to unknown network error" (Connessione peer interrotta a causa di un errore di rete sconosciuto)
+ Codice di errore 1300, messaggio: "Retry attempts are exhausted" (Nuovi tentativi esauriti)

Se viene ricevuto il primo errore ma il secondo no, l'SDK è ancora connesso allo stage e tenterà di ristabilire automaticamente le connessioni. Come misura di sicurezza, puoi chiamare `refreshStrategy` senza modificare i valori restituiti dal metodo di strategia, per attivare un tentativo di riconnessione manuale.

Se viene ricevuto il secondo errore, i tentativi di riconnessione dell'SDK sono falliti e il dispositivo locale non è più connesso allo stage. In questo caso, prova a rientrare nello stage chiamando `join` dopo aver ristabilito la connessione di rete.

In generale, il verificarsi di errori dopo l'accesso corretto a uno stage indica che l'SDK non è riuscito a ristabilire una connessione. Crea un nuovo oggetto `Stage` e prova ad accedere quando le condizioni della rete migliorano.

## Uso dei microfoni Bluetooth
<a name="android-publish-subscribe-bluetooth-microphones"></a>

Per pubblicare utilizzando dispositivi microfonici Bluetooth, è necessario avviare una connessione Bluetooth SCO:

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

# Problemi noti e soluzioni alternative per l'SDK di trasmissione IVS su Android \$1 Streaming in tempo reale
<a name="broadcast-android-known-issues"></a>

Questo documento elenca i problemi noti che potresti riscontrare quando utilizzi lo Streaming in tempo reale di Amazon IVS per la trasmissione su Android e suggerisce possibili soluzioni alternative.
+ Quando un dispositivo Android entra in modalità sospensione e si riattiva, è possibile che l'anteprima sia bloccata.

  **Soluzione alternativa:** crea e usa una nuova `Stage`.
+ Quando un partecipante accede con un token utilizzato da un altro partecipante, la prima connessione viene disconnessa senza un errore specifico.

  **Soluzione alternativa:** nessuna. 
+ Può verificarsi un problema raro per cui il publisher sta pubblicando, ma lo stato di pubblicazione che gli abbonati ricevono è `inactive`.

  **Soluzione alternativa:** prova a uscire e poi a partecipare alla sessione. Se il problema persiste, crea un nuovo token per il publisher.
+ Durante una sessione di stage può verificarsi un raro problema di distorsione audio a intermittenza, in genere durante le chiamate di lunga durata.

  **Soluzione alternativa:** il partecipante con audio distorto può uscire e accedere nuovamente alla sessione oppure annullare la pubblicazione e ripubblicare l'audio per risolvere il problema.
+ I microfoni esterni non sono supportati durante la pubblicazione su uno stage.

  **Soluzione alternativa:** non utilizzare un microfono esterno collegato tramite USB per la pubblicazione su uno stage.
+ La pubblicazione su uno stage con condivisione dello schermo usando `createSystemCaptureSources` non è supportata.

  **Soluzione alternativa:** gestisci l'acquisizione del sistema manualmente, utilizzando sorgenti di input di immagini personalizzate e sorgenti di input audio personalizzate.
+ Quando una `ImagePreviewView` viene rimossa da un elemento padre (ad esempio, `removeView()` viene chiamato dall'elemento padre), `ImagePreviewView` viene rilasciata immediatamente. `ImagePreviewView` non mostra alcun frame quando viene aggiunta a un'altra vista principale.

  **Soluzione alternativa:** richiedi un'altra anteprima utilizzando `getPreview`.
+ Quando partecipi a uno stage con un Samsung Galaxy S22/\$1 con Android 12, potresti riscontrare un errore 1401 e il dispositivo locale non riesce ad accedere allo stage o accede ma senza audio.

  **Soluzione alternativa:** esegui l'aggiornamento ad Android 13.
+ Quando accedi a uno stage con un Nokia X20 su Android 13, la fotocamera potrebbe non aprirsi e viene generata un'eccezione.

  **Soluzione alternativa:** nessuna.
+ I dispositivi con il chipset MediaTek Helio potrebbero non renderizzare correttamente i video dei partecipanti remoti.

  **Soluzione alternativa:** nessuna.
+ Su alcuni dispositivi, il sistema operativo del dispositivo può scegliere un microfono diverso da quello selezionato tramite l'SDK. Questo perché l'SDK di trasmissione Amazon IVS non può controllare come viene definito il percorso audio `VOICE_COMMUNICATION`, poiché varia in base ai diversi produttori di dispositivi.

  **Soluzione alternativa:** nessuna.
+ Alcuni codificatori video Android non possono essere configurati con dimensioni video inferiori a 176x176. La configurazione di una dimensione inferiore causa un errore e impedisce lo streaming.

  **Soluzione alternativa:** configura la dimensione del video in modo che non sia inferiore a 176x176.

# Gestione degli errori nell'SDK di trasmissione IVS su Android \$1 Streaming in tempo reale
<a name="broadcast-android-error-handling"></a>

Questa sezione fornisce una panoramica delle condizioni di errore, del modo in cui l'SDK di trasmissione IVS per lo streaming in tempo reale su Android le segnala all'applicazione e di cosa dovrebbe fare un'applicazione quando si verificano tali errori.

## Errori irreversibili e non irreversibili
<a name="broadcast-android-fatal-vs-nonfatal-errors"></a>

L'oggetto errore ha un campo booleano "è irreversibile" di `BroadcastException`.

In generale, gli errori irreversibili sono legati alla connessione al server degli stage (ad es. non è possibile stabilire una connessione oppure la connessione viene interrotta e non può essere recuperata). L'applicazione dovrebbe ricreare lo stage e riaccedervi, possibilmente con un nuovo token o quando la connettività del dispositivo viene ripristinata.

Gli errori non irreversibili sono generalmente correlati allo stato di pubblicazione/sottoscrizione e vengono gestiti dall'SDK, che riprova l'operazione di pubblicazione/sottoscrizione.

Puoi controllare questa proprietà:

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

## Errori di accesso
<a name="broadcast-android-stage-join-errors"></a>

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

Ciò accade quando il token dello stage non è conforme.

L'SDK genera un'eccezione Java da una chiamata a `stage.join`, con codice di errore = 1000 e fatal = true.

**Azione**: crea un token valido e riprova a partecipare.

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

Ciò accade quando il token dello stage è scaduto.

L'SDK genera un'eccezione Java da una chiamata a `stage.join`, con codice di errore = 1001 e fatal = true.

**Azione**: crea un nuovo token e riprova a partecipare.

### Token non valido o revocato
<a name="broadcast-android-stage-join-errors-invalid-token"></a>

Ciò accade quando il token dello stage è conforme ma viene rifiutato dal server degli stage. Ciò viene segnalato in modo asincrono tramite il renderer dello stage fornito dall'applicazione.

L'SDK chiama `onConnectionStateChanged` con un'eccezione, con codice di errore = 1026 e fatal = true.

**Azione**: crea un token valido e riprova a partecipare.

### Errori di rete per l'accesso iniziale
<a name="broadcast-android-stage-join-errors-network-initial-join"></a>

Ciò accade quando l'SDK non riesce a contattare il server degli stage per stabilire una connessione. Ciò viene segnalato in modo asincrono tramite il renderer dello stage fornito dall'applicazione.

L'SDK chiama `onConnectionStateChanged` con un'eccezione, con codice di errore = 1300 e fatal = true.

**Azione**: attendi il ripristino della connettività del dispositivo e riprova a connetterti.

### Errori di rete quando è già stato effettuato l'accesso
<a name="broadcast-android-stage-join-errors-network-already-joined"></a>

Se la connessione di rete del dispositivo si interrompe, l'SDK potrebbe perdere la connessione ai server dello stage. Ciò viene segnalato in modo asincrono tramite il renderer dello stage fornito dall'applicazione.

L'SDK chiama `onConnectionStateChanged` con un'eccezione, con codice di errore = 1300 e fatal = true.

**Azione**: attendi il ripristino della connettività del dispositivo e riprova a connetterti.

## Errori di pubblicazione/sottoscrizione
<a name="broadcast-android-publish-subscribe-errors"></a>

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

Esistono diversi tipi di errori:
+ MultihostSessionOfferCreationFailPublish (1020)
+ MultihostSessionOfferCreationFailSubscribe (1021)
+ MultihostSessionNoIceCandidates (1022)
+ MultihostSessionStageAtCapacity (1024)
+ SignallingSessionCannotRead (1201)
+ SignallingSessionCannotSend (1202)
+ Sessione di segnalazione: risposta errata (1203)

Questi vengono segnalati in modo asincrono tramite il renderer dello stage fornito dall'applicazione.

L'SDK riprova l'operazione per un numero limitato di volte. Durante i nuovi tentativi, lo stato di pubblicazione/sottoscrizione è `ATTEMPTING_PUBLISH`/`ATTEMPTING_SUBSCRIBE`. Se i nuovi tentativi hanno esito positivo, lo stato diventa `PUBLISHED`/`SUBSCRIBED`.

L'SDK chiama `onError` con il codice di errore pertinente e fatal = false.

**Azione**: non è necessaria alcuna azione, poiché l'SDK riprova automaticamente. Facoltativamente, l'applicazione può aggiornare la strategia per imporre ulteriori tentativi.

### Già stabilita, poi fallita
<a name="broadcast-android-publish-subscribe-errors-established"></a>

Una pubblicazione o una sottoscrizione possono fallire una volta stabilite, molto probabilmente a causa di un errore di rete. Il codice di errore per una "connessione peer interrotta a causa di un errore di rete" è 1400.

Ciò viene segnalato in modo asincrono tramite il renderer dello stage fornito dall'applicazione.

L'SDK riprova l'operazione di pubblicazione/sottoscrizione. Durante i nuovi tentativi, lo stato di pubblicazione/sottoscrizione è `ATTEMPTING_PUBLISH`/`ATTEMPTING_SUBSCRIBE`. Se i nuovi tentativi hanno esito positivo, lo stato diventa `PUBLISHED`/`SUBSCRIBED`.

L'SDK chiama `onError` con il codice di errore = 1400 e fatal = false.

**Azione**: non è necessaria alcuna azione, poiché l'SDK riprova automaticamente. Facoltativamente, l'applicazione può aggiornare la strategia per imporre ulteriori tentativi. In caso di perdita totale della connettività, è probabile che anche la connessione agli stage fallisca.

# SDK di trasmissione IVS: Guida per iOS I Streaming in tempo reale
<a name="broadcast-ios"></a>

L'SDK di trasmissione per lo streaming in tempo reale IVS per iOS consente ai partecipanti di inviare e ricevere video su iOS.

Il modulo `AmazonIVSBroadcast` implementa l'interfaccia descritta in questo documento. Sono supportate le seguenti operazioni:
+ Partecipa a uno stage 
+ Pubblica contenuti multimediali per gli altri partecipanti allo stage
+ Iscriviti ai contenuti multimediali degli altri partecipanti allo stage
+ Gestisci e monitora video e audio pubblicati sullo stage
+ Ottieni statistiche WebRTC per ogni connessione peer
+ Tutte le operazioni dell'SDK di trasmissione in streaming a bassa latenza IVS per iOS

**Ultima versione dell'SDK di trasmissione iOS:** 1.40.0 ([Note di rilascio](https://docs.aws.amazon.com/ivs/latest/RealTimeUserGuide/release-notes.html#mar12-26-broadcast-ios-rt)) 

**Documentazione di riferimento:** per informazioni sui metodi più importanti disponibili nell'SDK di trasmissione di Amazon IVS per iOS, consulta la documentazione di riferimento all'indirizzo [https://aws.github.io/amazon-ivs-'-docs/1.40.0/ios/](https://aws.github.io/amazon-ivs-broadcast-docs/1.40.0/ios/).

**Codice di esempio:** consultare il repository di esempio iOS su 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).

**Requisiti della piattaforma:** iOS 14\$1

# Guida introduttiva all'SDK di trasmissione IVS su iOS \$1 Streaming in tempo reale
<a name="broadcast-ios-getting-started"></a>

Questo documento illustra i passaggi necessari per iniziare a utilizzare l'SDK di trasmissione IVS per lo streaming in tempo reale su iOS.

## Installare la libreria
<a name="broadcast-ios-install"></a>

Si consiglia di integrare l'SDK di trasmissione tramite Swift Package Manager. (in alternativa, esiste la possibilità di aggiungere il framework al proprio progetto manualmente).

### Consigliato: Integrare l'SDK di trasmissione (Swift Package Manager)
<a name="broadcast-ios-install-swift"></a>

1. Scaricare il file Package.swift da [https://broadcast.live-video.net/1.40.0/Package.swift](https://broadcast.live-video.net/1.40.0/Package.swift).

1. Nel tuo progetto, crea una nuova directory denominata AmazonIVSBroadcast e aggiungila al controllo delle versioni.

1. Inserire il file Package.swift file nella nuova directory.

1. In Xcode, vai su **File > Aggiungi dipendenze del pacchetto** e seleziona **Aggiungi locale…**

1. Passare e selezionare la directory AmazonIVSBroadcast creata e selezionare **Aggiungi pacchetto**.

1. Quando viene richiesto di **scegliere i prodotti del pacchetto per AmazonIVSBroadcast**, selezionare **AmazonIVSBroadcastStages** come **prodotto del pacchetto** impostando la destinazione dell'applicazione nella sezione **Aggiungi alla destinazione**.

1. Seleziona **Aggiungi pacchetto**.

**Importante:** l'SDK di trasmissione per lo streaming in tempo reale IVS include tutte le funzionalità dell'SDK di trasmissione per lo streaming a bassa latenza IVS. Non è possibile integrare entrambi gli SDK nello stesso progetto.

### Approccio alternativo: installare manualmente il framework
<a name="broadcast-ios-install-manual"></a>

1. Scarica la versione più recente da [ 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. Estrarre i contenuti dell'archivio. `AmazonIVSBroadcast.xcframework` contiene l'SDK sia per il dispositivo sia per il simulatore.

1. Incorporare `AmazonIVSBroadcast.xcframework` trascinandolo nella sezione **Framework, librerie e contenuto incorporato** della scheda **Generali** per il target dell'applicazione.  
![\[La sezione Framework, librerie e contenuto incorporato della scheda Generali per il target dell'applicazione.\]](http://docs.aws.amazon.com/it_it/ivs/latest/RealTimeUserGuide/images/iOS_Broadcast_SDK_Guide_xcframework.png)

## Richiedere autorizzazioni
<a name="broadcast-ios-permissions"></a>

L'app deve richiedere l'autorizzazione per accedere alla fotocamera e al microfono dell'utente. (Questo non riguarda solo Amazon IVS, ma qualsiasi applicazione che abbia bisogno di accedere alle fotocamere e ai microfoni.)

Qui, controlliamo se l'utente ha già concesso le autorizzazioni e, in caso contrario, ne facciamo richiesta:

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

È necessario eseguire questa operazione per entrambe le tipologie di contenuti multimediali `.video` e `.audio`, se si desidera accedere rispettivamente a fotocamere e microfoni.

Inoltre, è necessario aggiungere voci per `NSCameraUsageDescription` e `NSMicrophoneUsageDescription` a `Info.plist`. In caso contrario, quando si tenta di richiedere le autorizzazioni l'app potrebbe subire un arresto anomalo.

## Disabilitare il timer di inattività dell'applicazione
<a name="broadcast-ios-disable-idle-timer"></a>

Questo passaggio è facoltativo, ma è consigliato. Impedisce al dispositivo di andare in sospensione durante l'utilizzo dell'SDK di trasmissione, che causerebbe l'interruzione della trasmissione.

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

# Pubblicazione e sottoscrizione con l'SDK di trasmissione IVS su iOS \$1 Streaming in tempo reale
<a name="ios-publish-subscribe"></a>

Questo documento illustra i passaggi necessari per pubblicare e sottoscrivere una fase utilizzando l'SDK di trasmissione IVS per lo streaming in tempo reale su iOS.

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

La funzionalità in tempo reale si basa su tre concetti fondamentali: [fase](#ios-publish-subscribe-concepts-stage), [strategia](#ios-publish-subscribe-concepts-strategy) e [renderer](#ios-publish-subscribe-concepts-renderer). L'obiettivo di progettazione è ridurre al minimo la quantità di logica lato client necessaria per creare un prodotto funzionante.

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

La classe `IVSStage` è il principale punto di interazione tra l'applicazione host e l'SDK. La classe rappresenta lo stage stesso e serve per entrare e uscire dallo stage. La creazione o la partecipazione a uno stage richiedono una stringa di token valida e non scaduta dal piano di controllo (control-plane) (rappresentata come `token`). Entrare e uscire da uno stage è semplice.

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

try stage.join()

stage.leave()
```

La classe `IVSStage` è anche il luogo in cui possono essere collegati `IVSStageRenderer` e `IVSErrorDelegate`:

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

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

Il protocollo `IVSStageStrategy` consente all'applicazione host di comunicare lo stato desiderato dello stage all'SDK. È necessario implementare tre funzioni: `shouldSubscribeToParticipant`, `shouldPublishParticipant` e `streamsToPublishForParticipant`. Sono tutte analizzate di seguito.

#### Sottoscrizione ai partecipanti
<a name="ios-publish-subscribe-concepts-strategy-participants"></a>

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

Quando un partecipante remoto partecipa a uno stage, l'SDK interroga l'applicazione host sullo stato della sottoscrizione desiderato per quel partecipante. Le opzioni sono `.none`, `.audioOnly` e `.audioVideo`. Quando si restituisce un valore per questa funzione, l'applicazione host non deve preoccuparsi dello stato di pubblicazione, dello stato della sottoscrizione corrente o dello stato della connessione allo stage. Se viene restituito `.audioVideo`, l'SDK attende che il partecipante remoto effettui la pubblicazione prima della sottoscrizione e aggiorna l'applicazione host tramite il renderer durante tutto il processo.

Di seguito è riportata un'implementazione di esempio:

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

Questa è l'implementazione completa di questa funzione per un'applicazione host che vuole sempre che tutti i partecipanti si vedano tra loro, ad esempio un'applicazione di chat video.

Sono possibili anche implementazioni più avanzate. Utilizza la proprietà `attributes` su `IVSParticipantInfo` per iscriverti selettivamente ai partecipanti in base agli attributi forniti dal server:

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

Questa può essere usata per creare uno stage in cui i moderatori possano monitorare tutti gli ospiti senza essere visti o ascoltati. L'applicazione host potrebbe utilizzare una logica aziendale aggiuntiva per consentire ai moderatori di vedersi ma rimanendo invisibili agli ospiti.

#### Configurazione dell'abbonamento ai partecipanti
<a name="ios-publish-subscribe-concepts-strategy-participants-config"></a>

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

Se un partecipante moto viene abbonato (consulta la sezione [Abbonamento ai partecipanti](#ios-publish-subscribe-concepts-strategy-participants)), l'SDK interroga l'applicazione host su una configurazione di abbonamento personalizzata per tale partecipante. Questa configurazione è facoltativa e consente all'applicazione host di controllare determinati aspetti del comportamento dell'abbonato. Per informazioni sui valori che è possibile configurare, consulta la sezione [SubscribeConfiguration](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/interfaces/SubscribeConfiguration) nella documentazione di riferimento dell'SDK.

Di seguito è riportata un'implementazione di esempio:

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

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

    return config
}
```

Questa implementazione aggiorna il ritardo minimo del jitter-buffer per tutti i partecipanti abbonati a un valore predefinito di `MEDIUM`.

Come per `shouldSubscribeToParticipant`, sono possibili anche implementazioni più avanzate. Il valore `ParticipantInfo` dato può essere utilizzato per aggiornare selettivamente la configurazione di abbonamento per partecipanti specifici.

Consigliamo di utilizzare i valori predefiniti. Specifica la configurazione personalizzata solo se desideri modificare un comportamento particolare.

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

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

Una volta connesso allo stage, l'SDK interroga l'applicazione host per vedere se un dato partecipante deve eseguire una pubblicazione. Viene richiamata solo per i partecipanti locali che hanno il permesso di pubblicare in base al token fornito.

Di seguito è riportata un'implementazione di esempio:

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

Si tratta di un'applicazione di chat video standard in cui gli utenti vogliono sempre pubblicare. Possono disattivare e riattivare l'audio e il video per essere nascosti o visti/ascoltati immediatamente. Possono anche usare il comando di pubblicazione/annullamento della pubblicazione, ma è molto più lento. È preferibile disattivare/riattivare l'audio nei casi d'uso in cui è consigliabile modificare spesso la visibilità.

#### Scelta dei flussi da pubblicare
<a name="ios-publish-subscribe-concepts-strategy-streams"></a>

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

Durante la pubblicazione, serve a determinare quali flussi audio e video devono essere pubblicati. Questo argomento verrà trattato dettagliatamente più avanti in [Pubblicazione di un flusso multimediale](#ios-publish-subscribe-publish-stream).

#### Aggiornamento della strategia
<a name="ios-publish-subscribe-concepts-strategy-updates"></a>

La strategia è pensata per essere dinamica: è possibile modificare i valori restituiti da una qualsiasi delle funzioni precedenti in qualsiasi momento. Ad esempio, se l'applicazione host non desidera pubblicare finché l'utente finale non tocca un pulsante, è possibile restituire una variabile da `shouldPublishParticipant` (del tipo `hasUserTappedPublishButton`). Quando quella variabile cambia in base a un'interazione da parte dell'utente finale, chiama `stage.refreshStrategy()` per segnalare all'SDK che dovrebbe eseguire una query sulla strategia per i valori più recenti, applicando solo quanto modificato. Se l'SDK rileva che il valore `shouldPublishParticipant` è cambiato, avvierà il processo di pubblicazione. Se le query dell'SDK e tutte le funzioni restituiscono lo stesso valore di prima, la chiamata `refreshStrategy` non apporterà alcuna modifica allo stage.

Se il valore restituito di `shouldSubscribeToParticipant` cambia da `.audioVideo` a `.audioOnly`, il flusso video verrà rimosso per tutti i partecipanti con valori restituiti modificati, se in precedenza esisteva un flusso video.

In genere, lo stage utilizza la strategia per applicare in modo più efficiente la differenza tra le strategie precedenti e quelle attuali, senza che l'applicazione host debba preoccuparsi di tutto lo stato necessario per gestirla correttamente. Per questo motivo, considera la chiamata a `stage.refreshStrategy()` come un'operazione a basso costo, perché non viene eseguita a meno che la strategia non cambi.

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

Il protocollo `IVSStageRenderer` comunica lo stato desiderato dello stage all'applicazione host. Gli aggiornamenti all'interfaccia utente dell'applicazione host in genere possono essere alimentati interamente dagli eventi forniti dal renderer. Il renderer fornisce le funzioni seguenti:

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

Non è previsto che le informazioni fornite dal renderer influiscano sui valori restituiti della strategia. Ad esempio, non è previsto che il valore restituito di `shouldSubscribeToParticipant` cambi quando viene chiamato `participant:didChangePublishState`. Se l'applicazione host desidera effettuare la sottoscrizione a un particolare partecipante, deve restituire il tipo di abbonamento desiderato indipendentemente dallo stato di pubblicazione di quel partecipante. L'SDK è responsabile di garantire che lo stato desiderato della strategia venga applicato al momento giusto in base allo stato dello stage.

Ricorda che solo i partecipanti alla pubblicazione attivano `participantDidJoin` e ogni volta che un partecipante interrompe la pubblicazione o abbandona la sessione dello stage viene attivato `participantDidLeave`.

## Pubblicazione di un flusso multimediale
<a name="ios-publish-subscribe-publish-stream"></a>

I dispositivi locali, come microfoni e fotocamere integrati, vengono rilevati tramite `IVSDeviceDiscovery`. Ecco un esempio di selezione della fotocamera frontale e del microfono predefinito, che vengono poi restituiti come `IVSLocalStageStreams` per la pubblicazione dall'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]
}
```

## Visualizzazione e rimozione dei partecipanti
<a name="ios-publish-subscribe-participants"></a>

Una volta completata la sottoscrizione, riceverai una serie di oggetti `IVSStageStream` tramite la funzione `didAddStreams` del renderer. Per visualizzare un'anteprima o ricevere le statistiche del livello audio su questo partecipante, puoi accedere all'oggetto `IVSDevice` sottostante dal flusso:

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

Quando un partecipante interrompe la pubblicazione o annulla l'iscrizione, la funzione `didRemoveStreams` viene chiamata con i flussi che sono stati rimossi. Le applicazioni host devono utilizzarlo come segnale per rimuovere il flusso video del partecipante dalla gerarchia delle visualizzazioni.

`didRemoveStreams` viene richiamato per tutti gli scenari in cui un flusso potrebbe essere rimosso, tra cui:
+ Il partecipante remoto interrompe la pubblicazione.
+ Un dispositivo locale annulla l'iscrizione o modifica l'abbonamento da `.audioVideo` a `.audioOnly`.
+ Il partecipante remoto lascia lo stage.
+ Il partecipante locale lascia lo stage.

Poiché `didRemoveStreams` viene richiamato per tutti gli scenari, non è richiesta alcuna logica aziendale personalizzata per la rimozione dei partecipanti dall'interfaccia utente durante le operazioni di abbandono remote o locali.

## Disattivazione e riattivazione dell'audio dei flussi multimediali
<a name="ios-publish-subscribe-mute-streams"></a>

Gli oggetti `IVSLocalStageStream` hanno una funzione `setMuted` che controlla se l'audio del flusso è disattivato. Questa funzione può essere richiamata sul flusso prima o dopo la restituzione dalla funzione della strategia `streamsToPublishForParticipant`.

**Importante**: se una nuova istanza di oggetto `IVSLocalStageStream` viene restituita da `streamsToPublishForParticipant` dopo una chiamata a `refreshStrategy`, lo stato di silenziamento del nuovo oggetto di flusso viene applicato allo stage. Fai attenzione quando crei nuove istanze `IVSLocalStageStream` per assicurarti che lo stato di silenziamento previsto venga mantenuto.

## Monitoraggio dello stato di silenziamento dei contenuti multimediali dei partecipanti remoti
<a name="ios-publish-subscribe-mute-state"></a>

Quando un partecipante modifica lo stato di silenziamento del proprio flusso video o audio, la funzione `didChangeMutedStreams` del renderer viene richiamata con una serie di flussi che sono stati modificati. Usa la proprietà `isMuted` su `IVSStageStream` per aggiornare l'interfaccia utente di conseguenza:

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

## Creazione di una configurazione dello stage
<a name="ios-publish-subscribe-stage-config"></a>

Per personalizzare i valori della configurazione video di uno stage, usa `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
```

## Ottenimento delle statistiche WebRTC
<a name="ios-publish-subscribe-webrtc-stats"></a>

Per ottenere le statistiche WebRTC più recenti per un flusso di pubblicazione o un flusso di iscrizione, usa `requestRTCStats` su `IVSStageStream`. Quando una raccolta è completata, riceverai statistiche tramite il `IVSStageStreamDelegate` che può essere impostato su `IVSStageStream`. Per raccogliere continuamente statistiche WebRTC, chiama questa funzione su 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)")
      }
   }
}
```

## Ottieni gli attributi dei partecipanti
<a name="ios-publish-subscribe-participant-attributes"></a>

Se specifichi gli attributi nella richiesta dell'operazione `CreateParticipantToken`, puoi visualizzare gli attributi nelle proprietà `IVSParticipantInfo`:

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

## Incorpora messaggi
<a name="ios-publish-subscribe-embed-messages"></a>

Il metodo `embedMessage` su IVSImageDevice consente di inserire payload di metadati direttamente nei frame video durante la pubblicazione. Ciò consente la messaggistica sincronizzata con i frame per applicazioni in tempo reale. L’embedding dei messaggi è disponibile solo quando si utilizza l'SDK per la pubblicazione in tempo reale (non per la pubblicazione a bassa latenza).

Non è garantito che i messaggi incorporati arrivino agli abbonati perché sono incorporati direttamente nei frame video e trasmessi tramite UDP, il che non garantisce la consegna dei pacchetti. La perdita di pacchetti durante la trasmissione può causare la perdita di messaggi, soprattutto in condizioni di rete precarie. Per mitigare questo problema, il metodo `embedMessage` include un parametro `repeatCount` che duplica il messaggio su più frame consecutivi, aumentando l'affidabilità della consegna. Questa funzione è disponibile solo per i flussi video.

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

I client di pubblicazione possono incorporare i payload dei messaggi nel loro flusso video utilizzando il metodo `embedMessage` su IVSImageDevice. La dimensione del payload deve essere maggiore di 0 KB e inferiore a 1 KB. Il numero di messaggi incorporati inseriti al secondo non deve superare i 10 KB al secondo.

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

### Ripetizione dei payload del messaggio
<a name="ios-embed-messages-repeat-payloads"></a>

Utilizza `repeatCount` per duplicare il messaggio su più frame per una maggiore affidabilità. Il valore deve essere compreso tra 0 e 30. I client di ricezione devono disporre di una logica per deduplicare il messaggio.

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

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

### Lettura dei messaggi incorporati
<a name="ios-embed-messages-read-messages"></a>

Vedi «Ottieni informazioni supplementari sul miglioramento (SEI)» di seguito per sapere come leggere i messaggi incorporati dai flussi in entrata. 

## Ottenimento di dati Supplemental Enhancement Information (SEI)
<a name="ios-publish-subscribe-sei-attributes"></a>

L'unità NAL Supplemental Enhancement Information (SEI) viene utilizzata per archiviare i metadati allineati al fotogramma insieme al video. I clienti abbonati possono leggere i payload SEI di un publisher che pubblica video H.264 ispezionando la proprietà `embeddedMessages` sugli oggetti `IVSImageDeviceFrame` che provengono da `IVSImageDevice` del publisher. A tal fine, acquisire `IVSImageDevice` di un publisher, quindi osservare ogni frame tramite un callback fornito a `setOnFrameCallback`, come mostrato nell'esempio seguente:

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

## Continuazione della sessione in background
<a name="ios-publish-subscribe-background-session"></a>

Quando l'app entra in background, puoi continuare a rimanere nello stage mentre ascolti l'audio remoto, anche se non puoi continuare a inviare la tua immagine e il tuo audio. Dovrai aggiornare la tua implementazione `IVSStrategy` per interrompere la pubblicazione e iscriverti a `.audioOnly` (o `.none`, se applicabile):

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

Quindi effettua una chiamata a `stage.refreshStrategy()`.

## Codifica a livelli con Simulcast
<a name="ios-publish-subscribe-layered-encoding-simulcast"></a>

La codifica a livelli con simulcast è una funzionalità di streaming in tempo reale IVS che consente ai publisher di inviare più livelli di qualità video differenti e agli abbonati di configurare dinamicamente o manualmente tali livelli. La funzionalità è descritta più approfonditamente nel documento [Ottimizzazioni dello streaming](real-time-streaming-optimization.md).

### Configurazione della codifica a livelli (Publisher)
<a name="ios-layered-encoding-simulcast-configure-publisher"></a>

Per abilitare la codifica a più livelli con simulcast, il publisher deve aggiungere la seguente configurazione a `IVSLocalStageStream` all’istanziazione:

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

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

// Other Stage implementation code
```

A seconda della risoluzione impostata nella configurazione video, un determinato numero di livelli verrà codificato e inviato come definito nella sezione [Livelli, qualità e framerate predefiniti](real-time-streaming-optimization.md#real-time-streaming-optimization-default-layers) di *Ottimizzazioni dello streaming*.

Inoltre, puoi facoltativamente configurare singoli livelli dall'interno della configurazione simulcast:

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

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

try config.simulcast.setLayers(layers)

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

// Other Stage implementation code
```

In alternativa, puoi creare configurazioni di livelli personalizzate, fino a un massimo di tre livelli. Se viene fornita una matrice vuota o non viene specificato alcun valore, vengono utilizzate le impostazioni predefinite sopra descritte. I livelli sono descritti con i seguenti setter di proprietà obbligatori:
+ `setSize: CGSize;`
+ `setMaxBitrate: integer;`
+ `setMinBitrate: integer;`
+ `setTargetFramerate: float;`

A partire dai preset, è possibile sovrascrivere le singole proprietà o creare una configurazione completamente nuova:

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

Per i valori massimi, i limiti e gli errori che possono essere attivati durante la configurazione di singoli livelli, consulta la documentazione di riferimento dell'SDK.

### Configurazione della codifica a livelli (Abbonato)
<a name="ios-layered-encoding-simulcast-configure-subscriber"></a>

L'abbonato non deve eseguire alcuna operazione per abilitare la codifica a livelli. Se un publisher invia layer simulcast, per impostazione predefinita il server si adatta dinamicamente tra i livelli per scegliere la qualità ottimale in base al dispositivo e alle condizioni di rete dell'abbonato.

In alternativa, per scegliere layer espliciti inviati dal publisher, sono disponibili diverse opzioni, descritte di seguito.

### Opzione 1: preferenza di qualità del livello iniziale
<a name="ios-layered-encoding-simulcast-layer-quality-preference"></a>

Usando la strategia `subscribeConfigurationForParticipant`, è possibile scegliere quale livello iniziale si desidera ricevere come abbonato:

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

    config.simulcast.initialLayerPreference = .lowestQuality

    return config
}
```

Per impostazione predefinita, agli abbonati viene sempre inviato per primo il livello di qualità più bassa; questo livello passa lentamente al livello di qualità più alta. Ciò ottimizza il consumo di larghezza di banda da parte dell'utente finale e offre il tempo ottimale per i video, riducendo i blocchi iniziali del video per gli utenti su reti più deboli.

Queste opzioni sono disponibili per `InitialLayerPreference`:
+ `lowestQuality` — Il server fornisce prima il livello video con la qualità più bassa. In questo modo, si ottimizza il consumo di larghezza di banda e il tempo di accesso ai contenuti multimediali. La qualità è definita come combinazione di dimensioni, bitrate e framerate del video. Ad esempio, un video 720p ha una qualità inferiore rispetto a un video 1080p.
+ `highestQuality` — Il server offre prima il livello video con la qualità più alta. Ciò ottimizza la qualità ma può aumentare il tempo di visualizzazione dei contenuti multimediali. La qualità è definita come combinazione di dimensioni, bitrate e framerate del video. Ad esempio, un video 1080p è di qualità superiore rispetto a un video 720p.

**Nota**: per rendere effettive le preferenze iniziali del livello (la chiamata `initialLayerPreference`), è necessario effettuare un nuovo abbonamento poiché questi aggiornamenti non si applicano all'abbonamento attivo.

### Opzione 2: livello preferito per lo streaming
<a name="ios-layered-encoding-simulcast-preferred-layer"></a>

Il metodo strategico `preferredLayerForStream` consente di selezionare un livello dopo l'inizio del flusso. Questo metodo di strategia riceve le informazioni sul partecipante e sul flusso, permettendoti di selezionare un livello singolarmente per ciascun partecipante. L’SDK chiama questo metodo in risposta a eventi specifici, come quando cambiano i livelli del flusso, lo stato del partecipante o quando l’applicazione host aggiorna la strategia.

Il metodo di strategia restituisce un oggetto `IVSRemoteStageStreamLayer`, che può essere uno dei seguenti:
+ Un oggetto di livello, ad esempio uno restituito da `IVSRemoteStageStream.layers`.
+ null, indicante che non deve essere selezionato alcun livello e che è preferibile l'adattamento dinamico.

Ad esempio, la strategia seguente prevede che gli utenti selezionino sempre il livello di video con la qualità più bassa disponibile:

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

Per reimpostare la selezione del livello e tornare all'adattamento dinamico, restituire null o non definito nella strategia. In questo esempio, `appState` è una variabile segnaposto che rappresenta lo stato dell’applicazione host.

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

### Opzione 3: helper per livelli RemoteStageStream
<a name="ios-layered-encoding-simulcast-remotestagestream-helpers"></a>

`IVSRemoteStageStream` dispone di diversi helper che possono essere usati per prendere decisioni sulla selezione dei livelli e visualizzare le selezioni corrispondenti agli utenti finali:
+ **Eventi dei livelli**: oltre a `IVSStageRenderer`, `IVSRemoteStageStreamDelegate` include eventi che comunicano modifiche di adattamento di livelli e simulcast:
  + `func stream(_ stream: IVSRemoteStageStream, didChangeAdaption adaption: Bool)`
  + `func stream(_ stream: IVSRemoteStageStream, didChange layers: [IVSRemoteStageStreamLayer])`
  + `func stream(_ stream: IVSRemoteStageStream, didSelect layer: IVSRemoteStageStreamLayer?, reason: IVSRemoteStageStream.LayerSelectedReason)`
+ **Metodi dei livelli**: `IVSRemoteStageStream` include diversi metodi helper che possono essere usati per ottenere informazioni sul flusso e sui livelli presentati. Questi metodi sono disponibili sul flusso remoto fornito nella strategia `preferredLayerForStream`, nonché sui flussi remoti esposti tramite `func stage(_ stage: IVSStage, participant: IVSParticipantInfo, didAdd streams: [IVSStageStream])`.
  + `stream.layers`
  + `stream.selectedLayer`
  + `stream.lowestQualityLayer`
  + `stream.highestQualityLayer`
  + `stream.layers(with: IVSRemoteStageStreamLayerConstraints)`

Per i dettagli, consulta la classe `IVSRemoteStageStream` nella [Documentazione di riferimento dell'SDK](https://aws.github.io/amazon-ivs-broadcast-docs/latest/ios/). Per il motivo `LayerSelected`, se viene restituito `UNAVAILABLE`, allora il livello richiesto non può essere selezionato. Per mantenere la stabilità del flusso, al suo posto viene effettuata la migliore selezione, che in genere è un livello di qualità inferiore.

## Trasmissione della fase a un canale IVS
<a name="ios-publish-subscribe-broadcast-stage"></a>

Per trasmettere uno stage, crea una `IVSBroadcastSession` separata e segui le normali istruzioni per la trasmissione con l'SDK descritte sopra. La proprietà `device` su `IVSStageStream` sarà un `IVSImageDevice` o `IVSAudioDevice` come mostrato nel frammento precedente; queste possono essere collegate al `IVSBroadcastSession.mixer` per trasmettere l'intero stage in un layout personalizzabile.

Facoltativamente, puoi comporre una fase e trasmetterla a un canale IVS a bassa latenza in modo da raggiungere un pubblico più vasto. Consulta [Abilitazione di più host su un flusso Amazon IVS](https://docs.aws.amazon.com//ivs/latest/LowLatencyUserGuide/multiple-hosts.html) nella Guida per l'utente dello streaming a bassa latenza di IVS.

# Come iOS sceglie la risoluzione della fotocamera e la frequenza dei fotogrammi
<a name="ios-publish-subscribe-resolution-framerate"></a>

La fotocamera gestita dall'SDK di trasmissione ottimizza la risoluzione e la frequenza dei fotogrammi (fotogrammi al secondo o FPS) per ridurre al minimo la produzione di calore e il consumo di energia. Questa sezione spiega come vengono selezionati la risoluzione e la frequenza dei fotogrammi per ottimizzare le applicazioni host per i rispettivi casi d'uso.

Quando si crea un `IVSLocalStageStream` con una `IVSCamera`, la fotocamera è ottimizzata per una frequenza dei fotogrammi di `IVSLocalStageStreamVideoConfiguration.targetFramerate` e una risoluzione di `IVSLocalStageStreamVideoConfiguration.size`. La chiamata `IVSLocalStageStream.setConfiguration` aggiorna la fotocamera con i valori più recenti. 

## Anteprima della fotocamera
<a name="resolution-framerate-camera-preview"></a>

Se si crea un'anteprima di una `IVSCamera` senza collegarla a una `IVSBroadcastSession` o una `IVSStage`, per impostazione predefinita vengono impostate una risoluzione di 1080p e una frequenza dei fotogrammi di 60 FPS.

## Trasmissione a una fase
<a name="resolution-framerate-broadcast-stage"></a>

Quando si utilizza una `IVSBroadcastSession` per trasmettere una `IVSStage`, l'SDK cerca di ottimizzare la telecamera con una risoluzione e una frequenza dei fotogrammi che soddisfino i criteri di entrambe le sessioni.

Ad esempio, se la configurazione di trasmissione è impostata per avere una frequenza dei fotogrammi di 15 FPS e una risoluzione di 1080p, mentre la fase ha una frequenza dei fotogrammi di 30 FPS e una risoluzione di 720p, l'SDK selezionerà una configurazione della fotocamera con una frequenza dei fotogrammi di 30 FPS e una risoluzione di 1080p. La `IVSBroadcastSession` salterà un fotogramma ogni due proveniente dalla fotocamera e la `IVSStage` ridimensionerà l'immagine da 1080p a 720p.

Se un'applicazione host prevede di utilizzare insieme `IVSBroadcastSession` e `IVSStage` con una fotocamera, si consiglia di impostare sui medesimi valori le proprietà `targetFramerate` e `size` delle rispettive configurazioni. Una mancata corrispondenza potrebbe causare la riconfigurazione automatica della fotocamera durante l'acquisizione del video, causando un breve ritardo nella consegna degli elementi video.

Se per il caso d'uso dell'applicazione host non fosse possibile utilizzare valori identici, creare prima la videocamera di qualità superiore impedirà alla telecamera di riconfigurarsi quando viene aggiunta la sessione di qualità inferiore. Ad esempio, se si trasmette a 1080p e 30 FPS e poi si prende parte a una fase impostata su 720p e 30 FPS, la fotocamera non si riconfigurerà automaticamente e il video continuerà senza interruzioni. Questo perché 720p è inferiore o uguale a 1080p e 30 FPS è inferiore o uguale a 30 FPS.

## Frequenza dei fotogrammi, risoluzioni e proporzioni arbitrari
<a name="resolution-framerate-arbitrary"></a>

La maggior parte dell'hardware della fotocamera può corrispondere esattamente ai formati più comuni, come 720p a 30 FPS o 1080p a 60 FPS. Tuttavia, non è possibile fornire una corrispondenza esatta con tutti i formati. L'SDK di trasmissione sceglie la configurazione della fotocamera in base alle seguenti regole (in ordine di priorità):

1. La larghezza e l'altezza della risoluzione sono maggiori o uguali alla risoluzione desiderata, ma larghezza e altezza sono le più piccole possibili nel rispetto di questo vincolo.

1. La frequenza dei fotogrammi è maggiore o uguale alla frequenza dei fotogrammi desiderata, ma la frequenza dei fotogrammi è la più bassa possibile nel rispetto di questo vincolo.

1. Le proporzioni corrispondono alle proporzioni desiderate.

1. Se esistono più formati corrispondenti, viene utilizzato il formato con il campo visivo più ampio.

Di seguito, sono riportati due esempi:
+ L'applicazione host sta cercando di trasmettere in 4k a 120 FPS. La fotocamera selezionata supporta solo 4k a 60 FPS oppure 1080p a 120 FPS. Il formato selezionato sarà 4k a 60 FPS, poiché la regola di risoluzione ha una priorità maggiore rispetto alla regola della frequenza dei fotogrammi.
+ È richiesta una risoluzione irregolare, 1910x1070. La fotocamera utilizzerà 1920x1080. *Attenzione: scegliendo una risoluzione come 1921x1080, la fotocamera passerà alla successiva risoluzione disponibile (ad esempio 2592x1944), il che comporta una penalizzazione della CPU e della larghezza di banda della memoria*.

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

Android non regola la risoluzione o la frequenza dei fotogrammi all'istante come fa iOS, quindi ciò non influisce sull'SDK di trasmissione Android.

# Problemi noti e soluzioni alternative per l'SDK di trasmissione IVS su iOS \$1 Streaming in tempo reale
<a name="broadcast-ios-known-issues"></a>

Questo documento elenca i problemi noti che potresti riscontrare quando utilizzi lo Streaming in tempo reale di Amazon IVS per la trasmissione su iOS e suggerisce possibili soluzioni alternative.
+ La modifica del routing audio Bluetooth può essere imprevedibile. Se si connette un nuovo dispositivo a metà sessione, iOS potrebbe cambiare o meno il routing di input in modo automatico. Inoltre, non è possibile scegliere tra più auricolari Bluetooth collegati contemporaneamente. Ciò accade sia nelle normali sessioni di trasmissione che in quelle relative allo stage.

  **Soluzione alternativa:** se si prevede di utilizzare un auricolare Bluetooth, collegarlo prima di avviare la trasmissione o lo stage e lasciarlo connesso per tutta la durata della sessione.
+ I partecipanti che utilizzano iPhone 14, iPhone 14 Plus, iPhone 14 Pro o iPhone 14 Pro Max potrebbero causare un problema di eco audio per gli altri partecipanti.

  **Soluzione alternativa:** i partecipanti che utilizzano i dispositivi interessati possono utilizzare le cuffie per evitare il problema dell'eco per gli altri partecipanti.
+ Quando un partecipante accede con un token utilizzato da un altro partecipante, la prima connessione viene disconnessa senza un errore specifico.

  **Soluzione alternativa:** nessuna.
+ Può verificarsi un problema raro per cui il publisher sta pubblicando, ma lo stato di pubblicazione che gli abbonati ricevono è `inactive`.

  **Soluzione alternativa:** prova a uscire e poi a partecipare alla sessione. Se il problema persiste, crea un nuovo token per il publisher.
+ Quando un partecipante pubblica o si iscrive, è possibile ricevere un errore con il codice 1400 che indica la disconnessione dovuta a un problema di rete, anche quando la rete è stabile.

  **Soluzione alternativa:** prova a effettuare nuovamente la pubblicazione/sottoscrizione.
+ Durante una sessione di stage può verificarsi un raro problema di distorsione audio a intermittenza, in genere durante le chiamate di lunga durata.

  **Soluzione alternativa:** il partecipante con audio distorto può uscire e accedere nuovamente alla sessione oppure annullare la pubblicazione e ripubblicare l'audio per risolvere il problema.

# Gestione degli errori nell'SDK di trasmissione IVS su iOS \$1 Streaming in tempo reale
<a name="broadcast-ios-error-handling"></a>

Questa sezione fornisce una panoramica delle condizioni di errore, del modo in cui l'SDK di trasmissione IVS per lo streaming in tempo reale su iOS le segnala all'applicazione e di cosa dovrebbe fare un'applicazione quando si verificano tali errori.

## Errori irreversibili e non irreversibili
<a name="broadcast-ios-fatal-vs-nonfatal-errors"></a>

L'oggetto errore ha un valore booleano "è irreversibile". Questa è una voce del dizionario sotto `IVSBroadcastErrorIsFatalKey` che contiene un valore booleano.

In generale, gli errori irreversibili sono legati alla connessione al server degli stage (ad es. non è possibile stabilire una connessione oppure la connessione viene interrotta e non può essere recuperata). L'applicazione dovrebbe ricreare lo stage e riaccedervi, possibilmente con un nuovo token o quando la connettività del dispositivo viene ripristinata.

Gli errori non irreversibili sono generalmente correlati allo stato di pubblicazione/sottoscrizione e vengono gestiti dall'SDK, che riprova l'operazione di pubblicazione/sottoscrizione.

Puoi controllare questa proprietà:

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

## Errori di accesso
<a name="broadcast-ios-stage-join-errors"></a>

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

Ciò accade quando il token dello stage non è conforme.

L'SDK genera un'eccezione Swift con codice di errore = 1000 e IVSBroadcastErrorIsFatalKey = YES.

**Azione**: crea un token valido e riprova a partecipare.

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

Ciò accade quando il token dello stage è scaduto.

L'SDK genera un'eccezione Swift con codice di errore = 1001 e IVSBroadcastErrorIsFatalKey = YES.

**Azione**: crea un nuovo token e riprova a partecipare.

### Token non valido o revocato
<a name="broadcast-ios-stage-join-errors-invalid-token"></a>

Ciò accade quando il token dello stage è conforme ma viene rifiutato dal server degli stage. Ciò viene segnalato in modo asincrono tramite il renderer dello stage fornito dall'applicazione.

L'SDK chiama `stage(didChange connectionState, withError error)` con codice di errore = 1026 e IVSBroadcastErrorIsFatalKey = YES.

**Azione**: crea un token valido e riprova a partecipare.

### Errori di rete per l'accesso iniziale
<a name="broadcast-ios-stage-join-errors-network-initial-join"></a>

Ciò accade quando l'SDK non riesce a contattare il server degli stage per stabilire una connessione. Ciò viene segnalato in modo asincrono tramite il renderer dello stage fornito dall'applicazione.

L'SDK chiama `stage(didChange connectionState, withError error)` con codice di errore = 1300 e IVSBroadcastErrorIsFatalKey = YES.

**Azione**: attendi il ripristino della connettività del dispositivo e riprova a connetterti.

### Errori di rete quando è già stato effettuato l'accesso
<a name="broadcast-ios-stage-join-errors-network-already-joined"></a>

Se la connessione di rete del dispositivo si interrompe, l'SDK potrebbe perdere la connessione ai server dello stage. Ciò viene segnalato in modo asincrono tramite il renderer dello stage fornito dall'applicazione.

L'SDK chiama `stage(didChange connectionState, withError error)` con codice di errore = 1300 e valore IVSBroadcastErrorIsFatalKey = YES.

**Azione**: attendi il ripristino della connettività del dispositivo e riprova a connetterti.

## Errori di pubblicazione/sottoscrizione
<a name="broadcast-ios-publish-subscribe-errors"></a>

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

Esistono diversi tipi di errori:
+ MultihostSessionOfferCreationFailPublish (1020)
+ MultihostSessionOfferCreationFailSubscribe (1021)
+ MultihostSessionNoIceCandidates (1022)
+ MultihostSessionStageAtCapacity (1024)
+ SignallingSessionCannotRead (1201)
+ SignallingSessionCannotSend (1202)
+ Sessione di segnalazione: risposta errata (1203)

Questi vengono segnalati in modo asincrono tramite il renderer dello stage fornito dall'applicazione.

L'SDK riprova l'operazione per un numero limitato di volte. Durante i nuovi tentativi, lo stato di pubblicazione/sottoscrizione è `ATTEMPTING_PUBLISH`/`ATTEMPTING_SUBSCRIBE`. Se i nuovi tentativi hanno esito positivo, lo stato diventa `PUBLISHED`/`SUBSCRIBED`.

L'SDK chiama `IVSErrorDelegate:didEmitError` con il codice di errore pertinente e `IVSBroadcastErrorIsFatalKey == NO`.

**Azione**: non è necessaria alcuna azione, poiché l'SDK riprova automaticamente. Facoltativamente, l'applicazione può aggiornare la strategia per imporre ulteriori tentativi.

### Già stabilita, poi fallita
<a name="broadcast-ios-publish-subscribe-errors-established"></a>

Una pubblicazione o una sottoscrizione possono fallire una volta stabilite, molto probabilmente a causa di un errore di rete. Il codice di errore per una "connessione peer interrotta a causa di un errore di rete" è 1400.

Ciò viene segnalato in modo asincrono tramite il renderer dello stage fornito dall'applicazione.

L'SDK riprova l'operazione di pubblicazione/sottoscrizione. Durante i nuovi tentativi, lo stato di pubblicazione/sottoscrizione è `ATTEMPTING_PUBLISH`/`ATTEMPTING_SUBSCRIBE`. Se i nuovi tentativi hanno esito positivo, lo stato diventa `PUBLISHED`/`SUBSCRIBED`.

L'SDK chiama `didEmitError` con codice di errore = 1400 e IVSBroadcastErrorIsFatalKey = NO.

**Azione**: non è necessaria alcuna azione, poiché l'SDK riprova automaticamente. Facoltativamente, l'applicazione può aggiornare la strategia per imporre ulteriori tentativi. In caso di perdita totale della connettività, è probabile che anche la connessione agli stage fallisca.

# SDK di trasmissione IVS: Dispositivi misti
<a name="broadcast-mixed-devices"></a>

I dispositivi misti sono dispositivi audio e video che prendono più sorgenti di ingresso e generano una singola uscita. I dispositivi di mixaggio sono una potente funzionalità che consente di definire e gestire più elementi sullo schermo (video) e tracce audio. È possibile combinare video e audio da più sorgenti come fotocamere, microfoni, catture dello schermo e audio e video generati dall'app. È possibile utilizzare le transizioni per spostare queste sorgenti nel video trasmesso in streaming su IVS, nonché aggiungere e rimuovere sorgenti durante lo streaming.

I dispositivi misti sono disponibili nelle versioni audio e immagine. Per creare un dispositivo con immagini misto, chiamare:

`DeviceDiscovery.createMixedImageDevice()` su Android

`IVSDeviceDiscovery.createMixedImageDevice()` su iOS

Il dispositivo restituito può essere collegato a una `BroadcastSession` (streaming a bassa latenza) o `Stage` (streaming in tempo reale), come qualsiasi altro dispositivo.

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

![\[Terminologia per i dispositivi misti per la trasmissione IVS.\]](http://docs.aws.amazon.com/it_it/ivs/latest/RealTimeUserGuide/images/Broadcast_SDK_Mixer_Glossary.png)



| Termine | Descrizione | 
| --- | --- | 
| Dispositivo | Un componente hardware o software che produce input audio o immagine. Esempi di dispositivi sono microfoni, fotocamere, cuffie Bluetooth e dispositivi virtuali come catture di schermo o ingressi di immagini personalizzate. | 
| Dispositivi misti | Un `Device` che può essere collegato a una `BroadcastSession` come qualsiasi altro `Device`, ma con API aggiuntive che consentono di aggiungere oggetti `Source`. I dispositivi misti dispongono di mixer interni che combinano audio o immagini, producendo un unico flusso di audio e immagini in uscita. I dispositivi misti sono disponibili nelle versioni audio e immagine.  | 
| Configurazione del dispositivo misto | Un oggetto di configurazione per il dispositivo misto. Per i dispositivi con immagini miste, questa opzione configura proprietà quali dimensioni e framerate. Per i dispositivi audio misti, configura il numero di canali. | 
|  Origine | Un container che definisce la posizione di un elemento visivo sullo schermo e le proprietà di una traccia audio nel mix audio. Un dispositivo misto può essere configurato con zero o più sorgenti. Alle sorgenti viene assegnata una configurazione che influisce sul modo in cui vengono utilizzati i contenuti multimediali della sorgente. L'immagine qui sopra mostra quattro sorgenti di immagini: [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/it_it/ivs/latest/RealTimeUserGuide/broadcast-mixed-devices.html)  | 
| Configurazione della sorgente |  Un oggetto di configurazione per la sorgente che entra in un dispositivo misto. Gli oggetti di configurazione completi sono descritti di seguito.   | 
| Transition | Per spostare uno slot in una nuova posizione o modificarne alcune proprietà, utilizza `MixedDevice.transitionToConfiguration()`. Questo metodo richiede: [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/it_it/ivs/latest/RealTimeUserGuide/broadcast-mixed-devices.html) | 

## Dispositivo audio misto
<a name="broadcast-mixed-audio-device"></a>

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

`MixedAudioDeviceConfiguration` su Android

`IVSMixedAudioDeviceConfiguration` su iOS


| Name | Tipo | Descrizione | 
| --- | --- | --- | 
| `channels` | Numero intero | Numero di canali di output dal mixer audio. Valori validi: 1, 2. Il canale 1 è audio mono mentre il canale 2 è audio stereo. Default: 2 | 

### Configurazione della sorgente
<a name="broadcast-mixed-audio-device-source-configuration"></a>

`MixedAudioDeviceSourceConfiguration` su Android

`IVSMixedAudioDeviceSourceConfiguration` su iOS


| Name | Tipo | Descrizione | 
| --- | --- | --- | 
| `gain` | Float | Guadagno audio. Questo è un moltiplicatore, quindi qualsiasi valore maggiore di 1 aumenta il guadagno e qualsiasi valore minore di 1 lo diminuisce. Valori validi: 0-2. Default: 1.  | 

## Dispositivo di immagine
<a name="broadcast-mixed-image-device"></a>

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

`MixedImageDeviceConfiguration` su Android

`IVSMixedImageDeviceConfiguration` su iOS


| Name | Tipo | Descrizione | 
| --- | --- | --- | 
| `size` | Vec2 | Dimensioni della tela video. | 
| `targetFramerate` | Numero intero | Il numero di frame di destinazione al secondo per il dispositivo misto. In media questo valore dovrebbe essere raggiunto, ma il sistema potrebbe perdere frame in determinate circostanze (ad esempio, con carico elevato della CPU o della GPU). | 
| `transparencyEnabled` | Booleano | Ciò consente la fusione utilizzando la proprietà `alpha` sulle configurazioni delle sorgenti delle immagini. L'impostazione di questa opzione su `true` aumenta il consumo di memoria e CPU. Impostazione predefinita: `false`. | 

### Configurazione della sorgente
<a name="broadcast-mixed-image-device-source-configuration"></a>

`MixedImageDeviceSourceConfiguration` su Android

`IVSMixedImageDeviceSourceConfiguration` su iOS


| Name | Tipo | Descrizione | 
| --- | --- | --- | 
| `alpha` | Float | L'alfa dello slot. Questo è un fattore moltiplicativo con qualsiasi valore alfa nell'immagine. Valori validi: 0-1, dove 0 è completamente trasparente e 1 è completamente opaco. Default: 1. | 
| `aspect` | AspectMode | La modalità proporzioni per qualsiasi immagine renderizzata nello slot. Valori validi: [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/it_it/ivs/latest/RealTimeUserGuide/broadcast-mixed-devices.html) Impostazione predefinita: `Fit`  | 
| `fillColor` | Vec 4 | Il colore di riempimento da utilizzare con `aspect Fit` quando le proporzioni dello slot e dell'immagine non corrispondono. Il formato è (rosso, verde, blu, alfa). Valore valido (per ciascun canale): 0 - 1. Default: (0, 0, 0, 0). | 
| `position` | Vec2 | Posizione dello slot (in pixel) rispetto all'angolo superiore sinistro della tela. Anche l'origine dello slot è in alto a sinistra. | 
| `size` | Vec2 | Dimensioni dello slot, in pixel. L'impostazione di questo valore imposta anche `matchCanvasSize` su `false`. Default: (0, 0); tuttavia, perché `matchCanvasSize` di default è `true`, la dimensione renderizzata dello slot è la dimensione della tela, non (0, 0). | 
| `zIndex` | Float | Ordinamento relativo degli slot. Gli slot con valori `zIndex` più elevati sono disegnati sopra gli slot con valori `zIndex` minori. | 

## Creazione e configurazione di un dispositivo di immagini misto
<a name="broadcast-mixed-image-device-creating-configuring"></a>

![\[Configurazione di una sessione di tramissione per il mixaggio.\]](http://docs.aws.amazon.com/it_it/ivs/latest/RealTimeUserGuide/images/Broadcast_SDK_Mixer_Configuring.png)


Qui creiamo una scena simile a quella all'inizio di questa guida, con tre elementi sullo schermo:
+ Slot in basso a sinistra per una fotocamera.
+ Slot in basso a destra per una sovrapposizione del logo.
+ Slot in alto a destra per un filmato.

Nota che l'origine della tela è l'angolo in alto a sinistra e questa è la stessa per gli slot. Quindi, posizionando uno slot in (0, 0) sarà posizionato nell'angolo in alto a sinistra con l'intero slot visibile.

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

## Rimozione delle sorgenti
<a name="broadcast-mixed-devices-removing-sources"></a>

Per rimuovere una sorgente, chiamare `MixedDevice.remove` con l'oggetto di `Source` che desideri rimuovere.

## Animazioni con transizioni
<a name="broadcast-mixed-devices-animations-transitions"></a>

Il metodo di transizione sostituisce la configurazione di una sorgente con una nuova configurazione. Questa sostituzione può essere animata nel tempo impostando una durata superiore a 0, in secondi. 

### Quali proprietà possono essere animate?
<a name="broadcast-mixed-devices-animations-properties"></a>

Non tutte le proprietà nella struttura dello slot possono essere animate. Qualsiasi proprietà basata su tipi Float può essere animata; altre proprietà hanno effetto all'inizio o alla fine dell'animazione.


| Name | Può essere animato? | Punto d'impatto | 
| --- | --- | --- | 
| `Audio.gain` | Sì | Interpolato | 
| `Image.alpha` | Sì | Interpolato | 
| `Image.aspect` | No | End | 
| `Image.fillColor` | Sì | Interpolato | 
| `Image.position` | Sì | Interpolato | 
| `Image.size` | Sì | Interpolato | 
| `Image.zIndex` Nota: `zIndex` sposta i piani 2D attraverso lo spazio 3D, quindi la transizione avviene quando i due piani si incrociano in un certo punto al centro dell'animazione. Questo potrebbe essere calcolato, ma dipende dai valori di `zIndex` di inizio e di fine. Per una transizione più fluida, combinalo con `alpha`.  | Sì | Sconosciuto | 

### Esempi semplici
<a name="broadcast-mixed-devices-animations-examples"></a>

Di seguito sono riportati esempi di acquisizione di una fotocamera a schermo intero utilizzando la configurazione definita in alto in [Creazione e configurazione di un dispositivo di immagini misto](#broadcast-mixed-image-device-creating-configuring). Questo è animato per 0,5 secondi.

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

## Mirroring della trasmissione
<a name="broadcast-mixed-devices-mirroring"></a>


| Per eseguire il mirroring di un dispositivo immagine collegato nella trasmissione in questa direzione... | Usa un valore negativo per... | 
| --- | --- | 
| Orizzontalmente | La larghezza dello slot | 
| Verticalmente | L’altezza dello slot | 
| Sia orizzontalmente che verticalmente | Larghezza e altezza dello slot | 

La posizione dovrà essere regolata dello stesso valore per mettere lo slot nella posizione corretta quando viene eseguito il mirroring.

Di seguito sono riportati alcuni esempi di mirroring della trasmissione in orizzontale e in verticale.

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

Mirroring orizzontale:

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

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

Mirroring orizzontale:

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

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

Nota: questo mirroring è diverso dal metodo `setMirrored` su `ImagePreviewView` (Android) e `IVSImagePreviewView` (iOS). Questo metodo influisce solo sulla visualizzazione dell'anteprima locale sul dispositivo e non ha alcun impatto sulla trasmissione.

# SDK di trasmissione IVS: scambio di token \$1 Streaming in tempo reale
<a name="broadcast-mobile-token-exchange"></a>

Lo scambio di token consente di effettuare l'aggiornamento o il downgrade delle funzionalità dei token dei partecipanti e di aggiornare gli attributi dei token all'interno dell'SDK per la trasmissione per dispositivi mobili, senza richiedere ai partecipanti di riconnettersi. Questa funzione si rivela utile in scenari come il co-hosting, in cui i partecipanti possono iniziare con funzionalità riservate ai soli abbonati e solo in seguito avranno bisogno di accedere a funzionalità di pubblicazione.

Restrizioni:
+ Lo scambio di token funziona solo con i token creati sul server utilizzando una [coppia di chiavi](https://docs.aws.amazon.com//ivs/latest/RealTimeUserGuide/getting-started-distribute-tokens.html#getting-started-distribute-tokens-self-signed). Non funziona con i token creati tramite l'[API CreateParticipantToken.](https://docs.aws.amazon.com/ivs/latest/RealTimeAPIReference/API_CreateParticipantToken.html) 
+ Se utilizzi lo scambio di token per modificare gli attributi che determinano i layout di composizione lato server (ad esempio, featuredParticipantAttribute e participantOrderAttribute), il layout di una composizione attiva non verrà aggiornato finché il partecipante non si riconnette. 

## Scambio di token
<a name="broadcast-mobile-token-exchange-exchanging-tokens"></a>

Lo scambio di token è semplice: chiama l'API `exchangeToken` sull'oggetto `Stage` / `IVSStage` e indica il nuovo token. Se le `capabilities` del nuovo token sono diverse da quelle del token precedente, le funzionalità del nuovo token vengono valutate immediatamente. Ad esempio, se il token precedente non ne aveva la funzionalità di `publish`, mentre il nuovo token ne è dotata, vengono invocate le funzioni della strategia della fase per la pubblicazione, che consentono all'applicazione host di decidere se pubblicare subito con la nuova funzionalità o attendere. Lo stesso vale per le funzionalità eliminate: se il token precedente aveva la funzionalità di `publish`, mentre il nuovo token non ne è dotata, il partecipante annulla immediatamente la pubblicazione senza invocare le funzioni della strategia della fase per la pubblicazione.

Quando si scambia un token, il token precedente e quello nuovo devono avere gli stessi valori per i seguenti campi di payload: 
+ `topic`
+ `resource`
+ `jti`
+ `whip_url`
+ `events_url`

Questi campi non sono modificabili. Lo scambio di un token che modifica un campo non modificabile comporta il rifiuto immediato dello scambio da parte dell'SDK.

Sarà invece possibile modificare i campi rimanenti, tra i quali:
+ `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)
```

## Ricezione degli aggiornamenti
<a name="broadcast-mobile-token-exchange-receiving-updates"></a>

Una funzione in `StageRenderer` / `IVSStageRenderer` riceve aggiornamenti sugli elementi già pubblicati dai partecipanti remoti che si scambiano i token per aggiornare i propri `userId` o i propri `attributes`. Se i partecipanti remoti non hanno già pubblicato elementi, i rispettivi `userId` e `attributes` verranno esposti tramite le funzioni`onParticipantJoined` / `participantDidJoin` esistenti del renderer solo se alla fine effettuano la pubblicazione.

### 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à degli aggiornamenti
<a name="broadcast-mobile-token-exchange-visibility"></a>

Quando un partecipante scambia un token per aggiornare il proprio `userId` o i propri `attributes`, la visibilità di queste modifiche dipende dallo stato di pubblicazione corrente: 
+ **Se il partecipante *non ha pubblicato elementi*:** l'aggiornamento viene elaborato in modo invisibile. Se alla fine effettua la pubblicazione, tutti gli SDK riceveranno gli `userId` e gli `attributes` aggiornati nell'ambito dell'evento di pubblicazione iniziale.
+ **Se il partecipante *ha già pubblicato elementi*:** l'aggiornamento viene trasmesso immediatamente. Tuttavia, solo gli SDK per dispositivi mobili v1.37.0\$1 ricevono la notifica. I partecipanti con l'SDK per la versione web, gli SDK per dispositivi mobili precedenti e la composizione lato server non vedono la modifica finché il partecipante non annulla la pubblicazione e pubblica nuovamente l'elemento.

Questa tabella illustra la matrice di supporto:


| Stato del partecipante | Osservatore: SDK per dispositivi mobili v1.37.0\$1 | Osservatore: SDK per dispositivi mobili precedenti SDK per versione web, composizione lato server | 
| --- | --- | --- | 
| Non pubblica elementi (processo avviato) | ✅ Visibile (in caso di pubblicazione tramite un evento a cui il partecipante ha preso parte) | ✅ Visibile (in caso di pubblicazione tramite un evento a cui il partecipante ha preso parte) | 
| Ha pubblicato elementi (nessuna nuova pubblicazione) | ✅ Visibile (immediatamente tramite l'evento aggiornato dei metadati dei partecipanti) | ❌ Non visibile | 
| Ha pubblicato elementi (annullamento e nuova pubblicazione) | ✅ Visibile (immediatamente tramite l'evento aggiornato dei metadati dei partecipanti) | ⚠️ Visibilità condizionata (solo in caso di nuova pubblicazione tramite un evento a cui il partecipante ha preso parte) | 

# SDK di trasmissione IVS: origini di immagini personalizzate I Streaming in tempo reale
<a name="broadcast-custom-image-sources"></a>

Le origini di input di immagini personalizzate consentono a un'applicazione di fornire il proprio input di immagini all'SDK di trasmissione anziché limitarsi alle fotocamere preimpostate. Una origine di immagine personalizzata può essere semplice come una filigrana semitrasparente o una scena statica "torno subito" oppure può consentire all'app di eseguire ulteriori elaborazioni personalizzate come l'aggiunta di filtri di bellezza alla fotocamera.

Quando si utilizza una sorgente di input di immagine personalizzata per il controllo personalizzato della fotocamera (ad esempio l'utilizzo di librerie di filtri estetici che richiedono l'accesso alla fotocamera), l'SDK di trasmissione non è più responsabile della gestione della fotocamera. Invece, l'applicazione è responsabile della corretta gestione del ciclo di vita della fotocamera. Consulta la documentazione ufficiale della piattaforma su come la tua applicazione dovrebbe gestire la fotocamera.

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

Dopo aver creato una sessione `DeviceDiscovery`, crea un'origine di input di immagine:

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

Questo metodo restituisce un `CustomImageSource`, che è una sorgente immagine supportata da un [Surface](https://developer.android.com/reference/android/view/Surface) Android standard. La classe secondaria `SurfaceSource` può essere ridimensionata e ruotata. Puoi inoltre creare un `ImagePreviewView` per visualizzare un'anteprima del contenuto.

Per recuperare il sottostante `Surface`:

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

Questo `Surface` può essere utilizzato come buffer di output per producer di immagini come Camera2, OpenGL ES e altre librerie. Il caso d'uso più semplice è disegnare direttamente una bitmap statica o un colore sulla tela di Surface. Tuttavia, molte librerie (come le librerie di filtri estetici) forniscono un metodo che consente a un'applicazione di specificare un `Surface` esterno per il rendering. È possibile utilizzare un metodo del genere per passare questo `Surface` alla libreria di filtri, il che consente alla libreria di emettere frame elaborati per lo streaming della sessione di trasmissione.

Questa `CustomImageSource` può essere avvolta in un `LocalStageStream` e restituito dal `StageStrategy` per la pubblicazione su un `Stage`.

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

Dopo aver creato una sessione `DeviceDiscovery`, crea un'origine di input di immagine:

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

Questo metodo restituisce un `IVSCustomImageSource`, che è una fonte di immagini che consente alla domanda di inviare `CMSampleBuffers` manualmente. Per i formati pixel supportati, consulta Riferimento all'SDK di trasmissione iOS; un collegamento alla versione più recente è presente nel manuale [Note di rilascio di Amazon IVS](release-notes.md) per l'ultima versione dell'SDK di trasmissione.

I campioni inviati all'origine personalizzata verranno trasmessi alla fase:

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

Per lo streaming di video, utilizzare questo metodo in una richiamata. Ad esempio, se si utilizza la fotocamera, ogni volta che viene ricevuto un nuovo buffer campione da un `AVCaptureSession`, l'applicazione può inoltrare il buffer campione alla sorgente di immagine personalizzata. Se lo desideri, l'applicazione può applicare ulteriori elaborazioni (come un filtro di bellezza) prima di inviare il campione alla sorgente di immagine personalizzata.

La `IVSCustomImageSource` può essere avvolta in un `IVSLocalStageStream` e restituito dal `IVSStageStrategy` per la pubblicazione su un `Stage`.

# SDK di trasmissione IVS: sorgenti audio personalizzate \$1 Streaming in tempo reale
<a name="broadcast-custom-audio-sources"></a>

**Nota:** questa guida è valida solo per l'SDK di trasmissione Android per lo streaming in tempo reale IVS. Le informazioni relative agli SDK per le versioni web e iOS verranno pubblicate in futuro.

Le sorgenti audio personalizzate consentono a un'applicazione di fornire il proprio input audio all'SDK di trasmissione, anziché limitarsi al microfono integrato del dispositivo. Una sorgente audio personalizzata consente alle applicazioni di trasmettere contenuti audio elaborati con effetti, mixare più flussi audio o effettuare l'integrazione con librerie di elaborazione audio di terzi.

Quando utilizzi una sorgente audio personalizzata, l'SDK di trasmissione non sarà più direttamente responsabile della gestione del microfono. L'applicazione sarà invece responsabile dell'acquisizione, dell'elaborazione e dell'invio dei dati audio alla sorgente personalizzata.

Il flusso di lavoro relativo alla sorgente audio personalizzata segue i passaggi descritti di seguito:

1. Input audio: crea una sorgente audio personalizzata con un formato audio specificato (frequenza di campionamento, canali, formato). 

1. Elaborazione: acquisisci o genera dati audio dalla pipeline di elaborazione audio.

1. Sorgente audio personalizzata: invia i buffer audio alla sorgente personalizzata utilizzando `appendBuffer()`.

1. Fase: completa il `LocalStageStream` e pubblicalo nella fase utilizzando la `StageStrategy`. 

1. Partecipanti: i partecipanti alla fase ricevono l'audio elaborato in tempo reale.

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

### Creazione di una sorgente audio personalizzata
<a name="custom-audio-sources-android-creating-a-custom-audio-source"></a>

Dopo aver creato una sessione di `DeviceDiscovery`, crea un input per una sorgente audio personalizzata:

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

Questo metodo restituisce una `CustomAudioSource`, che accetta dati audio PCM non elaborati. La sorgente audio personalizzata deve essere configurata con lo stesso formato audio prodotto dalla pipeline di elaborazione audio.

#### Formati audio supportati
<a name="custom-audio-sources-android-submitting-audio-data-supportedi-audio-formats"></a>


| Parametro | Opzioni | Descrizione | 
| --- | --- | --- | 
| Canali | 1 (mono), 2 (stereo) | Numero di canali audio. | 
| Frequenza di campionamento | RATE\$116000, RATE\$144100, RATE\$148000 | Frequenza di campionamento audio in Hz. Si consiglia di selezionare 48 kHz per una qualità elevata. | 
| Formato | INT16, FLOAT32 | Formato audio di esempio. INT16 è un PCM a virgola fissa a 16 bit, FLOAT32 è un PCM a virgola mobile a 32 bit. Sono disponibili sia i formati interlacciati che quelli planari. | 

### Invio dei dati audio
<a name="custom-audio-sources-android-submitting-audio-data"></a>

Per inviare dati audio alla sorgente personalizzata, utilizza il metodo `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();
```

**Considerazioni importanti:**
+ I dati audio devono essere nel formato specificato durante la creazione della sorgente personalizzata.
+ I timestamp devono aumentare in modo monotono e devono essere forniti dalla sorgente audio per una riproduzione audio fluida.
+ Invia l'audio regolarmente per evitare interruzioni nello streaming.
+ Il metodo restituisce il numero di campioni elaborati (0 indica un errore). 

### Pubblicazione in una fase
<a name="custom-audio-sources-android-publishing-to-a-stage"></a>

Completa la `CustomAudioSource` in uno `AudioLocalStageStream` e restituisci questo elemento dalla `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);
```

### Esempio completo: integrazione dell'elaborazione audio
<a name="custom-audio-sources-android-complete-example"></a>

Ecco un esempio completo che mostra l'integrazione con un SDK di elaborazione 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(); 
      } 
   } 
}
```

### Best practice
<a name="custom-audio-sources-android-best-practices"></a>

#### Uniformità del formato audio
<a name="custom-audio-sources-android-best-practices-audio-format-consistency"></a>

Assicurati che il formato audio inviato corrisponda al formato specificato durante la creazione della sorgente personalizzata:

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

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

Utilizzali i `ByteBuffers` diretti e riutilizzali per ridurre al minimo la rimozione di oggetti inutili: 

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

#### Tempistica e sincronizzazione
<a name="custom-audio-sources-android-best-practices-timing-and-synchronization"></a>

È necessario utilizzare i timestamp forniti dalla sorgente audio per una riproduzione audio fluida. Se la sorgente audio non fornisce un timestamp, crea il timestamp epoch e calcola manualmente la durata tra un campione e l'altro utilizzando il numero di fotogrammi e la dimensione dei fotogrammi. 

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

#### Gestione errori
<a name="custom-audio-sources-android-best-practices-error-handling"></a>

Controlla sempre il valore restituito da `appendBuffer()`: 

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

# SDK di trasmissione IVS: filtri di fotocamere di terze parti \$1 Streaming in tempo reale
<a name="broadcast-3p-camera-filters"></a>

Questa guida presuppone che tu abbia già familiarità con le origini di [immagini personalizzate](broadcast-custom-image-sources.md) e con l'integrazione dell'[SDK di trasmissione in streaming in tempo reale IVS](broadcast.md) nella tua applicazione.

I filtri della fotocamera consentono ai creatori di streaming live di aumentare o modificare l'aspetto del viso o dello sfondo. Ciò può potenzialmente aumentare il coinvolgimento degli spettatori, attirare gli spettatori e migliorare l'esperienza di streaming live.

# Integrazione di filtri di fotocamere di terze parti
<a name="broadcast-3p-camera-filters-integrating"></a>

Puoi integrare gli SDK dei filtri di fotocamere di terze parti con l'SDK di trasmissione IVS inviando l'output dell'SDK del filtro a una [sorgente di input di immagini personalizzata.](broadcast-custom-image-sources.md) Le origini di input di immagini personalizzate consentono a un'applicazione di fornire il proprio input di immagini all'SDK di trasmissione anziché limitarsi alle fotocamere preimpostate. L'SDK di un fornitore di filtri di terze parti può gestire il ciclo di vita della fotocamera per elaborare le immagini dalla fotocamera, applicare un effetto di filtro e inviarle in un formato che può essere passato a una sorgente di immagini personalizzata.

![\[Integrazione di SDK dei filtri di fotocamere di terze parti con l'SDK di trasmissione IVS inviando l'output dell'SDK del filtro a una sorgente di input di immagini personalizzata.\]](http://docs.aws.amazon.com/it_it/ivs/latest/RealTimeUserGuide/images/3P_Camera_Filters_Integrating.png)


Consulta la documentazione del tuo fornitore di filtri di terze parti per conoscere i metodi integrati per convertire un frame di una fotocamera, con l'effetto filtro, applicato a un formato che può essere passato a una [sorgente di input di immagini personalizzata.](broadcast-custom-image-sources.md) Il processo varia a seconda della versione dell'SDK di trasmissione IVS utilizzata:
+ **Web**: il fornitore del filtro deve essere in grado di eseguire il rendering del proprio output su un elemento dell'area di lavoro. Il metodo [captureStream](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/captureStream) può quindi essere utilizzato per restituire un MediaStream dei contenuti dell'area di lavoro. MediaStream può quindi essere convertito in un'istanza di [LocalStageStream](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/classes/LocalStageStream) e pubblicato su una fase.
+ **Android**: l'SDK del fornitore del filtro può eseguire il rendering di un frame su un dispositivo Android `Surface` fornito dall'SDK di trasmissione IVS o convertire il frame in una bitmap. Se si utilizza una bitmap, è possibile renderizzarla sul `Surface` sottostante fornito dalla sorgente dell'immagine personalizzata, sbloccandola e scrivendola nell'area di lavoro.
+ **iOS**: l'SDK di un fornitore di filtri di terze parti deve fornire un frame della fotocamera con un effetto filtro applicato come `CMSampleBuffer`. Per informazioni su come ottenere un `CMSampleBuffer` come risultato finale dopo l'elaborazione dell'immagine di una fotocamera, consulta la documentazione dell'SDK del fornitore di filtri di terze parti.

# Utilizzo di BytePlus con l'SDK di trasmissione IVS
<a name="broadcast-3p-camera-filters-integrating-byteplus"></a>

Questo documento spiega come utilizzare l'SDK BytePlus Effects con l'SDK di trasmissione IVS.

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

### Installazione e configurazione dell'SDK BytePlus Effects
<a name="integrating-byteplus-android-install-effects-sdk"></a>

Consulta la [Guida per l'accesso di Android](https://docs.byteplus.com/en/effects/docs/android-v4101-access-guide) di BytePlus per i dettagli su come installare, inizializzare e configurare l'SDK BytePlus Effects.

### Configurazione della sorgente di immagini personalizzata
<a name="integrating-byteplus-android-setup-image-source"></a>

Dopo aver inizializzato l'SDK, alimenta i fotogrammi della fotocamera elaborati con un effetto filtro applicato a una sorgente di input di immagini personalizzata. A tale scopo, crea un'istanza di un oggetto `DeviceDiscovery` e crea una sorgente di immagini personalizzata. Quando si utilizza una sorgente di input di immagini personalizzate per il controllo personalizzato della fotocamera, l'SDK di trasmissione non è più responsabile della gestione della fotocamera. Invece, l'applicazione è responsabile della corretta gestione del ciclo di vita della fotocamera.

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

### Converti l'output in una bitmap e lo invia a una sorgente di input di immagini personalizzata
<a name="integrating-byteplus-android-convert-to-bitmap"></a>

Per consentire ai fotogrammi della fotocamera con un effetto filtro applicato dall'SDK BytePlus Effects di essere inoltrati direttamente all'SDK di trasmissione IVS, converti l'output di una texture dell'SDK di BytePlus Effects in una bitmap. Quando un'immagine viene elaborata, il metodo `onDrawFrame()` viene richiamato dall'SDK. Il metodo `onDrawFrame()` è un metodo pubblico dell'interfaccia [GLSurfaceView.Renderer](https://developer.android.com/reference/android/opengl/GLSurfaceView.Renderer) di Android. Nell'app di esempio per Android fornita da BytePlus, questo metodo viene richiamato su ogni fotogramma della fotocamera e genera una texture. Allo stesso tempo, puoi integrare il metodo `onDrawFrame()` con la logica per convertire questa texture in una bitmap e inviarla a una sorgente di input di immagini personalizzata. Come illustrato nel seguente esempio di codice, utilizza il metodo `transferTextureToBitmap` fornito dall'SDK BytePlus per eseguire questa conversione. Questo metodo è fornito dalla libreria [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) dell'SDK BytePlus Effects, come illustrato nel seguente esempio di codice. È quindi possibile eseguire il rendering sull'Android `Surface` di un `CustomImageSource` sottostante scrivendo la bitmap risultante nell'area di lavoro di Surface. Numerose invocazioni successive dei risultati `onDrawFrame()` in una sequenza di bitmap e, se combinate, creano un flusso video.

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

# Utilizzo di DeepAR con l'SDK di trasmissione IVS
<a name="broadcast-3p-camera-filters-integrating-deepar"></a>

Questo documento spiega come utilizzare l'SDK DeepAR con l'SDK di trasmissione IVS.

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

Consulta la [Guida all'integrazione Android di DeepAR](https://docs.deepar.ai/deepar-sdk/integrations/video-calling/amazon-ivs/android/) per i dettagli su come integrare l'SDK DeepAR con l'SDK di trasmissione IVS Android.

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

Consulta la [Guida all'integrazione iOS di DeepAR](https://docs.deepar.ai/deepar-sdk/integrations/video-calling/amazon-ivs/ios/) per i dettagli su come integrare l'SDK DeepAR con l'SDK di trasmissione IVS iOS.

# Utilizzo di Snap con l'SDK di trasmissione IVS
<a name="broadcast-3p-camera-filters-integrating-snap"></a>

Questo documento spiega come utilizzare l'SDK Camera Kit di Snap con l'SDK di trasmissione IVS.

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

Questa sezione presuppone che tu abbia già dimestichezza con la [pubblicazione e la sottoscrizione di video utilizzando l'SDK di trasmissione Web](getting-started-pub-sub-web.md).

Per integrare l'SDK Camera Kit di Snap con l'SDK di trasmissione Web per lo streaming in tempo reale IVS, è necessario:

1. Installazione dell'SDK Camera Kit e Webpack. (Il nostro esempio utilizza Webpack come bundler, ma puoi utilizzare qualsiasi bundler di tua scelta.)

1. Crea `index.html`.

1. Aggiungi gli elementi di configurazione.

1. Crea `index.css`.

1. Visualizza e configura i partecipanti.

1. Visualizza le fotocamere e i microfoni collegati.

1. Crea una sessione Camera Kit.

1. Recupera gli obiettivi e compila il selettore degli obiettivi.

1. Trasforma l'output da una sessione di Camera Kit in un'area di lavoro.

1. Crea una funzione per compilare il menu a discesa degli obiettivi.

1. Fornisci a Camera Kit una origine multimediale per il rendering e la pubblicazione di un `LocalStageStream`.

1. Crea `package.json`.

1. Crea un file di configurazione di Webpack.

1. Configura un server HTTPS ed esegui il test.

Di seguito è riportata una descrizione di ciascuna di queste fasi.

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

In questo esempio utilizziamo come bundler Webpack, ma è possibile utilizzare qualsiasi tipo di bundler.

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

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

Quindi, create il boilerplate HTML e importa l'SDK di trasmissione Web come tag di script. Nel codice seguente, assicurati di sostituire `<SDK version>` con la versione dell'SDK di trasmissione che stai utilizzando.

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

### Aggiunta degli elementi di configurazione
<a name="integrating-snap-web-add-setup-elements"></a>

Crea il codice HTML per selezionare una fotocamera, un microfono e un obiettivo e specificare un token del partecipante:

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

Aggiungi codice HTML aggiuntivo al di sotto per visualizzare i feed delle fotocamere dei partecipanti locali e remoti:

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

Carica la logica aggiuntiva, inclusi i metodi helper per configurare la fotocamera e il file JavaScript in bundle. (Più avanti in questa sezione, creerai questi file JavaScript e li raggrupperai in un unico file, in modo da poter importare Camera Kit come modulo. Il file JavaScript fornito in bundle conterrà la logica per configurare Camera Kit, applicare un obiettivo e pubblicare il feed della fotocamera con un obiettivo applicato a una fase.) Aggiungi i tag di chiusura per gli elementi `body` e `html` per completare la creazione di `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>
```

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

Crea un file sorgente CSS per definire lo stile della pagina. Non esamineremo questo codice per concentrarci sulla logica per la gestione di uno Stage e l'integrazione con l'SDK Camera Kit di 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;
}
```

### Visualizzazione e configurazione dei partecipanti
<a name="integrating-snap-web-setup-participants"></a>

Successivamente, crea `helpers.js`, che contiene i metodi helper che utilizzerai per visualizzare e configurare i partecipanti:

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

### Visualizzazione delle fotocamere e i microfoni collegati
<a name="integrating-snap-web-display-cameras-microphones"></a>

Successivamente, crea `media-devices.js`, che contiene metodi helper per la visualizzazione di fotocamere e microfoni collegati al dispositivo:

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

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

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

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

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

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

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

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

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

  return { videoDevices, audioDevices };
}

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

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

### Creazione di una sessione Camera Kit
<a name="integrating-snap-web-camera-kit-session"></a>

Crea `stages.js`, che contiene la logica per applicare un obiettivo al feed della fotocamera e pubblicare il feed in una fase. Consigliamo di copiare e incollare il seguente blocco di codice in `stages.js`. Quindi, puoi rivedere ogni componente del codice per comprendere cosa accade nelle sezioni successive.

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

Nella prima parte di questo file, importiamo l'SDK di trasmissione e l'SDK Web di Camera Kit e inizializziamo le variabili che utilizzeremo con ciascun SDK. Creiamo una sessione Camera Kit chiamando `createSession` dopo aver [avviato l'SDK Web di Camera Kit](https://kit.snapchat.com/reference/CameraKit/web/0.7.0/index.html#bootstrapping-the-sdk). Tieni presente che un oggetto elemento dell'area di lavoro viene passato a una sessione; ciò indica a Camera Kit di renderizzare in quell'area di lavoro.

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

### Recupero degli obiettivi e compilazione del selettore degli obiettivi
<a name="integrating-snap-web-fetch-apply-lens"></a>

Per recuperare gli obiettivi, sostituisci il segnaposto dell'ID gruppo obiettivi con il tuo, che puoi trovare nel [Portale per gli sviluppatori di Camera Kit](https://camera-kit.snapchat.com/). Compila il menu a discesa di selezione degli obiettivi usando la funzione `populateLensSelector()` che creeremo in seguito.

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

### Trasforma l'output da una sessione di Camera Kit in un'area di lavoro
<a name="integrating-snap-web-render-output-to-canvas"></a>

Utilizza il metodo [captureStream](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/captureStream) per restituire un `MediaStream` dei contenuti dell'area di lavoro. L'area di lavoro conterrà un flusso video del feed della fotocamera con un obiettivo applicato. Inoltre, aggiungi listener di eventi per i pulsanti per disattivare l'audio della fotocamera e del microfono, nonché listener di eventi per entrare e uscire da una fase. Nel listener di eventi per entrare a far parte di una fase, passiamo a una sessione di Camera Kit e al `MediaStream` dall'area di lavoro in modo che possa essere pubblicata in una fase.

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

### Creazione di una funzione per compilare il menu a discesa degli obiettivi
<a name="integrating-snap-web-populate-lens-dropdown"></a>

Crea la seguente funzione per popolare il selettore **Obiettivo** con gli obiettivi recuperati in precedenza. Il selettore **Obiettivo** è un elemento dell'interfaccia utente della pagina che consente di selezionare da un elenco di obiettivi da applicare al feed della fotocamera. Inoltre, crea la funzione di callback `handleLensChange` per applicare l'obiettivo specificato quando viene selezionato dal menu a discesa **Obiettivo**.

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

### Fornire a Camera Kit un'origine multimediale per il rendering e un LocalStageStream
<a name="integrating-snap-web-publish-localstagestream"></a>

Per pubblicare un flusso video con un obiettivo applicato, crea una funzione chiamata `setCameraKitSource` da trasmettere nel `MediaStream` acquisito in precedenza dall'area di lavoro. Il `MediaStream` dall'area di lavoro non sta facendo nulla al momento perché non abbiamo ancora incorporato il feed della nostra fotcamera locale. Possiamo incorporare il nostro feed locale della fotocamera chiamando il metodo helper `getCamera` e assegnandolo a `localCamera`. Possiamo quindi passare il feed della fotocamera locale (via `localCamera`) e l'oggetto della sessione a `setCameraKitSource`. La funzione `setCameraKitSource` converte il feed locale della fotocamera in una [sorgente di contenuti multimediali per CameraKit](https://docs.snap.com/camera-kit/integrate-sdk/web/web-configuration#creating-a-camerakitsource) chiamando `createMediaStreamSource`. La sorgente multimediale di `CameraKit` viene quindi [trasformata](https://docs.snap.com/camera-kit/integrate-sdk/web/web-configuration#2d-transforms) per rispecchiare la fotocamera frontale. L'effetto Obiettivo viene quindi applicato alla sorgente multimediale e renderizzato nell'area di lavoro output mediante una chiamata a `session.play()`.

Con l'obiettivo ora applicato all'immagine `MediaStream` catturata dall'area di lavoro, possiamo quindi procedere alla pubblicazione in una fase. Ciò è possibile creando un `LocalStageStream` con le tracce video tratte da `MediaStream`. Un'istanza di `LocalStageStream` può quindi essere passata a un `StageStrategy` per essere pubblicata.

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

Il codice rimanente riportato di seguito serve per creare e gestire la nostra fase:

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

### Creazione di package.json
<a name="integrating-snap-web-package-json"></a>

Crea `package.json` e aggiungi la seguente configurazione JSON. Questo file definisce le nostre dipendenze e include un comando di script per creare il bundle del codice.

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

### Creazione di un file di configurazione di Webpack.
<a name="integrating-snap-web-webpack-config"></a>

Crea `webpack.config.js` e aggiungi il seguente codice. Questo raggruppa il codice che abbiamo creato finora in modo da poter utilizzare l'istruzione di importazione per utilizzare 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'),
  },
};
```

Infine, esegui `npm run build` per raggruppare il tuo JavaScript come definito nel file di configurazione di Webpack. A fini di test, potrai quindi distribuire HTML e JavaScript dal computer locale. In questo esempio, utilizziamo il modulo `http.server` di Python. 

### Configurazione di un server HTTPS e test
<a name="integrating-snap-web-https-server-test"></a>

Per testare il codice è necessario configurare un server HTTPS. L'utilizzo di un server HTTPS per lo sviluppo locale e il test dell'integrazione della tua app web con l'SDK di Snap Camera Kit ti aiuterà a evitare problemi di CORS (Cross-Origin Resource Sharing).

Apri un terminale e vai alla directory in cui hai creato tutto il codice fino a questo punto. Esegui il seguente comando per generare un certificato SSL/TLS autofirmato e una chiave privata:

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

Questo crea due file: `key.pem` (la chiave privata) e `cert.pem` (il certificato autofirmato). Crea un nuovo file Python denominato `https_server.py` e aggiungi il seguente codice:

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

Apri un terminale, vai alla directory in cui hai creato il file `https_server.py` ed esegui il seguente comando:

```
python3 https_server.py
```

Questo avvia il server HTTPS su https://localhost:4443, che serve i file dalla directory corrente. Assicurati che i file `cert.pem` e `key.pem` si trovino nella stessa directory del file `https_server.py`.

Apri il browser e vai a https://localhost:4443. Poiché si tratta di un certificato SSL/TLS autofirmato, non sarà considerato attendibile dal tuo browser web, quindi riceverai un avviso. Poiché è solo a scopo di test, puoi ignorare l'avviso. Dovresti quindi vedere l'effetto AR per lo Snap Lens che hai specificato in precedenza applicato al feed della fotocamera sullo schermo.

Nota che questa configurazione che utilizza i moduli integrati `http.server` e `ssl` di Python è adatta per scopi di sviluppo e test locali, ma non è consigliata per un ambiente di produzione. Il certificato SSL/TLS autofirmato utilizzato in questa configurazione non è considerato affidabile dai browser web e da altri client, pertanto gli utenti riceveranno avvisi di sicurezza quando accedono al server. Inoltre, sebbene in questo esempio utilizziamo i moduli http.server e ssl integrati di Python, puoi scegliere di utilizzare un'altra soluzione server HTTPS.

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

Per integrare l'SDK Camera Kit di Snap con l'SDK di trasmissione Android IVS, devi installare l'SDK Camera Kit, inizializzare una sessione Camera Kit, applicare un obiettivo e inviare l'output della sessione Camera Kit alla sorgente di input dell'immagine personalizzata.

Per installare l'SDK Camera Kit, aggiungi quanto segue al file `build.gradle` del modulo. Sostituisci `$cameraKitVersion` con l'[ultima versione dell'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"
```

Inizializza e ottieni un `cameraKitSession`. Camera Kit fornisce anche un comodo wrapper per le API [CameraX](https://developer.android.com/media/camera/camerax) di Android, quindi non è necessario scrivere una logica complicata per utilizzare CameraX con Camera Kit. È possibile utilizzare l'oggetto `CameraXImageProcessorSource` come [sorgente](https://snapchat.github.io/camera-kit-reference/api/android/latest/-camera-kit/com.snap.camerakit/-source/index.html) per [ImageProcessor](https://snapchat.github.io/camera-kit-reference/api/android/latest/-camera-kit/com.snap.camerakit/-image-processor/index.html), il che consente di avviare lo streaming di frame in anteprima dalla fotocamera.

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

### Recupero e applicazione di un obiettivo
<a name="integrating-snap-android-fetch-apply-lenses"></a>

Puoi configurare gli obiettivi e il loro ordine nel carosello del [Portale per sviluppatori di 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);
      }));
});
```

Per la trasmissione, invia i fotogrammi elaborati al `Surface` di base di una sorgente di immagini personalizzate. Usa un oggetto `DeviceDiscovery` e crea un oggetto `CustomImageSource` per restituire un `SurfaceSource`. È quindi possibile eseguire il rendering dell'output da una sessione `CameraKit` al `Surface` sottostante fornito da `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
```

# Utilizzo della sostituzione dello sfondo con l'SDK di trasmissione IVS
<a name="broadcast-3p-camera-filters-background-replacement"></a>

La sostituzione dello sfondo è un tipo di filtro della fotocamera che consente ai creatori di streaming live di modificare lo sfondo. Come illustrato nel seguente diagramma, la sostituzione dello sfondo comporta:

1. L'ottenimento di un'immagine della fotocamera dal feed live della fotocamera.

1. La segmentazione in componenti di primo piano e di secondo piano utilizzando Google ML Kit.

1. La combinazione della maschera di segmentazione risultante con un'immagine di sfondo personalizzata.

1. L'invio a una sorgente di immagini personalizzate per la trasmissione.

![\[Il flusso di lavoro per l'implementazione della sostituzione dello sfondo.\]](http://docs.aws.amazon.com/it_it/ivs/latest/RealTimeUserGuide/images/3P_Camera_Filters_Background_Replacement.png)


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

Questa sezione presuppone che tu abbia già dimestichezza con la [pubblicazione e la sottoscrizione di video utilizzando l'SDK di trasmissione Web](https://docs.aws.amazon.com//ivs/latest/RealTimeUserGuide/getting-started-pub-sub-web.html).

Per sostituire lo sfondo di uno streaming live con un'immagine personalizzata, utilizza il [modello di segmentazione dei selfie](https://developers.google.com/mediapipe/solutions/vision/image_segmenter#selfie-model) con [MediaPipe Image Segmenter](https://developers.google.com/mediapipe/solutions/vision/image_segmenter). Si tratta di un modello di machine learning che identifica quali pixel del fotogramma video sono in primo piano o sullo sfondo. Puoi quindi utilizzare i risultati del modello per sostituire lo sfondo di uno streaming live, copiando i pixel in primo piano dal feed video in un'immagine personalizzata che rappresenta il nuovo sfondo.

Per integrare la sostituzione dello sfondo con l'SDK di trasmissione Web per lo streaming in tempo reale IVS, è necessario:

1. Installare MediaPipe e Webpack. (Il nostro esempio utilizza Webpack come bundler, ma puoi utilizzare qualsiasi bundler di tua scelta.)

1. Crea `index.html`.

1. Aggiungere elementi multimediali.

1. Aggiungere un tag di script.

1. Crea `app.js`.

1. Caricare un'immagine di sfondo personalizzata.

1. Creare un'istanza di `ImageSegmenter`.

1. Eseguire il rendering del feed video su un'area di lavoro.

1. Creare la una logica di sostituzione dello sfondo.

1. Creare un file di configurazione di Webpack.

1. Raggruppare il file JavaScript.

### Installazione di MediaPipe e Webpack
<a name="background-replacement-web-install-mediapipe-webpack"></a>

Per iniziare, installa i pacchetti npm `@mediapipe/tasks-vision` e `webpack`. L'esempio seguente utilizza Webpack come bundler JavaScript; se preferisci, puoi usare un bundler diverso.

#### JavaScript
<a name="background-replacement-web-install-mediapipe-webpack-code"></a>

```
npm i @mediapipe/tasks-vision webpack webpack-cli
```

Assicurati di aggiornare anche il tuo `package.json` per specificare `webpack` come script di compilazione:

#### JavaScript
<a name="background-replacement-web-update-package-json-code"></a>

```
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack"
  },
```

### Creazione di index.html
<a name="background-replacement-web-create-index"></a>

Quindi, create il boilerplate HTML e importa l'SDK di trasmissione Web come tag di script. Nel codice seguente, assicurati di sostituire `<SDK version>` con la versione dell'SDK di trasmissione che stai utilizzando.

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

### Aggiunta di elementi multimediali
<a name="background-replacement-web-add-media-elements"></a>

Quindi, aggiungi un elemento video e due elementi dell'area di lavoro all'interno del tag body. L'elemento video conterrà il feed live della fotocamera e sarà utilizzato come input per MediaPipe Image Segmenter. Il primo elemento dell'area di lavoro verrà utilizzato per renderizzare un'anteprima del feed che verrà trasmesso. Il secondo elemento dell'area di lavoro verrà utilizzato per renderizzare l'immagine personalizzata che verrà utilizzata come sfondo. Poiché la seconda area di lavoro con l'immagine personalizzata viene utilizzata solo come sorgente per copiare a livello di codice i pixel da essa all'area di lavoro finale, è nascosta alla vista.

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

### Aggiunta di un tag di script
<a name="background-replacement-web-add-script-tag"></a>

Aggiungi un tag di script per caricare un file JavaScript in bundle che conterrà il codice per eseguire la sostituzione dello sfondo e pubblicarlo su una fase:

```
<script src="./dist/bundle.js"></script>
```

### Crea app.js
<a name="background-replacement-web-create-appjs"></a>

Quindi, crea un file JavaScript per ottenere gli oggetti elemento per gli elementi video e dell'area di lavoro creati nella pagina HTML. Importa i moduli `ImageSegmenter` e `FilesetResolver`. Il modulo `ImageSegmenter` verrà utilizzato per eseguire l'attività di segmentazione.

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

Quindi, crea una funzione chiamata `init()` a recuperare il MediaStream dalla fotocamera dell'utente e richiama una funzione di callback ogni volta che un frame della fotocamera termina il caricamento. Aggiungi i listener di eventi per i pulsanti per entrare e uscire da una fase.

Tieni presente che quando si entra in una fase, viene passata una variabile denominata `segmentationStream`. Si tratta di un flusso video catturato da un elemento dell'area di lavoro contenente un'immagine in primo piano sovrapposta all'immagine personalizzata che rappresenta lo sfondo. Successivamente, questo flusso personalizzato verrà utilizzato per creare un'istanza di un `LocalStageStream`, che può essere pubblicata in una fase.

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

### Caricamento di un'immagine di sfondo personalizzata
<a name="background-replacement-web-background-image"></a>

Nella parte inferiore della funzione `init`, aggiungi il codice per chiamare una funzione denominata `initBackgroundCanvas`, che carica un'immagine personalizzata da un file locale e la rende su un'area di lavoro. Definiremo questa funzione nel passaggio successivo. Assegna il file `MediaStream` recuperato dalla fotocamera dell'utente all'oggetto video. Successivamente, questo oggetto video verrà passato a Image Segmenter. Inoltre, imposta una funzione denominata `renderVideoToCanvas` come funzione di callback da richiamare ogni volta che un fotogramma video ha terminato il caricamento. Definiremo questa funzione in un passaggio successivo.

#### JavaScript
<a name="background-replacement-web-load-background-image-code"></a>

```
initBackgroundCanvas();

  video.srcObject = localCamera;
  video.addEventListener("loadeddata", renderVideoToCanvas);
```

Implementiamo la funzione `initBackgroundCanvas`, che carica un'immagine da un file locale. In questo esempio, viene utilizzata l'immagine di una spiaggia come sfondo personalizzato. L'area di lavoro contenente l'immagine personalizzata verrà nascosta alla visualizzazione, poiché la unirai ai pixel di primo piano dell'elemento dell'area di lavoro contenente il feed della fotocamera.

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

### Creazione di un'istanza di ImageSegmenter
<a name="background-replacement-web-imagesegmenter"></a>

Quindi, crea un'istanza di `ImageSegmenter`, che segmenterà l'immagine e restituirà il risultato come maschera. Quando crei un'istanza di un `ImageSegmenter`, utilizzerai il [modello di segmentazione dei 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,
  });
};
```

### Rendering del feed video su un'area di lavoro
<a name="background-replacement-web-render-video-to-canvas"></a>

Quindi, crea la funzione che esegue il rendering del feed video sull'altro elemento dell'area di lavoro. È necessario renderizzare il feed video su un'area di lavoro in modo da poter estrarre i pixel di primo piano da esso utilizzando l'API Canvas 2D. Durante questa operazione, passeremo anche un fotogramma video alla nostra istanza di `ImageSegmenter`, utilizzando il metodo [segmentforVideo](https://developers.google.com/mediapipe/api/solutions/js/tasks-vision.imagesegmenter#imagesegmentersegmentforvideo) per segmentare il primo piano dallo sfondo nel fotogramma video. Quando il metodo [segmentforVideo](https://developers.google.com/mediapipe/api/solutions/js/tasks-vision.imagesegmenter#imagesegmentersegmentforvideo) viene completato, richiama la nostra funzione di callback personalizzata, `replaceBackground`, per eseguire la sostituzione dello sfondo.

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

### Creazione di una logica di sostituzione dello sfondo
<a name="background-replacement-web-logic"></a>

Crea la funzione `replaceBackground`, che unisce l'immagine di sfondo personalizzata con il primo piano dal feed della fotocamera per sostituire lo sfondo. La funzione recupera innanzitutto i dati dei pixel sottostanti dell'immagine di sfondo personalizzata e del feed video dai due elementi dell'area di lavoro creati in precedenza. Quindi scorre attraverso la maschera fornita da `ImageSegmenter`, che indica quali pixel sono in primo piano. Mentre itera attraverso la maschera, copia selettivamente i pixel che contengono il feed della fotocamera dell'utente nei dati dei pixel di sfondo corrispondenti. Fatto ciò, converte i dati finali dei pixel con il primo piano copiato sullo sfondo e li disegna su un'area di lavoro.

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

Per riferimento, ecco il file `app.js` completo contenente tutta la logica di cui sopra:

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

### Creazione di un file di configurazione di Webpack.
<a name="background-replacement-web-webpack-config"></a>

Aggiungi questa configurazione al file di configurazione di Webpack per raggruppare `app.js`, in modo che le chiamate di importazione funzionino:

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

### Raggruppamento dei tuoi file JavaScript
<a name="background-replacement-web-bundle-javascript"></a>

```
npm run build
```

Avvia un semplice server HTTP dalla directory contenente `index.html` e apri `localhost:8000` per vedere il risultato:

```
python3 -m http.server -d ./
```

## Android
<a name="background-replacement-android"></a>

Per sostituire lo sfondo del tuo streaming live, puoi utilizzare l'API di segmentazione dei selfie di [Google ML Kit](https://developers.google.com/ml-kit/vision/selfie-segmentation). L'API di segmentazione dei selfie accetta l'immagine di una fotocamera come input e restituisce una maschera che fornisce un punteggio di affidabilità per ogni pixel dell'immagine, indicando se era in primo piano o sullo sfondo. In base al punteggio di affidabilità, puoi quindi recuperare il colore dei pixel corrispondente dall'immagine di sfondo o dall'immagine in primo piano. Questo processo continua fino a quando non sono stati esaminati tutti i punteggi di affidabilità nella maschera. Il risultato è una nuova gamma di colori dei pixel contenenti pixel in primo piano combinati con i pixel dell'immagine di sfondo.

Per integrare la sostituzione dello sfondo con l'SDK di trasmissione per lo streaming in tempo reale IVS, è necessario:

1. Installare le librerie CameraX e Google ML Kit.

1. Inizializzare le variabili boilerplate.

1. Creare una sorgente di immagini personalizzate.

1. Gestire i frame della fotocamera.

1. Passre i frame della fotocamera a Google ML Kit.

1. Sovrapprre i frame della fotocamera in primo piano allo sfondo personalizzato.

1. Inserire la nuova immagine in una sorgente di immagini personalizzate.

### Installazione delle librerie CameraX e di Google ML Kit
<a name="background-replacement-android-install-camerax-googleml"></a>

Per estrarre immagini dal feed live della fotocamera, utilizza la libreria CameraX di Android. Per installare l'SDK Camera Kit e Google ML Kit, aggiungi quanto segue al file `build.gradle` del modulo. Sostituisci `${camerax_version}` e `${google_ml_kit_version}` con la versione più recente delle librerie [CameraX](https://developer.android.com/jetpack/androidx/releases/camera) e [Google ML Kit](https://developers.google.com/ml-kit/vision/selfie-segmentation/android), rispettivamente. 

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

Importa le seguenti librerie:

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

### Inizializzazione delle variabili boilerplate.
<a name="background-replacement-android-initialize-variables"></a>

Inizializza un'istanza di `ImageAnalysis` e un'istanza di un `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
```

Inizializza un'istanza Segmenter in [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)
```

### Creazione di una sorgente di immagini personalizzate
<a name="background-replacement-android-create-image-source"></a>

Nel metodo `onCreate` della tua attività, crea un'istanza di un oggetto `DeviceDiscovery` e crea una sorgente di immagini personalizzate. Il `Surface` fornito dalla sorgente di immagini personalizzate riceverà l'immagine finale, con il primo piano sovrapposto a un'immagine di sfondo personalizzata. Creerai quindi un'istanza di un `ImageLocalStageStream` utilizzando la sorgente di immagini personalizzate. L'istanza di un `ImageLocalStageStream` (denominata `filterStream` in questo esempio) può quindi essere pubblicata in una fase. Consulta la [Guida all'SDK di trasmissione Android IVS](broadcast-android.md) per le istruzioni di configurazione di una fase. Infine, crea anche un thread che verrà utilizzato per gestire la fotocamera.

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

### Gestione di frame della fotocamera
<a name="background-replacement-android-camera-frames"></a>

Quindi, crea una funzione per inizializzare la fotocamera. Questa funzione utilizza la libreria CameraX per estrarre immagini dal feed live della fotocamera. Innanzitutto, crea un'istanza di un `ProcessCameraProvider` chiamata `cameraProviderFuture`. Questo oggetto rappresenta un risultato futuro dell'ottenimento di un fornitore di fotocamere. Quindi carica un'immagine dal tuo progetto come bitmap. Questo esempio utilizza l'immagine di una spiaggia come sfondo, ma è possibile utilizzare qualsiasi immagine desiderata.

Quindi aggiungi un listener a `cameraProviderFuture`. Questo listener riceve una notifica quando la fotocamera diventa disponibile o se si verifica un errore durante il processo di ricerca di un fornitore di fotocamere.

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

All'interno del listener, crea `ImageAnalysis.Builder` per accedere a ogni singolo fotogramma dal feed live della fotocamera. Imposta la strategia di contropressione su `STRATEGY_KEEP_ONLY_LATEST`. Ciò garantisce che per l'elaborazione venga fornito un solo fotogramma alla volta. Converte ogni singolo fotogramma della fotocamera in una bitmap in modo da poterne estrarre i pixel per combinarli successivamente con l'immagine di sfondo personalizzata.

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

### Passaggio dei frame della fotocamera a Google ML Kit
<a name="background-replacement-android-frames-to-mlkit"></a>

Quindi, crea un file `InputImage` e invialo all'istanza di Segmenter per l'elaborazione. Un `InputImage` può essere creato da un `ImageProxy` fornito dall'istanza di `ImageAnalysis`. Una volta fornito `InputImage` a Segmenter, viene restituita una maschera con punteggi di affidabilità che indicano la probabilità che un pixel sia in primo piano o sullo sfondo. Questa maschera fornisce anche proprietà di larghezza e altezza, che verranno utilizzate per creare un nuovo array contenente i pixel di sfondo dell'immagine di sfondo personalizzata caricata in precedenza.

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

### Sovrapposizione dei frame della fotocamera in primo piano allo sfondo personalizzato
<a name="background-replacement-android-overlay-frame-foreground"></a>

Con la maschera contenente i punteggi di affidabilità, il frame della fotocamera come bitmap e i pixel a colori dell'immagine di sfondo personalizzata, hai tutto ciò di cui hai bisogno per sovrapporre il primo piano allo sfondo personalizzato. La funzione `overlayForeground` viene quindi chiamata con i parametri seguenti:

#### Java
<a name="background-replacement-android-call-overlayforeground-code"></a>

```
resultBitmap = overlayForeground(mask, maskWidth, maskHeight, inputBitmap, backgroundPixels)
```

Questa funzione scorre attraverso la maschera e verifica i valori di affidabilità per determinare se ottenere il colore dei pixel corrispondente dall'immagine di sfondo o dal frame della fotocamera. Se il valore di affidabilità indica che un pixel nella maschera è molto probabilmente sullo sfondo, otterrà il colore dei pixel corrispondente dall'immagine di sfondo; in caso contrario, otterrà il colore dei pixel corrispondente dal frame della fotocamera per costruire il primo piano. Una volta che la funzione termina l'iterazione nella maschera, viene creata e restituita una nuova bitmap utilizzando la nuova matrice di pixel colorati. Questa nuova bitmap contiene il primo piano sovrapposto allo sfondo personalizzato.

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

### Inserimento della nuova immagine in una sorgente di immagini personalizzata
<a name="background-replacement-android-custom-image-source"></a>

È quindi possibile scrivere la nuova bitmap nella `Surface` fornita dalla sorgente di immagini personalizzata. Questa operazione la trasmetterà alla fase.

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

Ecco la funzione completa per ottenere i fotogrammi della fotocamera, passarli a Segmenter e sovrapporli allo sfondo:

#### Java
<a name="background-replacement-android-custom-image-source-startcamera-code"></a>

```
@androidx.annotation.OptIn(androidx.camera.core.ExperimentalGetImage::class)
    private fun startCamera(surface: Surface) {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
        val imageResource = R.drawable.clouds
        val bgBitmap: Bitmap = BitmapFactory.decodeResource(resources, imageResource)
        var resultBitmap: Bitmap;

        cameraProviderFuture.addListener({
            // Used to bind the lifecycle of cameras to the lifecycle owner
            val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

            val imageAnalyzer = ImageAnalysis.Builder()
            analysisUseCase = imageAnalyzer
                .setTargetResolution(Size(720, 1280))
                .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
                .build()

            analysisUseCase!!.setAnalyzer(cameraExecutor) { imageProxy: ImageProxy ->
                val mediaImage = imageProxy.image
                val tempBitmap = imageProxy.toBitmap();
                val inputBitmap = tempBitmap.rotate(imageProxy.imageInfo.rotationDegrees.toFloat())

                if (mediaImage != null) {
                    val inputImage =
                        InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)

                    segmenter.process(inputImage)
                        .addOnSuccessListener { segmentationMask ->
                            val mask = segmentationMask.buffer
                            val maskWidth = segmentationMask.width
                            val maskHeight = segmentationMask.height
                            val backgroundPixels = IntArray(maskWidth * maskHeight)
                            bgBitmap.getPixels(backgroundPixels, 0, maskWidth, 0, 0, maskWidth, maskHeight)

                            resultBitmap = overlayForeground(mask, maskWidth, maskHeight, inputBitmap, backgroundPixels)
                            canvas = surface.lockCanvas(null);
                            canvas.drawBitmap(resultBitmap, 0f, 0f, null)

                            surface.unlockCanvasAndPost(canvas);

                        }
                        .addOnFailureListener { exception ->
                            Log.d("App", exception.message!!)
                        }
                        .addOnCompleteListener {
                            imageProxy.close()
                        }

                }
            };

            val cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA

            try {
                // Unbind use cases before rebinding
                cameraProvider.unbindAll()

                // Bind use cases to camera
                cameraProvider.bindToLifecycle(this, cameraSelector, analysisUseCase)

            } catch(exc: Exception) {
                Log.e(TAG, "Use case binding failed", exc)
            }

        }, ContextCompat.getMainExecutor(this))
    }
```

# SDK di trasmissione IVS: modalità audio per dispositivi mobili I Streaming in tempo reale
<a name="broadcast-mobile-audio-modes"></a>

La qualità audio è una parte importante di qualsiasi esperienza multimediale in team reali e non esiste una configurazione audio valida per tutti i casi d'uso. Per garantire ai tuoi utenti la migliore esperienza di ascolto di uno streaming IVS in tempo reale, i nostri SDK mobili offrono diverse configurazioni audio preimpostate oltre a personalizzazioni più avanzate, se necessarie.

## Introduzione
<a name="broadcast-mobile-audio-modes-introduction"></a>

Gli SDK di trasmissione mobile IVS forniscono una classe `StageAudioManager`. Questa classe è progettata per essere l'unico punto di contatto per il controllo delle modalità audio sottostanti su entrambe le piattaforme. Su Android, controlla [AudioManager](https://developer.android.com/reference/android/media/AudioManager), che include la modalità audio, la sorgente audio, il tipo di contenuto, l'utilizzo e i dispositivi di comunicazione. Su iOS, controlla l'applicazione [AVAudioSession](https://developer.apple.com/documentation/avfaudio/avaudiosession) e verifica che [voiceProcessing](https://developer.apple.com/documentation/avfaudio/avaudioionode/3152101-voiceprocessingenabled?language=objc) sia abilitato.

**Importante**: non interagire con `AVAudioSession` o `AudioManager` direttamente mentre l'SDK di trasmissione in tempo reale IVS è attivo. Ciò potrebbe causare la perdita dell'audio o la registrazione o la riproduzione dell'audio sul dispositivo sbagliato.

Prima di creare il primo oggetto `DeviceDiscovery` o `Stage`, è necessario configurare 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
```

------

Se nulla è impostato su `StageAudioManager` prima dell'inizializzazione di un'istanza `DeviceDiscovery` o `Stage`, viene applicata automaticamente la preimpostazione `VideoChat`.

## Preimpostazioni della modalità audio
<a name="broadcast-mobile-audio-modes-presets"></a>

L'SDK per la trasmissione in tempo reale offre tre preimpostazioni, ognuna personalizzata per i casi d'uso più comuni, come descritto di seguito. Per ogni preimpostazione, sono coperte cinque categorie principali che differenziano le preimpostazioni l'una dall'altra.

La categoria **Controllo del volume** si riferisce al tipo di volume (volume multimediale o volume delle chiamate) che viene utilizzato o modificato tramite i controlli del volume fisici del dispositivo. Tieni presente che ciò influisce sul volume quando si cambia modalità audio. Ad esempio, supponiamo che il volume del dispositivo sia impostato sul valore massimo quando si utilizza la preimpostazione della chat video. Il passaggio alla preimpostazione Solo abbonati causa un livello di volume diverso da quello del sistema operativo, il che potrebbe comportare una variazione significativa del volume del dispositivo.

### Videochat
<a name="audio-modes-presets-video-chat"></a>

Questa è la preimpostazione predefinita, progettata per quando il dispositivo locale deve avere una conversazione in tempo reale con altri partecipanti.

**Problema noto su iOS**: se si utilizza questa preimpostazione e non si collega un microfono, l'audio viene riprodotto attraverso l'auricolare anziché l'altoparlante del dispositivo. Utilizza questa preimpostazione solo in combinazione con un microfono.


| Categoria | Android | iOS | 
| --- | --- | --- | 
| Cancellazione dell'eco | Abilitato | Abilitato | 
| Controllo del volume | Volume delle chiamate | Volume delle chiamate | 
| Selezione del microfono | Limitato in base al sistema operativo. I microfoni USB potrebbero non essere disponibili. | Limitato in base al sistema operativo. I microfoni USB potrebbero non essere disponibili. Gli auricolari Bluetooth che gestiscono contemporaneamente sia l'input che l'output (ad esempio gli AirPods) dovrebbero funzionare. | 
| Uscita audio | Dovrebbe funzionare qualsiasi dispositivo di output. | Limitato in base al sistema operativo. Le cuffie con cavo potrebbero non essere disponibili. | 
| Qualità audio | Medio/Basso. Suonerà come una telefonata, non come una riproduzione multimediale. | Medio/Basso Suonerà come una telefonata, non come una riproduzione multimediale. | 

### Solo sottoscrizione
<a name="audio-modes-presets-subscribe-only"></a>

Questa preimpostazione è progettata per chi prevede di abbonarsi ad altri partecipanti alla pubblicazione ma non di pubblicare personalmente. Si concentra sulla qualità audio e supporta tutti i dispositivi di output disponibili.


| Categoria | Android | iOS | 
| --- | --- | --- | 
| Cancellazione dell'eco | Disabilitato | Disabilitato | 
| Controllo del volume | Volume multimediale | Volume multimediale | 
| Selezione del microfono | N/D, questa preimpostazione non è progettata per la pubblicazione. | N/D, questa preimpostazione non è progettata per la pubblicazione. | 
| Uscita audio | Dovrebbe funzionare qualsiasi dispositivo di output. | Dovrebbe funzionare qualsiasi dispositivo di output. | 
| Qualità audio | Elevato. Qualsiasi tipo di file multimediale dovrebbe essere chiaro, compresa la musica. | Elevato. Qualsiasi tipo di file multimediale dovrebbe essere chiaro, compresa la musica. | 

### Studio
<a name="audio-modes-presets-studio"></a>

Questa preimpostazione è progettata per abbonamenti di alta qualità pur mantenendo la possibilità di pubblicazione. Richiede l'hardware di registrazione e riproduzione per consentire la cancellazione dell'eco. Un caso d'uso in questo caso potrebbe essere l'utilizzo di un microfono USB e un auricolare con cavo. L'SDK manterrà la massima qualità audio facendo affidamento sulla separazione fisica di tali dispositivi dalla causa dell'eco.


| Categoria | Android | iOS | 
| --- | --- | --- | 
| Cancellazione dell'eco | La cancellazione dell'eco dalla piattaforma è disabilitata, ma la cancellazione dell'eco del software può comunque verificarsi se `StageAudioConfiguration.enableEchoCancellation` è vero. | Disabilitato | 
| Controllo del volume | Volume multimediale, nella maggior parte dei casi. Volume di chiamata quando è collegato un microfono Bluetooth.  | Volume multimediale | 
| Selezione del microfono | Dovrebbe funzionare qualsiasi microfono. | Dovrebbe funzionare qualsiasi microfono. | 
| Uscita audio | Dovrebbe funzionare qualsiasi dispositivo di output. | Dovrebbe funzionare qualsiasi dispositivo di output. | 
| Qualità audio | Elevato. Entrambe le parti dovrebbero essere in grado di inviare musica e ascoltarla chiaramente dall'altra parte. Quando è collegato un auricolare Bluetooth, la qualità audio diminuirà a causa dell'attivazione della modalità Bluetooth SCO. | Elevato. Entrambe le parti dovrebbero essere in grado di inviare musica e ascoltarla chiaramente dall'altra parte. Quando è collegato un auricolare Bluetooth, la qualità audio potrebbe diminuire a causa dell'attivazione della modalità Bluetooth SCO, a seconda dell'auricolare.  | 

## Casi d'uso avanzati
<a name="broadcast-mobile-audio-modes-advanced-use-cases"></a>

Oltre alle preimpostazioni, gli SDK di trasmissione in streaming in tempo reale iOS e Android consentono di configurare le modalità audio della piattaforma sottostante:
+ Su Android, imposta [AudioSource](https://developer.android.com/reference/android/media/MediaRecorder.AudioSource), [Usage](https://developer.android.com/reference/android/media/AudioAttributes#USAGE_ALARM) e [ContentType](https://developer.android.com/reference/android/media/AudioAttributes#CONTENT_TYPE_MOVIE).
+ Su iOS, usa [AVAudioSession.Category](https://developer.apple.com/documentation/avfaudio/avaudiosession/category), [AVAudioSession.CategoryOptions](https://developer.apple.com/documentation/avfaudio/avaudiosession/categoryoptions), [AVAudioSession.Mode](https://developer.apple.com/documentation/avfaudio/avaudiosession/mode) e la possibilità di attivare/disattivare se l'[elaborazione vocale](https://developer.apple.com/documentation/avfaudio/avaudioionode/3152101-voiceprocessingenabled?language=objc) è abilitata o meno durante la pubblicazione.

Nota: quando si utilizzano questi metodi dell'SDK per l'audio, è possibile configurare erroneamente la sessione audio sottostante. Ad esempio, l'utilizzo dell'opzione `.allowBluetooth` su iOS in combinazione con la categoria `.playback` crea una configurazione audio non valida e l'SDK non può registrare o riprodurre l'audio. Questi metodi sono progettati per essere utilizzati solo quando un'applicazione prevede requisiti specifici per le sessioni audio che sono stati convalidati.

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

------

### Cancellazione dell'eco su iOS
<a name="advanced-use-cases-ios_echo_cancellation"></a>

La cancellazione dell'eco su iOS può essere controllata indipendentemente anche tramite `IVSStageAudioManager` utilizzando il rispettivo metodo `echoCancellationEnabled`. Questo metodo controlla se l'[elaborazione vocale](https://developer.apple.com/documentation/avfaudio/avaudioionode/3152101-voiceprocessingenabled?language=objc) è abilitata sui nodi di input e output del sottostante `AVAudioEngine` utilizzato dall'SDK. È importante comprendere l'effetto della modifica manuale di questa proprietà:
+ La proprietà `AVAudioEngine` viene rispettata solo se il microfono dell'SDK è attivo; ciò è dovuto al requisito iOS che richiede che l'elaborazione vocale sia abilitata contemporaneamente su entrambi i nodi di input e output. Normalmente, ciò viene fatto utilizzando il microfono restituito da `IVSDeviceDiscovery` per creare un elemento `IVSLocalStageStream` da pubblicare. In alternativa, il microfono può essere abilitato, senza essere utilizzato per la pubblicazione, collegando un elemento `IVSAudioDeviceStatsCallback` al microfono stesso. Questo approccio alternativo è utile se è necessario cancellare l'eco quando si utilizza un microfono personalizzato basato su sorgente audio anziché il microfono dell'SDK IVS.
+ L'attivazione della proprietà `AVAudioEngine` richiede la modalità `.videoChat` o `.voiceChat`. La richiesta di una modalità diversa fa sì che il framework audio sottostante di iOS vada in conflitto con l'SDK, causando la perdita dell'audio.
+ L'attivazione automatica di `AVAudioEngine` abilita l'opzione `.allowBluetooth `.

I comportamenti possono variare a seconda del dispositivo e della versione di iOS.

### Sorgenti audio personalizzate su iOS
<a name="advanced-use-cases-ios_custom_audio_sources"></a>

Le sorgenti audio personalizzate possono essere utilizzate con l'SDK tramite `IVSDeviceDiscovery.createAudioSource`. Quando ci si connette a una fase, l'SDK di trasmissione in streaming in tempo reale IVS gestisce comunque un'istanza `AVAudioEngine` interna per la riproduzione audio, anche se il microfono dell'SDK non viene utilizzato. Di conseguenza, i valori forniti a `IVSStageAudioManager` devono essere compatibili con l'audio fornito dalla sorgente audio personalizzata.

Se la sorgente audio personalizzata utilizzata per la pubblicazione registra dal microfono ma è gestita dall'applicazione host, l'SDK di cancellazione dell'eco riportato sopra non funzionerà a meno che non sia attivato il microfono gestito dall'SDK. Per ovviare a questo requisito, consulta [Cancellazione dell'eco su iOS](#advanced-use-cases-ios_echo_cancellation).

### Pubblicazione con Bluetooth su Android
<a name="advanced-use-cases-bluetooth-android"></a>

L'SDK torna automaticamente alla preimpostazione `VIDEO_CHAT` su Android quando vengono soddisfatte le seguenti condizioni:
+ La configurazione assegnata non utilizza il valore di utilizzo `VOICE_COMMUNICATION`.
+ Un microfono Bluetooth è collegato al dispositivo.
+ Il partecipante locale sta pubblicando in una fase.

Questa è una limitazione del sistema operativo Android per quanto riguarda l'utilizzo degli auricolari Bluetooth per la registrazione audio.

## Integrazione con altri SDK
<a name="broadcast-mobile-audio-modes-integrating-other-sdks"></a>

Poiché sia iOS che Android supportano solo una modalità audio attiva per applicazione, è normale che si verifichino conflitti se l'applicazione utilizza più SDK che richiedono il controllo della modalità audio. Quando si verificano questi conflitti, è possibile provare alcune strategie di risoluzione comuni, illustrate di seguito.

### Corrisponde ai valori della modalità audio
<a name="integrating-other-sdks-match-values"></a>

Utilizzando le opzioni avanzate di configurazione audio dell'SDK IVS o le funzionalità degli altri SDK, fai in modo che i due SDK si allineino ai valori sottostanti.

### Agora
<a name="integrating-other-sdks-agora"></a>

#### iOS
<a name="integrating-other-sdks-agora-ios"></a>

Su iOS, dire all'SDK Agora di mantenere `AVAudioSession` attivo ne impedirà la disattivazione mentre l'SDK di trasmissione in streaming in tempo reale IVS lo utilizza.

```
myRtcEngine.SetParameters("{\"che.audio.keep.audiosession\":true}");
```

#### Android
<a name="integrating-other-sdks-agora-android"></a>

Evita di chiamare `setEnableSpeakerphone` su `RtcEngine` e chiama `enableLocalAudio(false)` durante la pubblicazione con l'SDK di trasmissione in streaming in tempo reale IVS. Potrai chiamare nuovamente `enableLocalAudio(true)` quando l'SDK IVS non è in fase di pubblicazione.