Publicación y suscripción con el SDK de transmisión para iOS de IVS
En esta sección se explican los pasos necesarios para publicar y suscribirse a una fase mediante una aplicación iOS.
Creación de vistas
Lo primero es usar el archivo ViewController.swift creado automáticamente para importar AmazonIVSBroadcast y, luego, agregar algunas @IBOutlets que vincular:
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!
Ahora creamos esas vistas y las vinculamos en Main.storyboard. Esta es la estructura de vistas que utilizaremos:
Para configurar AutoLayout, tenemos que personalizar tres vistas. La primera vista es Vista de colección de participantes (una UICollectionView). Alinee Inicial, Final e Inferior con respecto a Área segura. Alinee también Superior con respecto a Contenedor de controles.
La segunda vista es Contenedor de controles. Alinee Inicial, Final y Superior con respecto a Área segura:
La tercera y última vista es Vista de pila vertical. Alinee Superior, Inicial, Final e Inferior con respecto a Supervista. En cuanto al diseño, establezca el espaciado en 8 en lugar de en 0.
Las UIStackViews se encargarán del diseño de las vistas restantes. En las tres UIStackViews, seleccione la opción Relleno para Alineación y Distribución.
Por último, vinculemos estas vistas a nuestro ViewController. Desde arriba, asigne las siguientes vistas:
-
El campo de texto de unión se asigna a
textFieldToken. -
El botón de unión se asigna a
buttonJoin. -
La etiqueta de estado se asigna a
labelState. -
El botón de publicación se asigna a
switchPublish. -
Vista de colección de participantes se asigna a
collectionViewParticipants.
Aproveche también para establecer el dataSource del elemento Vista de colección de participantes en el ViewController propietario:
Ahora creamos la subclase UICollectionViewCell en la que se renderizarán los participantes. Lo primero es crear un nuevo archivo Cocoa Touch Class:
Asígnele el nombre ParticipantUICollectionViewCell y conviértala en una subclase de UICollectionViewCell en Swift. Volvemos al archivo de Swift y creamos @IBOutlets que vincular:
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!
En el archivo XIB asociado, cree esta jerarquía de vistas:
En AutoLayout, volveremos a modificar tres vistas. La primera vista es Vista previa de contenedores. Alinee Final, Inicial, Superior e Inferior con respecto a Celda de vista de colección de participantes.
La segunda vista es Vista. Alinee Inicial y Superior con respecto a Celda de vista de colección de participantes y cambie el valor a 4.
La tercera vista es Vista de pila. Alinee Final, Inicial, Superior e Inferior con respecto a Supervista y cambie el valor a 4.
Permisos y temporizador de inactividad
En ViewController, tenemos que desactivar el temporizador de inactividad del sistema para evitar que el dispositivo entre en estado de suspensión mientras se esté utilizando nuestra aplicación:
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 }
A continuación, solicitamos los permisos de cámara y micrófono del sistema:
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) } }
Estado de la aplicación
Tenemos que configurar collectionViewParticipants con el archivo de diseño que creamos anteriormente:
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") }
Para representar a cada participante, creamos una estructura simple llamada StageParticipant. Esta puede incluirse en el archivo ViewController.swift, aunque también se puede crear un archivo nuevo.
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 } }
Para hacer un seguimiento de esos participantes, los guardamos en una matriz que sea una propiedad privada de ViewController:
private var participants = [StageParticipant]()
Esta propiedad se utilizará para facilitar información al UICollectionViewDataSource que enlazamos en el guion gráfico anterior:
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'") } } }
Para ver su propia vista previa antes de unirse a un escenario, creamos inmediatamente un participante local:
override func viewDidLoad() { /* existing UICollectionView code */ participants.append(StageParticipant(isLocal: true, participantId: nil)) }
Esto hace que la celda de un participante se renderice inmediatamente cuando se ejecute la aplicación. La celda representa al participante local.
Los usuarios quieren poder verse antes de unirse a un escenario, por lo que, a continuación, implementamos el método setupLocalUser(). El código de gestión de permisos anterior se encarga de llamar a este método. Almacenamos la referencia de la cámara y el micrófono como objetos de 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) }
Con esto detectamos la cámara y el micrófono del dispositivo a través del SDK y los almacenamos en nuestro objeto streams local. A continuación, asignamos la matriz streams del primer participante (el participante local que creamos anteriormente) a streams. Por último, llamamos a participantsChanged con un index de 0 y un changeType con el valor updated. Esta es una función auxiliar para actualizar UICollectionView con bonitas animaciones. Este es el resultado:
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)]) } }
No se preocupe por cell.set aún, lo veremos luego, pero ahí es donde renderizaremos el contenido de la celda en función del participante.
ChangeType es una enumeración simple:
enum ChangeType { case joined, updated, left }
Lo último es ver si el escenario está conectado. Usamos un simple valor bool para comprobarlo. Cuando este valor se actualice, actualizará automáticamente nuestra interfaz de usuario.