

# Casos de uso avançados para o SDK de Transmissão para iOS do IVS \| Streaming de baixa latência
<a name="broadcast-ios-use-cases"></a>

Aqui, apresentamos alguns casos de uso avançados. Comece com a configuração básica acima e continue aqui.

## Criar uma configuração de transmissão do Android
<a name="broadcast-ios-create-configuration"></a>

Aqui, criamos uma configuração personalizada com dois slots de mixer que nos permitem vincular duas fontes de vídeo ao mixer. Um deles (`custom`) é tela cheia e fica posicionado atrás do outro (`camera`), que é menor e está localizado no canto inferior direito. Para o slot `custom`, não definimos uma posição, um tamanho ou um modo de aspecto. Como não definimos esses parâmetros, o slot usa as configurações de vídeo para tamanho e posição.

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

## Criar a sessão de transmissão (versão avançada)
<a name="broadcast-ios-create-session-advanced"></a>

Crie uma `IVSBroadcastSession`, como fez no [exemplo básico](broadcast-ios-getting-started.md#broadcast-ios-create-session), mas forneça sua configuração personalizada aqui. Forneça também `nil` para a matriz de dispositivos, pois vamos adicioná-los manualmente.

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

## Iterar e anexar um dispositivo de câmera
<a name="broadcast-ios-attach-camera"></a>

Aqui nós fazemos a iteração por meio dos dispositivos de entrada que o SDK detectou. O SDK retornará apenas dispositivos incorporados no iOS. Mesmo que os dispositivos de áudio Bluetooth estejam conectados, eles serão exibidos como um dispositivo interno.

Depois de encontrarmos um dispositivo que queremos usar, chamamos `attachDevice` para anexá-lo:

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

## Trocar câmeras
<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)")
        }
    }
}
```

## Criar uma fonte de entrada personalizada
<a name="broadcast-ios-create-input-source"></a>

Para inserir dados de som ou imagem gerados pela aplicação, use `createImageSource` ou `createAudioSource`. Os dois métodos criam dispositivos virtuais (`IVSCustomImageSource` e `IVSCustomAudioSource`) que podem ser vinculados ao mixer como qualquer outro dispositivo.

Os dispositivos retornados por ambos os métodos aceitam `CMSampleBuffer` por meio da função `onSampleBuffer`:
+ Para fontes de vídeo, o formato de pixel deve ser `kCVPixelFormatType_32BGRA`, `420YpCbCr8BiPlanarFullRange` ou `420YpCbCr8BiPlanarVideoRange`.
+ Para fontes de áudio, o buffer deve conter dados de PCM Linear.

Não é possível utilizar `AVCaptureSession` com entrada de câmera para alimentar uma fonte de imagem personalizada enquanto também usa um dispositivo de câmera fornecido pelo SDK de Transmissão. Se quiser usar várias câmeras simultaneamente, use `AVCaptureMultiCamSession` e forneça duas fontes de imagem personalizadas.

As fontes de imagem personalizadas devem ser usadas principalmente com conteúdo estático, como imagens, ou com conteúdo de vídeo:

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

## Monitorar a conectividade de rede
<a name="broadcast-ios-network-connection"></a>

É comum que os dispositivos móveis percam a conectividade de rede temporariamente e a recuperem enquanto estão em movimento. Por isso, é importante monitorar a conectividade de rede de sua aplicação e responder adequadamente quando as coisas mudam. 

Quando a conexão com o transmissor for perdida, o estado do SDK de Transmissão será alterado para `error` e depois para `disconnected`. Você será notificado sobre essas alterações pelo `IVSBroadcastSessionDelegate`. Ao receber essas alterações de estado:

1. Monitore o estado de conectividade de sua aplicação de transmissão e chame `start` com seu endpoint e chave de transmissão, assim que sua conexão for restaurada.

1. **Importante:** monitore o retorno de chamada de estado delegada e verifique se o estado é alterado para `connected` depois de chamar `start` novamente.

## Desconectar um dispositivo
<a name="broadcast-ios-detach-device"></a>

Se você deseja desconectar e não substituir um dispositivo, desconecte-o com `IVSDevice` ou `IVSDeviceDescriptor`:

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

## Integração com o ReplayKit
<a name="broadcast-ios-replaykit"></a>

Para transmitir a tela e o áudio do sistema do dispositivo no iOS, é necessário integrá-lo com o [ReplayKit](https://developer.apple.com/documentation/replaykit?language=objc). O SDK de Transmissão do Amazon IVS facilita a integração com o ReplayKit usando `IVSReplayKitBroadcastSession`. Na subclasse `RPBroadcastSampleHandler`, crie uma instância de `IVSReplayKitBroadcastSession`, então:
+ Iniciar a sessão em `broadcastStarted`
+ Interromper sessão em `broadcastFinished`

O objeto de sessão terá três fontes personalizadas para imagens da tela, áudio da aplicação e áudio do microfone. Passe o `CMSampleBuffers` fornecido em `processSampleBuffer` para essas fontes personalizadas.

Para lidar com a orientação do dispositivo, é necessário extrair metadados específicos do ReplayKit do buffer de amostra. Use o seguinte código:

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

É possível integrar o ReplayKit usando `IVSBroadcastSession` em vez de `IVSReplayKitBroadcastSession`. Porém, a variante específica do ReplayKit tem várias modificações para reduzir o espaço de memória interna, para permanecer dentro do teto de memória da Apple para extensões de transmissão.

## Obter configurações recomendadas de transmissão
<a name="broadcast-ios-recommended-settings"></a>

Para avaliar a conexão do usuário antes de iniciar uma transmissão, use `IVSBroadcastSession.recommendedVideoSettings` para executar um breve teste. À medida que o teste for executado, você receberá várias recomendações, ordenadas da mais recomendada para a menos recomendada. Nessa versão do SDK, não é possível reconfigurar a `IVSBroadcastSession` atual, então você precisará desalocá-la e criar uma nova com as configurações recomendadas. Você continuará recebendo `IVSBroadcastSessionTestResults` até que `result.status` seja `Success` ou `Error`. É possível verificar o progresso com `result.progress`.

O Amazon IVS é compatível com uma taxa de bits de até 8,5 Mbps (para canais cujo `type` seja `STANDARD` ou `ADVANCED`), de modo que a `maximumBitrate` retornada por esse método nunca excede 8,5 Mbps. Para considerar pequenas flutuações da performance da rede, o `initialBitrate` recomendado retornado por esse método é ligeiramente menor do que a taxa de bits verdadeira medida no teste. (Geralmente, não é aconselhável usar 100% da largura de banda disponível.)

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

## Uso da reconexão automática
<a name="broadcast-ios-auto-reconnect"></a>

O IVS oferece suporte à reconexão automática a uma transmissão caso a transmissão pare inesperadamente sem chamar a API `stop`; por exemplo, uma perda temporária na conectividade de rede. Para ativar a reconexão automática, defina a propriedade `enabled` em `IVSBroadcastConfiguration.autoReconnect` como `true`.

Quando algo faz com que o fluxo pare inesperadamente, o SDK tenta novamente até 5 vezes, seguindo uma estratégia de recuo linear. Isso notifica sua aplicação sobre o estado da nova tentativa por meio da função `IVSBroadcastSessionDelegate.didChangeRetryState`.

Nos bastidores, a reconexão automática usa a funcionalidade de [aquisição de fluxo](streaming-config.md#streaming-config-stream-takeover) do IVS, anexando um número de prioridade, começando com 1, ao final da chave de fluxo fornecida. Pela duração da instância `IVSBroadcastSession`, esse número é incrementado em 1 cada vez que uma reconexão é tentada. Isso significa que se a conexão do dispositivo for perdida 4 vezes durante uma transmissão e cada perda exigir de 1 a 4 tentativas, a prioridade da última transmissão poderá estar entre 5 e 17. Por isso, *recomendamos que você não use a aquisição de fluxo do IVS a partir de outro dispositivo enquanto a reconexão automática estiver ativada no SDK para o mesmo canal*. Não há garantias de qual prioridade o SDK estará usando no momento, e o SDK tentará se reconectar com uma prioridade mais alta se outro dispositivo assumir o controle.

## Como usar vídeo de plano de fundo
<a name="broadcast-ios-background-video"></a>

Você pode dar continuidade a uma transmissão sem RelayKit, mesmo que a sua aplicação esteja em segundo plano.

Para economizar energia e manter as aplicações em primeiro plano ágeis, o iOS só oferece acesso à GPU a uma aplicação por vez. O SDK de Transmissão do Amazon IVS usa a GPU em diversos estágios do pipeline de vídeo, inclusive para compor várias fontes de entrada, escalar a imagem e codificá-la. Embora a aplicação de transmissão esteja em segundo plano, não há garantia de que o SDK possa executar qualquer uma dessas ações.

Para solucionar isso, use o método `createAppBackgroundImageSource`. Ele permite que o SDK continue transmitindo vídeo e áudio enquanto está em segundo plano. Ele retorna uma `IVSBackgroundImageSource`, que é uma `IVSCustomImageSource` normal com a inclusão de uma função `finish`. Cada `CMSampleBuffer` fornecido à fonte da imagem de plano de fundo é codificado na proporção fornecida pela sua `IVSVideoConfiguration` original. Os timestamps no `CMSampleBuffer` são ignorados.

Em seguida, o SDK escala e codifica essas imagens e as armazena em cache, e faz um loop automático desse feed quando a sua aplicação entra em segundo plano. Quando a sua aplicação retorna ao primeiro plano, os dispositivos de imagem anexados voltam a ficar ativos e o looping da transmissão pré-codificada é interrompido.

Para desfazer esse processo, use `removeImageSourceOnAppBackgrounded`. Não é necessário chamar esse método, a não ser que você queira reverter explicitamente o comportamento em segundo plano do SDK, caso contrário, ele será automaticamente limpo na desalocação da `IVSBroadcastSession`.

**Observações:** *é altamente recomendável que você chame esse método como parte da configuração da sessão de transmissão antes que a sessão entre ao vivo.* O método é caro (codifica vídeo) e, por isso, a performance de uma transmissão ao vivo enquanto esse método estiver em execução poderá ser degradada.

### Exemplo: geração de uma imagem estática para vídeos de plano de fundo
<a name="background-video-example-static-image"></a>

Fornecer uma única imagem para a fonte de plano de fundo gera um GOP completo dessa imagem estática.

Veja um exemplo de uso de 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()
```

Como alternativa, em vez de criar um CIImage de uma cor sólida, você pode usar imagens em pacotes. O único código exibido aqui refere-se a como converter um UIImage em um CIImage para usar com a amostra anterior:

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

### Exemplo: vídeo com AVAssetImageGenerator
<a name="background-video-example-avassetimagegenerator"></a>

Você pode usar um `AVAssetImageGenerator` para gerar `CMSampleBuffers` de um `AVAsset` (e não de um `AVAsset` de uma transmissão de 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()
    }
}
```

É possível gerar `CVPixelBuffers` usando um `AVPlayer` e uma `AVPlayerItemVideoOutput`. Contudo, isso exige o uso de um `CADisplayLink` e realiza uma execução quase em tempo real, enquanto o `AVAssetImageGenerator` pode processar quadros muito mais rápido.

### Limitações
<a name="background-video-limitations"></a>

A sua aplicação precisa de [direitos de áudio em segundo plano](https://developer.apple.com/documentation/xcode/configuring-background-execution-modes) para evitar a suspensão ao entrar em segundo plano.

`createAppBackgroundImageSource`A só poderá ser chamada enquanto a sua aplicação estiver em primeiro plano, pois ela precisa de acesso à GPU para ser concluída.

`createAppBackgroundImageSource`A sempre codifica para um GOP completo. Por exemplo, se você tiver um intervalo de quadro-chave de dois segundos (o padrão) e estiver executando a 30 fps, ele codificará um múltiplo de 60 quadros.
+ Se menos de 60 quadros forem fornecidos, o último quadro será repetido até que 60 quadros sejam atingidos, independentemente do valor da opção de corte.
+ Se mais de 60 quadros forem fornecidos e a opção de corte for `true`, os últimos N quadros serão descartados, em que N é o restante do número total de quadros enviados dividido por 60.
+ Se mais de 60 quadros forem fornecidos e a opção de corte for `false`, o último quadro será repetido até que o próximo múltiplo de 60 quadros seja atingido.