

# 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. Para obter mais informações, consulte [Problemas conhecidos e soluções no SDK de Transmissão para iOS do IVS \| Streaming de baixa latência](broadcast-ios-issues.md).

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.