

# Casi d'uso avanzati per l'SDK di trasmissione IVS per iOS \| Streaming a bassa latenza
<a name="broadcast-ios-use-cases"></a>

Qui presentiamo alcuni casi d'uso avanzati. Iniziare con la configurazione di base di cui sopra e continuare qui.

## Creare la configurazione di trasmissione
<a name="broadcast-ios-create-configuration"></a>

Qui creiamo una configurazione personalizzata con due slot mixer che ci permettono di associare due fonti video al mixer. Uno (`custom`) è a schermo intero e disposto dietro l'altro (`camera`), che è più piccolo e si trova nell'angolo in basso a destra. Per lo slot `custom` non impostiamo una posizione, una dimensione o una modalità di aspetto. Poiché non impostiamo questi parametri, lo slot utilizza le impostazioni video per ciò che riguarda dimensioni e posizione.

```
let config = IVSBroadcastConfiguration()
try config.audio.setBitrate(128_000)
try config.video.setMaxBitrate(3_500_000)
try config.video.setMinBitrate(500_000)
try config.video.setInitialBitrate(1_500_000)
try config.video.setSize(CGSize(width: 1280, height: 720))
config.video.defaultAspectMode = .fit
config.mixer.slots = [
    try {
        let slot = IVSMixerSlotConfiguration()
        // Do not automatically bind to a source
        slot.preferredAudioInput = .unknown
        // Bind to user image if unbound
        slot.preferredVideoInput = .userImage
        try slot.setName("custom")
        return slot
    }(),
    try {
        let slot = IVSMixerSlotConfiguration()
        slot.zIndex = 1
        slot.aspect = .fill
        slot.size = CGSize(width: 300, height: 300)
        slot.position = CGPoint(x: config.video.size.width - 400, y: config.video.size.height - 400)
        try slot.setName("camera")
        return slot
    }()
]
```

## Creare la sessione di trasmissione (versione avanzata)
<a name="broadcast-ios-create-session-advanced"></a>

Creare una `IVSBroadcastSession` come nell'[esempio di base](broadcast-ios-getting-started.md#broadcast-ios-create-session), ma fornire qui la propria configurazione personalizzata. Inoltre, inserire `nil` per l'array dei dispositivi, perché lo aggiungeremo manualmente.

```
let broadcastSession = try IVSBroadcastSession(
   configuration: config, // The configuration we created above
   descriptors: nil, // We’ll manually attach devices after
   delegate: self)
```

## Iterare e collegare un dispositivo fotocamera
<a name="broadcast-ios-attach-camera"></a>

Qui iteriamo attraverso i vari dispositivi di input rilevati dall'SDK. L'SDK restituirà solo i dispositivi integrati in iOS. Anche se i dispositivi audio Bluetooth sono collegati, verranno visualizzati come un dispositivo incorporato. Per ulteriori informazioni, consulta [Problemi noti e soluzioni alternative per l'SDK di trasmissione IVS per iOS \| Streaming a bassa latenza](broadcast-ios-issues.md).

Una volta che troviamo un dispositivo che desideriamo utilizzare, per collegarlo utilizziamo una chiamata `attachDevice`:

```
let frontCamera = IVSBroadcastSession.listAvailableDevices()
    .filter { $0.type == .camera && $0.position == .front }
    .first
if let camera = frontCamera {
    broadcastSession.attach(camera, toSlotWithName: "camera") { device, error in
        // check error
    }
}
```

## Scambiare fotocamere
<a name="broadcast-ios-swap-cameras"></a>

```
// This assumes you’ve kept a reference called `currentCamera` that points to the current camera.
let wants: IVSDevicePosition = (currentCamera.descriptor().position == .front) ? .back : .front
// Remove the current preview view since the device will be changing.
previewView.subviews.forEach { $0.removeFromSuperview() }
let foundCamera = IVSBroadcastSession
        .listAvailableDevices()
        .first { $0.type == .camera && $0.position == wants }
guard let newCamera = foundCamera else { return }
broadcastSession.exchangeOldDevice(currentCamera, withNewDevice: newCamera) { newDevice, _ in
    currentCamera = newDevice
    if let camera = newDevice as? IVSImageDevice {
        do {
            previewView.addSubview(try finalCamera.previewView())
        } catch {
            print("Error creating preview view \(error)")
        }
    }
}
```

## Creare una fonte di ingresso personalizzata
<a name="broadcast-ios-create-input-source"></a>

Per inserire dati audio o immagini generati dall'app, utilizza `createImageSource` o `createAudioSource`. Entrambi questi metodi creano dispositivi virtuali (`IVSCustomImageSource` e `IVSCustomAudioSource`) che possono essere associati al mixer come qualsiasi altro dispositivo.

I dispositivi restituiti da entrambi questi metodi accettano un `CMSampleBuffer`attraverso la sua funzione `onSampleBuffer`:
+ Per le fonti video, il formato pixel deve essere `kCVPixelFormatType_32BGRA`, `420YpCbCr8BiPlanarFullRange` oppure `420YpCbCr8BiPlanarVideoRange`.
+ Per le fonti audio, il buffer deve contenere dati PCM lineari.

Non è possibile utilizzare una `AVCaptureSession` con l'input della fotocamera per il feed di una fonte immagine personalizzata, utilizzando al contempo un dispositivo fotocamera fornito dall'SDK di trasmissione. Se si desidera utilizzare più fotocamere contemporaneamente, utilizzare `AVCaptureMultiCamSession` e fornire due fonti di immagini personalizzate.

Le fonti di immagini personalizzate devono essere utilizzate principalmente con contenuti statici come immagini o con contenuti video:

```
let customImageSource = broadcastSession.createImageSource(withName: "video")
try broadcastSession.attach(customImageSource, toSlotWithName: "custom")
```

## Controllare la connettività di rete
<a name="broadcast-ios-network-connection"></a>

Capita spesso che durante gli spostamenti i dispositivi mobili perdano temporaneamente e riacquistino la connettività di rete. Per questo motivo, è importante monitorare la connettività di rete dell'app e rispondere in modo appropriato quando si verificano cambiamenti. 

Quando la connessione della trasmissione viene persa, lo stato dell'SDK di trasmissione cambierà in `error` e poi in `disconnected`. Si riceverà una notifica riguardo a queste modifiche tramite `IVSBroadcastSessionDelegate`. Quando si ricevono queste modifiche dello stato:

1. Monitorare lo stato di connettività dell'app di trasmissione ed effettuare una chiamata `start` con l'endpoint e la chiave di flusso, una volta che la connessione è stata ripristinata.

1. **Importante:** monitorare il callback del delegato dello stato e assicurarsi che lo stato cambi in `connected` dopo avere chiamato nuovamente `start`.

## Scollegare un dispositivo
<a name="broadcast-ios-detach-device"></a>

Se si desidera distaccare e non sostituire un dispositivo, distaccarlo con `IVSDevice` o `IVSDeviceDescriptor`:

```
broadcastSession.detachDevice(currentCamera)
```

## Integrazione di ReplayKit
<a name="broadcast-ios-replaykit"></a>

Per riprodurre in streaming lo schermo del dispositivo e l'audio di sistema su iOS, è necessario effettuare un'integrazione con [ReplayKit](https://developer.apple.com/documentation/replaykit?language=objc). L'SDK di trasmissione di Amazon IVS semplifica l'integrazione di ReplayKit utilizzando `IVSReplayKitBroadcastSession`. Nella propria sottoclasse `RPBroadcastSampleHandler`, creare un'istanza di `IVSReplayKitBroadcastSession`, quindi:
+ Avviare la sessione in `broadcastStarted`
+ Interrompere la sessione in `broadcastFinished`

L'oggetto sessione avrà tre fonti personalizzate per le immagini dello schermo, l'audio dell'app e l'audio del microfono. Passare i `CMSampleBuffers` forniti nel `processSampleBuffer` a tali fonti personalizzate.

Per gestire l'orientamento del dispositivo, è necessario estrarre i metadati specifici di Replaykit dal buffer del sample. Eseguire il seguente codice:

```
let imageSource = session.systemImageSource;
if let orientationAttachment = CMGetAttachment(sampleBuffer, key: RPVideoSampleOrientationKey as CFString, attachmentModeOut: nil) as? NSNumber,
    let orientation = CGImagePropertyOrientation(rawValue: orientationAttachment.uint32Value) {
    switch orientation {
    case .up, .upMirrored:
        imageSource.setHandsetRotation(0)
    case .down, .downMirrored:
        imageSource.setHandsetRotation(Float.pi)
    case .right, .rightMirrored:
        imageSource.setHandsetRotation(-(Float.pi / 2))
    case .left, .leftMirrored:
        imageSource.setHandsetRotation((Float.pi / 2))
    }
}
```

Si può integrare ReplayKit utilizzando la `IVSBroadcastSession` al posto della `IVSReplayKitBroadcastSession`. Tuttavia, la variante specifica di Replaykit ha diverse modifiche per ridurre l'ingombro della memoria interna, così da non superare il tetto di memoria stabilito da Apple per le estensioni di trasmissione.

## Ottenere impostazioni di trasmissione suggerite
<a name="broadcast-ios-recommended-settings"></a>

Per valutare la connessione utente prima di avviare una trasmissione, utilizzare `IVSBroadcastSession.recommendedVideoSettings` per eseguire un breve test. Durante l'esecuzione del test, riceverai vari suggerimenti che sono ordinati dal più consigliato al meno raccomandato. In questa versione dell'SDK, non è possibile riconfigurare l'attuale `IVSBroadcastSession`, pertanto è necessario deallocarla e quindi crearne una nuova con le impostazioni consigliate. Si continuerà a ricevere `IVSBroadcastSessionTestResults` fino a che `result.status` è `Success` o `Error`. È possibile controllare lo stato di avanzamento mediante `result.progress`.

Amazon IVS supporta un bitrate massimo di 8,5 Mb/s (per i canali il cui `type` è `STANDARD` o `ADVANCED`), quindi il `maximumBitrate` restituito da questo metodo non supera mai 8,5 Mb/s. Per tenere in considerazione le piccole fluttuazioni nelle prestazioni di rete, il `initialBitrate` suggerito restituito da questo metodo è leggermente inferiore al bitrate reale misurato nel test. (Solitamente è sconsigliabile utilizzare il 100% della larghezza di banda disponibile.)

```
func runBroadcastTest() {
    self.test = session.recommendedVideoSettings(with: IVS_RTMPS_URL, streamKey: IVS_STREAMKEY) { [weak self] result in
        if result.status == .success {
            self?.recommendation = result.recommendations[0];
        }
    }
}
```

## Utilizzo della riconnessione automatica
<a name="broadcast-ios-auto-reconnect"></a>

IVS supporta la riconnessione automatica a una trasmissione se la trasmissione si interrompe inaspettatamente senza chiamare l'API `stop`, ad esempio in caso di perdita temporanea della connettività di rete. Per abilitare la riconnessione automatica, imposta la proprietà `enabled` su `IVSBroadcastConfiguration.autoReconnect` su `true`.

Quando qualcosa causa l'interruzione imprevista del flusso, l'SDK riprova fino a 5 volte, seguendo una strategia di backoff lineare. Notifica all'applicazione lo stato del nuovo tentativo tramite la funzione `IVSBroadcastSessionDelegate.didChangeRetryState`.

Dietro le quinte, la riconnessione automatica utilizza la funzionalità [stream-takeover](streaming-config.md#streaming-config-stream-takeover) di IVS aggiungendo un numero di priorità, che inizia con 1, alla fine della chiave di flusso fornita. Per tutta la durata dell'istanza `IVSBroadcastSession`, tale numero viene incrementato di 1 ogni volta che viene tentata una riconnessione. Ciò significa che se la connessione del dispositivo viene interrotta 4 volte durante una trasmissione e ogni perdita richiede 1-4 nuovi tentativi, la priorità dell'ultimo flusso in uscita potrebbe essere compresa tra 5 e 17. Per questo motivo, *consigliamo di non utilizzare l'acquisizione del flusso IVS da un altro dispositivo se per lo stesso canale nell'SDK è abilitata la riconnessione automatica*. Non ci sono garanzie sulla priorità utilizzata dall'SDK in quel momento e l'SDK proverà a riconnettersi con una priorità più alta se un altro dispositivo prende il controllo.

## Usare video in background
<a name="broadcast-ios-background-video"></a>

È possibile continuare una trasmissione non Relaykit, anche con l'applicazione in background.

Per risparmiare energia e mantenere reattive le applicazioni in primo piano, iOS offre l'accesso alla GPU a una sola applicazione alla volta. L'SDK di trasmissione di Amazon IVS utilizza la GPU in più fasi della pipeline video, inclusa la composizione di più sorgenti di ingresso, il ridimensionamento dell'immagine e la codifica dell'immagine. Mentre l'applicazione di trasmissione è in background, non vi è alcuna garanzia che l'SDK possa eseguire queste operazioni.

Per risolvere questo problema, utilizzare il metodo `createAppBackgroundImageSource`. Consente all'SDK di continuare a trasmettere video e audio in background. Restituisce un `IVSBackgroundImageSource`, che è un `IVSCustomImageSource` normale con una funzione `finish` aggiuntiva. Ogni `CMSampleBuffer` fornito alla sorgente di immagini in background è codificato alla frequenza di fotogrammi fornita dalla `IVSVideoConfiguration` originale. I timestamp sul `CMSampleBuffer` vengono ignorati.

L'SDK quindi dimensiona e codifica le immagini e le memorizza nella cache, eseguendo automaticamente il loop di quel feed quando l'applicazione entra in background. Quando l'applicazione torna in primo piano, i dispositivi immagine collegati diventano nuovamente attivi e il flusso pre-codificato interrompe il ciclo.

Per annullare questo processo, utilizzare `removeImageSourceOnAppBackgrounded`. Non è necessario chiamarlo a meno che non si desideri ripristinare esplicitamente il comportamento in background dell'SDK; in caso contrario, viene ripulito automaticamente alla deallocazione della `IVSBroadcastSession`.

**Note:** *si consiglia vivamente di chiamare questo metodo come parte della configurazione della sessione di trasmissione, prima che la sessione diventi attiva.* Il metodo è costoso (codifica video), quindi le prestazioni di una trasmissione in diretta mentre questo metodo è in esecuzione potrebbero risultare ridotte.

### Esempio: generazione di un'immagine statica per il video in background
<a name="background-video-example-static-image"></a>

La fornitura di una singola immagine alla fonte in background genera un GOP completo di quell'immagine statica.

Di seguito è riportato un esempio che utilizza CIImage:

```
// Create the background image source
guard let source = session.createAppBackgroundImageSource(withAttemptTrim: true, onComplete: { error in
    print("Background Video Generation Done - Error: \(error.debugDescription)")
}) else {
    return
}

// Create a CIImage of the color red.
let ciImage = CIImage(color: .red)

// Convert the CIImage to a CVPixelBuffer
let attrs = [
    kCVPixelBufferCGImageCompatibilityKey: kCFBooleanTrue,
    kCVPixelBufferCGBitmapContextCompatibilityKey: kCFBooleanTrue,
    kCVPixelBufferMetalCompatibilityKey: kCFBooleanTrue,
] as CFDictionary

var pixelBuffer: CVPixelBuffer!
CVPixelBufferCreate(kCFAllocatorDefault,
                    videoConfig.width,
                    videoConfig.height,
                    kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,
                    attrs,
                    &pixelBuffer)

let context = CIContext()
context.render(ciImage, to: pixelBuffer)

// Submit to CVPixelBuffer and finish the source
source.add(pixelBuffer)
source.finish()
```

In alternativa, invece di creare un'immagine CIImage a tinta unita, è possibile utilizzare immagini in bundle. L'unico codice mostrato qui è come convertire un UIImage in un CIImage da utilizzare con l'esempio precedente:

```
// Load the pre-bundled image and get it’s CGImage
guard let cgImage = UIImage(named: "image")?.cgImage else {
    return
}

// Create a CIImage from the CGImage
let ciImage = CIImage(cgImage: cgImage)
```

### Esempio: video con AVAssetImageGenerator
<a name="background-video-example-avassetimagegenerator"></a>

Si può utilizzare un `AVAssetImageGenerator` per generare `CMSampleBuffers` da un `AVAsset` (anche se non è un `AVAsset` del flusso HLS):

```
// Create the background image source
guard let source = session.createAppBackgroundImageSource(withAttemptTrim: true, onComplete: { error in
    print("Background Video Generation Done - Error: \(error.debugDescription)")
}) else {
    return
}

// Find the URL for the pre-bundled MP4 file
guard let url = Bundle.main.url(forResource: "sample-clip", withExtension: "mp4") else {
    return
}
// Create an image generator from an asset created from the URL.
let generator = AVAssetImageGenerator(asset: AVAsset(url: url))
// It is important to specify a very small time tolerance.
generator.requestedTimeToleranceAfter = .zero
generator.requestedTimeToleranceBefore = .zero

// At 30 fps, this will generate 4 seconds worth of samples.
let times: [NSValue] = (0...120).map { NSValue(time: CMTime(value: $0, timescale: CMTimeScale(config.video.targetFramerate))) }
var completed = 0

let context = CIContext(options: [.workingColorSpace: NSNull()])

// Create a pixel buffer pool to efficiently feed the source
let attrs = [
    kCVPixelBufferPixelFormatTypeKey: kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,
    kCVPixelBufferCGImageCompatibilityKey: kCFBooleanTrue,
    kCVPixelBufferCGBitmapContextCompatibilityKey: kCFBooleanTrue,
    kCVPixelBufferMetalCompatibilityKey: kCFBooleanTrue,
    kCVPixelBufferWidthKey: videoConfig.width,
    kCVPixelBufferHeightKey: videoConfig.height,
] as CFDictionary
var pool: CVPixelBufferPool!
CVPixelBufferPoolCreate(kCFAllocatorDefault, nil, attrs, &pool)

generator.generateCGImagesAsynchronously(forTimes: times) { requestTime, image, actualTime, result, error in
    if let image = image {
        // convert to CIImage then CVpixelBuffer
        let ciImage = CIImage(cgImage: image)
        var pixelBuffer: CVPixelBuffer!
        CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, pool, &pixelBuffer)
        context.render(ciImage, to: pixelBuffer)
        source.add(pixelBuffer)
    }
    completed += 1
    if completed == times.count {
        // Mark the source finished when all images have been processed
        source.finish()
    }
}
```

È possibile generare `CVPixelBuffers` tramite un `AVPlayer` e `AVPlayerItemVideoOutput`. Tuttavia, ciò richiede l'utilizzo di un `CADisplayLink` e l'esecuzione è più vicina al tempo reale, mentre `AVAssetImageGenerator` può elaborare i fotogrammi molto più velocemente.

### Limitazioni
<a name="background-video-limitations"></a>

L'applicazione ha bisogno di [diritti audio in background](https://developer.apple.com/documentation/xcode/configuring-background-execution-modes) per evitare che venga sospesa dopo essere passata in background.

`createAppBackgroundImageSource` può essere chiamato solo mentre l'applicazione è in primo piano, poiché per il suo completamento è necessario accedere alla GPU.

`createAppBackgroundImageSource` codifica sempre in un GOP completo. Ad esempio, se si dispone di un intervallo di fotogrammi chiave di 2 secondi (impostazione di default) e si esegue a 30 fps, codifica un multiplo di 60 fotogrammi.
+ Se vengono forniti meno di 60 fotogrammi, l'ultimo fotogramma viene ripetuto fino al raggiungimento di 60 fotogrammi, indipendentemente dal valore dell'opzione di taglio.
+ Se vengono forniti più di 60 fotogrammi e l'opzione di rifinitura è `true`, gli ultimi N frame vengono eliminati, dove N è il resto del numero totale di fotogrammi inviati diviso per 60.
+ Se vengono forniti più di 60 fotogrammi e l'opzione di rifinitura è `false`, l'ultimo fotogramma viene ripetuto fino al raggiungimento del multiplo successivo di 60 fotogrammi.