

# Fase 5: Pubblicazione e sottoscrizione di video
<a name="getting-started-pub-sub"></a>

È possibile pubblicare/abbonarsi (in tempo reale) a IVS con:
+ Gli [SDK di trasmissione IVS](https://docs.aws.amazon.com//ivs/latest/LowLatencyUserGuide/getting-started-set-up-streaming.html#broadcast-sdk) nativi che supportano WebRTC e RTMPS. Consigliamo questa soluzione, in particolare per gli scenari di produzione. Consulta i dettagli di seguito per [web](getting-started-pub-sub-web.md), [Android](getting-started-pub-sub-android.md) e [iOS](getting-started-pub-sub-ios.md).
+ La console Amazon IVS: è adatta per testare i flussi. Consulta qui di seguito.
+ Altri codificatori software e hardware di streaming: è possibile utilizzare qualsiasi codificatore di streaming che supporti i protocolli RTMP, RTMPS o WHIP. Per ulteriori informazioni, consulta la sezione [Acquisizione dei flussi](rt-stream-ingest.md).

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

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

   È possibile accedere alla console Amazon IVS anche dalla [Console di gestione AWS](https://console.aws.amazon.com/).

1. Nel riquadro di navigazione, seleziona **Fasi**. (Se il riquadro di navigazione è compresso, espandilo selezionando l'icona dell'hamburger.)

1. Seleziona la fase a cui desideri abbonarti o pubblicare per accedere alla relativa pagina dei dettagli.

1. Per abbonarti: se la fase ha uno o più publisher, puoi abbonarti premendo il pulsante **Abbonati** nella scheda **Abbonati**. Le schede sono sotto la sezione **Configurazione generale**.

1. Per pubblicare:

   1. Seleziona la scheda **Pubblica**.

   1. Ti verrà richiesto di concedere alla console IVS l'accesso alla videocamera e al microfono; **Consenti** tali autorizzazioni.

   1. Nella parte inferiore della scheda **Pubblica**, utilizza le caselle a discesa per selezionare i dispositivi di input per il microfono e la videocamera.

   1. Per iniziare a pubblicare, seleziona **Inizia a pubblicare**.

   1. Per visualizzare i contenuti pubblicati, torna alla scheda **Abbonati**.

   1. Per interrompere la pubblicazione, vai alla scheda **Pubblica** e premi il pulsante **Interrompi pubblicazione** in basso.

**Nota**: l'abbonamento e la pubblicazione consumano risorse e ti verrà addebitata una tariffa oraria per il periodo di connessione alla fase. Per ulteriori informazioni, consulta la sezione [Streaming in tempo reale](https://aws.amazon.com/ivs/pricing/#Real-Time_Streaming) nella pagina dei prezzi di IVS.

# Pubblica e sottoscrivi con l'SDK di trasmissione Web IVS
<a name="getting-started-pub-sub-web"></a>

Questa sezione illustra i passaggi necessari per pubblicare e sottoscrivere una fase utilizzando l'app web.

## Creazione di un boilerplate HTML
<a name="getting-started-pub-sub-web-html"></a>

Per prima cosa, creiamo il boilerplate HTML e importiamo la libreria come tag di script:

```
<!DOCTYPE html>
<html lang="en">

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

  <!-- Import the SDK -->
  <script src="https://web-broadcast.live-video.net/1.33.0/amazon-ivs-web-broadcast.js"></script>
</head>

<body>

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

</body>
</html>
```

## Accettazione dell'input di token e aggiunta dei pulsanti Unisciti/Abbandona
<a name="getting-started-pub-sub-web-join"></a>

Qui riempiamo il corpo con i nostri controlli di input. Tali controlli prendono come input il token e configurano i pulsanti **Unisciti** e **Abbandona**. In genere le applicazioni richiedono il token dall'API dell'applicazione, ma per questo esempio il token verrà copiato e incollato nell'input del token.

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

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

## Aggiunta di elementi del container multimediale
<a name="getting-started-pub-sub-web-media"></a>

Questi elementi serviranno da supporto ai media per i nostri partecipanti locali e remoti. Aggiungiamo un tag di script per caricare la logica dell'applicazione definita in `app.js`.

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

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

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

Questa operazione completa la pagina HTML che dovrebbe essere visualizzata durante il caricamento di `index.html` in un browser:

![\[Visualizzazione dello streaming in tempo reale in un browser: configurazione HTML completata.\]](http://docs.aws.amazon.com/it_it/ivs/latest/RealTimeUserGuide/images/RT_Browser_View.png)


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

Passiamo alla definizione dei contenuti del file `app.js`. Inizia importando tutte le proprietà richieste dal pacchetto globale dell'SDK:

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

## Creazione di variabili dell'applicazione
<a name="getting-started-pub-sub-web-vars"></a>

Stabilisci le variabili che contengono i riferimenti agli elementi HTML dei pulsanti **Unisciti** e **Abbandona** e lo stato di archiviazione dell'applicazione:

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

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

## Crea joinStage 1: definisci la funzione e convalida l'input
<a name="getting-started-pub-sub-web-joinstage1"></a>

La funzione `joinStage` prende il token di input, crea una connessione alla fase e inizia a pubblicare video e audio recuperati da `getUserMedia`.

Per iniziare, definiamo la funzione e convalidiamo lo stato e l'input del token. Approfondiremo questa funzione nelle prossime sezioni.

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

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

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

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

## Crea joinStage 2: ottieni i contenuti multimediali da pubblicare
<a name="getting-started-pub-sub-web-joinstage2"></a>

Ecco i contenuti multimediali che verranno pubblicati nella fase:

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

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

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

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

## Crea joinStage 3: definisci la strategia della fase e crea la fase
<a name="getting-started-pub-sub-web-joinstage3"></a>

La strategia della fase è il fulcro della logica decisionale che l'SDK utilizza per decidere cosa pubblicare e quali partecipanti sottoscrivere. Per ulteriori informazioni sullo scopo della funzione, consulta la sezione [Strategia](web-publish-subscribe.md#web-publish-subscribe-concepts-strategy).

Questa strategia è semplice. Dopo essere entrati nella fase, pubblica i flussi appena recuperati e sottoscrivi l'audio e i video di ogni partecipante remoto:

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

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

## Crea joinStage 4: gestisci gli eventi nella fase ed esegui il rendering dei contenuti multimediali
<a name="getting-started-pub-sub-web-joinstage4"></a>

Le fasi emettono numerosi eventi. Dovremo ascoltare `STAGE_PARTICIPANT_STREAMS_ADDED` e `STAGE_PARTICIPANT_LEFT` per eseguire il rendering e rimuovere contenuti multimediali da e verso la pagina. Una serie più esaustiva di eventi è riportata nella sezione [Eventi](web-publish-subscribe.md#web-publish-subscribe-concepts-events).

In questo esempio creeremo quattro funzioni di supporto la gestione degli elementi DOM necessari: `setupParticipant`, `teardownParticipant`, `createVideoEl` e `createContainer`.

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

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

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

    let streamsToDisplay = streams;

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

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

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


// Helper functions for managing DOM

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

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

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

  return videoEl;
}

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

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

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

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

  return participantContainer;
}
```

## Crea joinStage 5: unisciti alla fase
<a name="getting-started-pub-sub-web-joinstage5"></a>

Completiamo la nostra funzione `joinStage` unendoci finalmente alla fase.

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

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

Definisci la funzione `leaveStage` che verrà invocata dal pulsante Abbandona.

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

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

## Inizializza i gestori di eventi di input
<a name="getting-started-pub-sub-web-handlers"></a>

Aggiungeremo un'ultima funzione al file `app.js`. Questa funzione viene richiamata immediatamente quando la pagina viene caricata e stabilisce i gestori di eventi per unirsi e abbandonare la fase.

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

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

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

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

## Esegui l'applicazione e fornisci un token
<a name="getting-started-pub-sub-run-app"></a>

A questo punto puoi condividere la pagina Web localmente o con altri, [apri la pagina](#getting-started-pub-sub-web-media) e inserisci un token di partecipazione ed entra nella fase.

## Fasi successive
<a name="getting-started-pub-sub-next"></a>

Per esempi più dettagliati che coinvolgono npm, React e altro, consulta [SDK di trasmissione IVS: guida Web (guida per lo streaming in tempo reale)](broadcast-web.md).

# Pubblica e sottoscrivi con l'SDK di trasmissione Android IVS
<a name="getting-started-pub-sub-android"></a>

Questa sezione illustra i passaggi necessari per pubblicare e sottoscrivere una fase utilizzando l'app per Android.

## Creazione delle viste
<a name="getting-started-pub-sub-android-views"></a>

Iniziamo creando un layout semplice per la nostra app utilizzando il file `activity_main.xml` creato automaticamente. Il layout contiene un `EditText` per aggiungere un token, un `Button` Unisciti, un `TextView` per visualizzare lo stato della fase e un `CheckBox` per attivare la pubblicazione.

![\[Configura il layout di pubblicazione per la tua app Android.\]](http://docs.aws.amazon.com/it_it/ivs/latest/RealTimeUserGuide/images/Publish_Android_1.png)


Ecco l'XML alla base della vista:

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

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

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

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

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

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

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

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

        </androidx.constraintlayout.widget.ConstraintLayout>

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

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

Abbiamo fatto riferimento a un paio di ID di stringa, quindi creeremo il nostro file `strings.xml` per intero:

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

Colleghiamo queste viste nell'XML a `MainActivity.kt`:

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

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

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

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

Ora creiamo una vista degli elementi per `RecyclerView`. Per fare ciò, fai clic con il pulsante destro del mouse sulla directory `res/layout`  e seleziona**Nuovo > File di risorse di layout**. Assegna un nome a questo nuovo file `item_stage_participant.xml`.

![\[Crea una vista degli elementi per l'app Android RecyclerView.\]](http://docs.aws.amazon.com/it_it/ivs/latest/RealTimeUserGuide/images/Publish_Android_2.png)


Il layout di questo elemento è semplice: contiene una vista per il rendering del flusso video di un partecipante e un elenco di etichette per visualizzare le informazioni sul partecipante:

![\[Crea una vista degli elementi per l'app Android RecyclerView - Etichette.\]](http://docs.aws.amazon.com/it_it/ivs/latest/RealTimeUserGuide/images/Publish_Android_3.png)


Ecco il codice XML:

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

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

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

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

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

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

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

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

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

    </LinearLayout>

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

Questo file XML inizia una classe che non è stata ancora creata, `ParticipantItem`. Poiché l'XML include il namespace completo, assicurati di aggiornare il file XML con il tuo namespace. Creiamo questa classe e configuriamo le viste, ma per ora lasciamola vuota.

Crea una nuova classe Kotlin, `ParticipantItem`:

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

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

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

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

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

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

Per utilizzare la fotocamera e il microfono, è necessario richiedere le autorizzazioni all'utente. A tal fine seguiamo un flusso di autorizzazioni standard:

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

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

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

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

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

## Stato dell'app
<a name="getting-started-pub-sub-android-app-state"></a>

La nostra applicazione tiene traccia dei partecipanti a livello locale in un `MainViewModel.kt` e lo stato verrà comunicato al `MainActivity` usando [StateFlow](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-state-flow/) di Kotlin.

Crea una nuova classe Kotlin, `MainViewModel`:

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

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

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

}
```

In `MainActivity.kt` gestiamo il nostro modello di  viste:

```
import androidx.activity.viewModels

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

Per usare `AndroidViewModel` e queste estensioni `ViewModel` di Kotlin, dovrai aggiungere quanto segue al file `build.gradle` del modulo:

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

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

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

Creeremo una semplice sottoclasse `RecyclerView.Adapter` per tenere traccia dei partecipanti e aggiornare `RecyclerView` relativamente agli eventi della fase. Ma prima, abbiamo bisogno di una classe che rappresenti un partecipante. Crea una nuova classe Kotlin, `StageParticipant`:

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

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

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

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

Useremo questa classe nella classe `ParticipantAdapter`che verrà creata successivamente. Iniziamo definendo la classe e creando una variabile per tenere traccia dei partecipanti:

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

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

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

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

Dobbiamo anche definire il nostro `RecyclerView.ViewHolder` prima di implementare il resto delle sostituzioni:

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

In questo modo, possiamo implementare le sostituzioni `RecyclerView.Adapter` standard:

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

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

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

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

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

Infine, aggiungiamo nuovi metodi che chiameremo da `MainViewModel` quando vengono apportate modifiche ai partecipanti. Questi metodi sono operazioni CRUD standard sull'adattatore.

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

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

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

Di nuovo in `MainViewModel` dobbiamo creare e conservare un riferimento a questo adattatore:

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

## Stato della fase
<a name="getting-started-pub-sub-android-views-stage-state"></a>

Dobbiamo anche tenere traccia di alcuni stati della fase all'interno di `MainViewModel`. Definiamo ora queste proprietà:

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

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

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

Per vedere l'anteprima prima di entrare nella fase, creiamo immediatamente un partecipante locale:

```
init {
    deviceDiscovery = DeviceDiscovery(application)

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

Vogliamo assicurarci di cancellare queste risorse quando viene cancellato `ViewModel`. Sostituiamo subito `onCleared()` in modo da non dimenticare di cancellare queste risorse.

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

Ora completiamo la proprietà `streams` locale appena vengono concesse le autorizzazioni, implementando il metodo `permissionsGranted` richiamato in precedenza:

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

    stage?.refreshStrategy()

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

## Implementazione dell'SDK della fase
<a name="getting-started-pub-sub-android-stage-sdk"></a>

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

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

L'implementazione di `Stage.Strategy` è semplice:

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

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

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

Per riassumere, eseguiamo la pubblicazione in base allo stato interno di `publishEnabled` e pubblichiamo i flussi raccolti in precedenza. Infine, per questo esempio, sottoscriviamo sempre gli altri partecipanti, ricevendo sia il loro audio che i loro video.

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

Anche l'implementazione di `StageRenderer` è abbastanza semplice, sebbene dato il numero di funzioni contenga un po' più di codice. L'approccio generale in questo renderer è quello di aggiornare `ParticipantAdapter` quando l'SDK notifica una modifica a un partecipante. Ci sono alcuni scenari in cui gestiamo i partecipanti locali in modo diverso, perché abbiamo deciso di gestirli noi stessi in modo che possano vedere l'anteprima della fotocamera prima di partecipare.

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

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

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

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

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

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

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

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

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

## Implementazione di un RecyclerView LayoutManager personalizzato
<a name="getting-started-pub-sub-android-layout"></a>

La creazione di un layout con un numero diverso di partecipanti può essere complessa. Vuoi che occupino l'intera cornice della vista principale, ma non vuoi gestire singolarmente la configurazione di ogni partecipante. Per semplificare questa operazione, descriveremo l'implementazione di un `RecyclerView.LayoutManager`.

Crea un'altra nuova classe, `StageLayoutManager`, che dovrebbe estendere `GridLayoutManager`. Questa classe è progettata per calcolare il layout per ogni partecipante in base al numero di partecipanti in un layout di riga/colonna basato sul flusso. Ogni riga ha la stessa altezza delle altre, ma le colonne possono avere larghezze diverse a seconda della riga. Vedi il commento sul codice sopra la variabile `layouts` per una descrizione di come personalizzare questo comportamento.

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

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

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

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

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

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

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

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

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

Di nuovo in `MainActivity.kt`, dobbiamo impostare l'adattatore e il gestore del layout per il `RecyclerView`:

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

## Aggancio delle operazioni dell'interfaccia utente
<a name="getting-started-pub-sub-android-actions"></a>

Stiamo per finire; ci sono solo alcune operazioni dell'interfaccia utente di cui dobbiamo eseguire l'hook.

Per prima cosa dobbiamo far sì che `MainActivity` osservi le modifiche `StateFlow` da `MainViewModel`:

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

Successivamente dobbiamo aggiungere i listener al nostro pulsante Unisciti e alla casella di controllo Pubblica:

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

Entrambe le funzionalità di chiamata sopra riportate nel `MainViewModel`, che implementiamo ora:

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

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

## Rendering dei partecipanti
<a name="getting-started-pub-sub-android-participants"></a>

Infine, dobbiamo eseguire il rendering dei dati che riceviamo dall'SDK sull'elemento del partecipante che abbiamo creato in precedenza. Abbiamo già la logica `RecyclerView` finita, quindi dobbiamo solo implementare l'API `bind` in `ParticipantItem`.

Inizieremo aggiungendo la funzione vuota e poi la esamineremo dettagliatamente:

```
fun bind(participant: StageParticipant) {

}
```

Per prima cosa gestiremo lo stato di facilità, l'ID partecipante, lo stato di pubblicazione e lo stato di sottoscrizione. Per questi, ci limitiamo ad aggiornare direttamente il `TextViews`:

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

Successivamente aggiorneremo gli stati di disattivazione audio e video. Per ottenere lo stato di audio disattivato, dobbiamo trovare `ImageDevice` e `AudioDevice` dall'array dei flussi. Per ottimizzare le prestazioni, ricordiamo gli ID degli ultimi dispositivi collegati.

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

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

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

Infine vogliamo eseguire il rendering di un'anteprima per `imageDevice`:

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

E mostriamo le statistiche audio di `audioDevice`:

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

# Pubblica e sottoscrivi con l'SDK di trasmissione iOS IVS
<a name="getting-started-pub-sub-ios"></a>

Questa sezione illustra i passaggi necessari per pubblicare e sottoscrivere una fase utilizzando l'app per iOS.

## Creazione delle viste
<a name="getting-started-pub-sub-ios-views"></a>

Iniziamo usando il file `ViewController.swift` creato automaticamente per importare `AmazonIVSBroadcast` e poi aggiungiamo `@IBOutlets` per collegare:

```
import AmazonIVSBroadcast

class ViewController: UIViewController {

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

A questo punto creiamo quelle viste e le colleghiamo in `Main.storyboard`. Ecco la struttura delle viste che useremo:

![\[Usa Main.storyboard per creare una vista iOS.\]](http://docs.aws.amazon.com/it_it/ivs/latest/RealTimeUserGuide/images/Publish_iOS_1.png)


Per la configurazione di AutoLayout, dobbiamo personalizzare tre viste. La prima vista è **Partecipanti della vista Raccolta** (`UICollectionView`). Collegato **Iniziale**, **Finale** e **In basso** a **Area sicura**. Collegato anche **In alto** a **Container controlli**.

![\[Personalizza la vista Partecipanti della vista Raccolta per iOS.\]](http://docs.aws.amazon.com/it_it/ivs/latest/RealTimeUserGuide/images/Publish_iOS_2.png)


La seconda vista è **Container controlli**. Collegato **Iniziale**, **Finale** e **In alto** a **Area sicura**.

![\[Personalizza la vista del container di controlli per iOS.\]](http://docs.aws.amazon.com/it_it/ivs/latest/RealTimeUserGuide/images/Publish_iOS_3.png)


La terza e ultima vista è **Vista Stack verticale**. Collegato **In alto**, **Iniziale**, **Finale** e **In basso** a **Supervista**. Per lo stile, imposta la spaziatura su 8 anziché su 0.

![\[Personalizza la vista Stack verticale per iOS.\]](http://docs.aws.amazon.com/it_it/ivs/latest/RealTimeUserGuide/images/Publish_iOS_4.png)


**UIStackViews** gestirà il layout delle viste rimanenti. Per tutte e tre **UIStackViews**, usa **Riempi** per **Allineamento** e **Distribuzione**.

![\[Personalizza le viste iOS rimanenti con UIStackViews.\]](http://docs.aws.amazon.com/it_it/ivs/latest/RealTimeUserGuide/images/Publish_iOS_5.png)


Infine, colleghiamo queste viste a `ViewController`. Dall'alto, mappa le seguenti viste:
+ Il **campo di testo Unisciti** si collega a `textFieldToken`.
+ Il **pulsante Unisciti** si collega a `buttonJoin`.
+ Lo **stato dell'etichetta** si collega a `labelState`.
+ **Cambia pubblicazione** si collega a `switchPublish`.
+ **Partecipanti della vista Raccolta** si collega a `collectionViewParticipants`.

Usa questo tempo anche per impostare `dataSource` dell'elemento **Partecipanti della vista Raccolta** al `ViewController` di proprietà:

![\[Imposta l'origine dati dei partecipanti della vista Raccolta per l'app iOS.\]](http://docs.aws.amazon.com/it_it/ivs/latest/RealTimeUserGuide/images/Publish_iOS_6.png)


A questo punto creiamo la sottoclasse `UICollectionViewCell` in cui eseguire il rendering dei partecipanti. Inizia creando un nuovo file **Classe Cocoa Touch**:

![\[Crea un UICollectionViewCell per eseguire il rendering dei partecipanti iOS in tempo reale.\]](http://docs.aws.amazon.com/it_it/ivs/latest/RealTimeUserGuide/images/Publish_iOS_7.png)


Denominalo `ParticipantUICollectionViewCell` e rendilo una sottoclasse di `UICollectionViewCell` in Swift. Ricominciamo dal file Swift, creando il `@IBOutlets`per collegare:

```
import AmazonIVSBroadcast

class ParticipantCollectionViewCell: UICollectionViewCell {

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

Nel file XIB associato, crea questa gerarchia di viste:

![\[Nel file XIB associato, crea questa gerarchia di viste iOS:\]](http://docs.aws.amazon.com/it_it/ivs/latest/RealTimeUserGuide/images/Publish_iOS_8.png)


Per AutoLayout, modificheremo nuovamente tre viste. La prima vista è **Container di anteprima vista**. Imposta **Finale**, **Iniziale**, **In alto** e **In basso** su **Cella vista raccolta partecipanti**.

![\[Personalizza la vista Container di anteprima vista per iOS.\]](http://docs.aws.amazon.com/it_it/ivs/latest/RealTimeUserGuide/images/Publish_iOS_9.png)


La seconda vista è **Vista**. Imposta **Iniziale** e **In alto** su **Cella vista raccolta partecipanti** e modifica il valore su 4.

![\[Personalizza la vista Vista per iOS.\]](http://docs.aws.amazon.com/it_it/ivs/latest/RealTimeUserGuide/images/Publish_iOS_10.png)


La terza vista è **Vista stack**. Imposta **Finale**, **Iniziale**, **In alto** e **In basso** su **Supervista**, quindi modifica il valore su 4.

![\[Personalizza la vista Vista stack per iOS.\]](http://docs.aws.amazon.com/it_it/ivs/latest/RealTimeUserGuide/images/Publish_iOS_11.png)


## Autorizzazioni e timer di inattività
<a name="getting-started-pub-sub-ios-perms"></a>

Tornando a `ViewController`, disabiliteremo il timer di inattività del sistema per impedire al dispositivo di andare in modalità standby mentre la nostra applicazione è in uso:

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

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

Successivamente richiederemo al sistema le autorizzazioni per fotocamera e microfono:

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

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

## Stato dell'app
<a name="getting-started-pub-sub-ios-app-state"></a>

Dobbiamo configurare `collectionViewParticipants` con il file di layout creato in precedenza:

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

Per rappresentare ogni partecipante, creiamo una semplice struttura chiamata `StageParticipant`. Questa può essere inclusa nel file `ViewController.swift`  oppure è possibile creare un nuovo file.

```
import Foundation
import AmazonIVSBroadcast

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

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

Per tenere traccia di questi partecipanti, ne conserviamo un array come proprietà privata nel nostro `ViewController`:

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

Questa proprietà verrà utilizzata per alimentare `UICollectionViewDataSource` che era collegato allo storyboard precedente:

```
extension ViewController: UICollectionViewDataSource {

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

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

}
```

Per vedere l'anteprima prima di entrare nella fase, creiamo immediatamente un partecipante locale:

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

Ciò comporta il rendering di una cella partecipante immediatamente dopo l'esecuzione dell'app, che rappresenta il partecipante locale.

Gli utenti vogliono essere in grado di vedere se stessi prima di unirsi a una fase, quindi ora implementeremo il metodo `setupLocalUser()` che viene chiamato prima dal codice di gestione delle autorizzazioni. Memorizziamo il riferimento della fotocamera e del microfono come oggetti `IVSLocalStageStream`.

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

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

Qui abbiamo trovato la fotocamera e il microfono del dispositivo tramite l'SDK e li abbiamo archiviati nel nostro oggetto `streams` locale, quindi abbiamo assegnato l'array `streams` del primo partecipante (il partecipante locale che abbiamo creato in precedenza) al nostro `streams`. Finalmente richiamiamo `participantsChanged` con un `index` pari a 0 e `changeType` pari a `updated`. Questa funzione è una funzione helper  per aggiornare il nostro `UICollectionView` con simpatiche animazioni. Ecco come appare:

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

Non preoccuparti ancora di `cell.set`, ci arriveremo più tardi, ma è lì che eseguiremo il rendering del contenuto della cella in base al partecipante.

`ChangeType` è un semplice enum:

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

Infine, vogliamo verificare se la fase è collegata. Per tenerne traccia, usiamo un semplice `bool`, che aggiornerà automaticamente la nostra interfaccia utente quando si aggiornata da sola.

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

## Implementazione dell'SDK della fase
<a name="getting-started-pub-sub-ios-stage-sdk"></a>

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

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

L'implementazione di `IVSStageStrategy` è semplice:

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

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

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

Per riassumere, eseguiamo la pubblicazione solo se l'opzione di pubblicazione è in posizione "on" e, se pubblichiamo, pubblicheremo i flussi raccolti in precedenza. Infine, per questo esempio, sottoscriviamo sempre gli altri partecipanti, ricevendo sia il loro audio che i loro video.

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

Anche l'implementazione di `IVSStageRenderer` è abbastanza semplice, sebbene dato il numero di funzioni contenga un po' più di codice. L'approccio generale in questo renderer è quello di aggiornare l'array `participants` quando l'SDK notifica una modifica a un partecipante. Ci sono alcuni scenari in cui gestiamo i partecipanti locali in modo diverso, perché abbiamo deciso di gestirli noi stessi in modo che possano vedere l'anteprima della fotocamera prima di partecipare.

```
extension ViewController: IVSStageRenderer {

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

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

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

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

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

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

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

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

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

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

Questo codice utilizza un'estensione per convertire lo stato della connessione in testo intuitivo per l'utente:

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

## Implementazione di un  UICollectionViewLayout personalizzato
<a name="getting-started-pub-sub-ios-layout"></a>

La creazione di un layout con un numero diverso di partecipanti può essere complessa. Vuoi che occupino l'intera cornice della vista principale, ma non vuoi gestire singolarmente la configurazione di ogni partecipante. Per semplificare questa operazione, descriveremo l'implementazione di un `UICollectionViewLayout`.

Crea un altra nuovo file, `ParticipantCollectionViewLayout.swift`, che dovrebbe estendere `UICollectionViewLayout`. Questa classe userà un'altra classe chiamata `StageLayoutCalculator`, di cui parleremo presto. La classe riceve i valori di frame calcolati per ogni partecipante e quindi genera gli oggetti `UICollectionViewLayoutAttributes`  necessari.

```
import Foundation
import UIKit

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

    private let layoutCalculator = StageLayoutCalculator()

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

    override func prepare() {
        super.prepare()

        guard let collectionView = collectionView else { return }

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

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

    override var collectionViewContentSize: CGSize {
        return contentBounds.size
    }

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

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

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

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

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

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

        return attributesArray
    }

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

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

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

Più importante è la classe `StageLayoutCalculator.swift`. Questa classe è progettata per calcolare i frame per ogni partecipante in base al numero di partecipanti in un layout di riga/colonna basato sul flusso. Ogni riga ha la stessa altezza delle altre, ma le colonne possono avere larghezze diverse a seconda della riga. Vedi il commento sul codice sopra la variabile `layouts` per una descrizione di come personalizzare questo comportamento.

```
import Foundation
import UIKit

class StageLayoutCalculator {

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

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

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

        let halfPadding = padding / 2.0

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

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

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

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

}
```

Di nuovo in `Main.storyboard`, assicurati di impostare la classe di layout per `UICollectionView` sulla classe che abbiamo appena creato:

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


## Aggancio delle operazioni dell'interfaccia utente
<a name="getting-started-pub-sub-ios-actions"></a>

Stiamo per finire, dobbiamo solo creare qualche `IBActions`.

Per prima cosa gestiremo il pulsante Unisciti. Questo pulsante risponde in modo diverso in base al valore di `connectingOrConnected`. Quando è già connesso, abbandona semplicemente la fase. Se è disconnesso, legge il testo dal token `UITextField` e crea un nuovo `IVSStage` con quel testo. Quindi aggiungiamo il nostro `ViewController` come `strategy`,`errorDelegate` e renderer per `IVSStage`, infine ci uniamo alla fase in modo asincrono.

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

L'altra operazione dell'interfaccia utente di cui dobbiamo eseguire l'hook è il cambio di pubblicazione:

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

## Rendering dei partecipanti
<a name="getting-started-pub-sub-ios-participants"></a>

Infine, dobbiamo eseguire il rendering dei dati che riceviamo dall'SDK sulla cella del partecipante che abbiamo creato in precedenza. Abbiamo già la logica `UICollectionView` finita, quindi dobbiamo solo implementare l'API `set` in `ParticipantCollectionViewCell.swift`.

Inizieremo aggiungendo la funzione `empty` e poi la esamineremo dettagliatamente:

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

Per prima cosa gestiremo lo stato di facilità, l'ID partecipante, lo stato di pubblicazione e lo stato di sottoscrizione. Per questi, ci limitiamo ad aggiornare direttamente il `UILabels`:

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

Le proprietà testuali delle enumerazioni di pubblicazione e sottoscrizione provengono da estensioni locali:

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

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

Successivamente aggiorneremo gli stati di disattivazione audio e video. Per ottenere lo stato di audio disattivato, dobbiamo trovare `IVSImageDevice` e `IVSAudioDevice` dall'array `streams`. Per ottimizzare le prestazioni, ricorderemo gli ultimi dispositivi collegati.

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

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

registeredStreams = Set(participant.streams)

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

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

Infine vogliamo eseguire il rendering di un'anteprima per `imageDevice` e visualizzare le statistiche audio dal `audioDevice`:

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

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

L'ultima funzione che dobbiamo creare è `updatePreview()`, che aggiunge un'anteprima del partecipante alla nostra vista:

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

Quanto sopra utilizza una funzione helper su `UIView` per semplificare l'embedding delle viste secondarie:

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