Todos os produtos
Search
Central de documentação

ApsaraVideo Live:Implementar comunicação de áudio e vídeo no iOS

Última atualização: Jun 30, 2026

Este guia demonstra como integrar o ARTC SDK ao seu projeto iOS para criar uma aplicação de áudio e vídeo em tempo real, ideal para casos de uso como transmissões ao vivo interativas e chamadas de vídeo.

Conceitos principais

Antes de começar, revise os seguintes conceitos fundamentais:

  • ARTC SDK: SDK da Alibaba Cloud para interações de áudio e vídeo em tempo real.

  • GRTN: Rede Global de Transporte em Tempo Real da Alibaba Cloud, que oferece serviços de comunicação de áudio e vídeo com latência ultrabaixa, alta qualidade, segurança e confiabilidade.

  • canal: Sala virtual destinada a interações de áudio e vídeo em tempo real.

  • host: Função que permite ao usuário publicar fluxos de áudio e vídeo em um canal, além de assinar fluxos publicados por outros hosts.

  • viewer: Função que habilita o usuário a assinar fluxos de áudio e vídeo em um canal, sem permissão para publicar fluxos.

image
  1. Chame setChannelProfile para definir o cenário do canal e, em seguida, chame joinChannel para entrar em um canal:

    • No cenário de chamada de vídeo, todos os usuários assumem a função de host e podem publicar e assinar fluxos.

    • No cenário de transmissão interativa, é necessário chamar setClientRole para configurar a função do usuário. Defina a função como host para os usuários que irão publicar um fluxo. Caso o usuário precise apenas assinar um fluxo, defina sua função como viewer.

  2. Após entrar em um canal, a função do usuário determina se ele pode publicar ou assinar fluxos:

    • Todos os participantes de um canal podem assinar seus fluxos de áudio e vídeo.

    • Um host tem permissão para publicar fluxos de áudio e vídeo no canal.

    • Se um viewer precisar publicar um fluxo, deverá chamar o método setClientRole para alterar sua função para host.

Projeto de exemplo

O ARTC SDK fornece um projeto de exemplo open-source para aplicações de áudio e vídeo em tempo real.

Pré-requisitos

Antes de executar o projeto de exemplo, certifique-se de que seu ambiente de desenvolvimento atenda aos seguintes requisitos:

  • Ferramenta de desenvolvimento: Xcode 14.0 ou superior. Recomenda-se utilizar a versão estável mais recente.

  • Configuração recomendada: CocoaPods 1.9.3 ou superior.

  • Dispositivo de teste: Um dispositivo físico iOS executando iOS 9.0 ou posterior.

Nota

Utilize um dispositivo físico para testes, pois os simuladores podem não oferecer suporte a determinados recursos.

  • Rede: É necessária uma conexão de rede estável.

  • Configuração da aplicação: Obtenha o AppID e a AppKey da sua aplicação ApsaraVideo Real-time Communication. Para mais informações, consulte Criar aplicação.

Criar um projeto (opcional)

Esta seção descreve como criar um novo projeto e adicionar as permissões necessárias para interação de áudio e vídeo. Caso já possua um projeto, ignore esta etapa.

  1. Abra o Xcode, acesse File > New > Project e selecione o modelo App. Na tela seguinte, defina Interface como Storyboard e Language como Swift.

image.png

  1. Configure as definições do seu projeto, incluindo Bundle Identifier, Signing e Minimum Deployments.

Configurar o projeto

Etapa 3: Criar uma interface de usuário

Desenvolva uma interface de usuário adequada ao seu cenário de interação em tempo real. Para este exemplo de chamada de vídeo com múltiplos participantes, crie uma ScrollView. Quando um usuário entrar na chamada, adicione uma visualização de vídeo a esse contêiner. Ao sair, remova a respectiva visualização e atualize o layout.

Exemplo de código

class VideoCallMainVC: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        self.title = self.channelId
        
        self.setup()
        self.startPreview()
        self.joinChannel()
    }
    
    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        
        self.leaveAnddestroyEngine()
    }
    
    @IBOutlet weak var contentScrollView: UIScrollView!
    var videoViewList: [VideoView] = []

    // Create a video call render view and add it to contentScrollView
    func createVideoView(uid: String) -> VideoView {
        let view = VideoView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
        view.uidLabel.text = uid
        
        self.contentScrollView.addSubview(view)
        self.videoViewList.append(view)
        self.updateVideoViewsLayout()
        return view
    }

    // Remove a video call render view from contentScrollView.
    func removeVideoView(uid: String) {
        let videoView = self.videoViewList.first { $0.uidLabel.text == uid }
        if let videoView = videoView {
            videoView.removeFromSuperview()
            self.videoViewList.removeAll(where: { $0 == videoView})
            self.updateVideoViewsLayout()
        }
    }
    // Refresh the layout of the subviews in contentScrollView.
    func updateVideoViewsLayout() {
        let margin = 24.0
        let width = (self.contentScrollView.bounds.width - margin * 3.0) / 2.0
        let height = width // width * 16.0 / 9.0
        let count = 2
        for i in 0..<self.videoViewList.count {
            let view = self.videoViewList[i]
            let x = Double(i % count) * (width + margin) + margin
            let y = Double(i / count) * (height + margin) + margin
            view.frame = CGRect(x: x, y: y, width: width, height: height)
        }
        self.contentScrollView.contentSize = CGSize(width: self.contentScrollView.bounds.width, height: margin + Double(self.videoViewList.count / count + 1) * height + margin)
    }
}

Implementação

Esta seção explica como utilizar o ARTC SDK para desenvolver uma aplicação básica de áudio e vídeo em tempo real. Copie o exemplo de código para testar rapidamente e siga as etapas para compreender as chamadas essenciais da API.

O diagrama abaixo ilustra o fluxo de trabalho básico de uma chamada de áudio e vídeo em tempo real:

image

Exemplo de código para o cenário de chamada de vídeo

Exemplo de código

class VideoCallMainVC: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        self.title = self.channelId

        self.setup()
        self.startPreview()
        self.joinChannel()
    }

    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)

        self.leaveAnddestroyEngine()
    }

    @IBOutlet weak var contentScrollView: UIScrollView!
    var videoViewList: [VideoView] = []

    var channelId: String = ""
    var userId: String = ""

    var rtcEngine: AliRtcEngine? = nil

    var joinToken: String? = nil

    func setup() {

        // Create and initialize the engine.
        let engine = AliRtcEngine.sharedInstance(self, extras:nil)

        // Set log level.
        engine.setLogLevel(.info)

        // Set the channel profile to Interactive Mode. Use AliRtcInteractivelive for all RTC scenarios.
        engine.setChannelProfile(AliRtcChannelProfile.interactivelive)
        // Set the client role. Use AliRtcClientRoleInteractive for users who need to both publish and subscribe. Use AliRtcClientRolelive for users who only subscribe.
        engine.setClientRole(AliRtcClientRole.roleInteractive)

        // Set the audio profile. The default is high-quality mode (AliRtcEngineHighQualityMode) and music scenario (AliRtcSceneMusicMode).
        engine.setAudioProfile(AliRtcAudioProfile.engineHighQualityMode, audio_scene: AliRtcAudioScenario.sceneMusicMode)

        // Set the video encoding parameters.
        let config = AliRtcVideoEncoderConfiguration()
        config.dimensions = CGSize(width: 720, height: 1280)
        config.frameRate = 20
        config.bitrate = 1200
        config.keyFrameInterval = 2000
        config.orientationMode = AliRtcVideoEncoderOrientationMode.adaptive
        engine.setVideoEncoderConfiguration(config)
        engine.setCapturePipelineScaleMode(.post)

        // By default, the SDK publishes the audio stream. Calling publishLocalAudioStream(true) is optional.
        engine.publishLocalVideoStream(true)
        // By default, the SDK publishes the video stream. For a video call, calling publishLocalVideoStream(true) is optional.
        // To stop publishing video for a voice-only call, call publishLocalVideoStream(false).
        engine.publishLocalAudioStream(true)

        // Set the default to subscribe to all remote audio and video streams.
        engine.setDefaultSubscribeAllRemoteAudioStreams(true)
        engine.subscribeAllRemoteAudioStreams(true)
        engine.setDefaultSubscribeAllRemoteVideoStreams(true)
        engine.subscribeAllRemoteVideoStreams(true)

        self.rtcEngine = engine
    }

    func joinChannel() {

        // Join with a single-parameter token
        if let joinToken = self.joinToken {
            let msg =  "JoinWithToken: \(joinToken)"

            let param = AliRtcChannelParam()
            let ret = self.rtcEngine?.joinChannel(joinToken, channelParam: param) { [weak self] errCode, channelId, userId, elapsed in
                                                                                   if errCode == 0 {
                                                                                       // success

                                                                                   }
                                                                                   else {
                                                                                       // failed
                                                                                   }

                                                                                   let resultMsg = "\(msg) \n CallbackErrorCode: \(errCode)"
                                                                                   resultMsg.printLog()
                                                                                   UIAlertController.showAlertWithMainThread(msg: resultMsg, vc: self!)
            }
            
            let resultMsg = "\(msg) \n ReturnErrorCode: \(ret ?? 0)"
            resultMsg.printLog()
            if ret != 0 {
                UIAlertController.showAlertWithMainThread(msg: resultMsg, vc: self)
            }
            return
        }
    }
    
    func startPreview() {
        let videoView = self.createVideoView(uid: self.userId)
        
        let canvas = AliVideoCanvas()
        canvas.view = videoView.canvasView
        canvas.renderMode = .auto
        canvas.mirrorMode = .onlyFrontCameraPreviewEnabled
        canvas.rotationMode = ._0
        
        self.rtcEngine?.setLocalViewConfig(canvas, for: AliRtcVideoTrack.camera)
        self.rtcEngine?.startPreview()
    }
    
    func leaveAnddestroyEngine() {
        self.rtcEngine?.stopPreview()
        self.rtcEngine?.leaveChannel()
        AliRtcEngine.destroy()
        self.rtcEngine = nil
    }
    
    // Create a video call render view and add it to contentScrollView.
    func createVideoView(uid: String) -> VideoView {
        let view = VideoView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
        view.uidLabel.text = uid
        
        self.contentScrollView.addSubview(view)
        self.videoViewList.append(view)
        self.updateVideoViewsLayout()
        return view
    }
    
    // Remove a video call render view from contentScrollView.
    func removeVideoView(uid: String) {
        let videoView = self.videoViewList.first { $0.uidLabel.text == uid }
        if let videoView = videoView {
            videoView.removeFromSuperview()
            self.videoViewList.removeAll(where: { $0 == videoView})
            self.updateVideoViewsLayout()
        }
    }
    
    // Refresh the layout of the subviews in contentScrollView.
    func updateVideoViewsLayout() {
        let margin = 24.0
        let width = (self.contentScrollView.bounds.width - margin * 3.0) / 2.0
        let height = width // width * 16.0 / 9.0
        let count = 2
        for i in 0..<self.videoViewList.count {
            let view = self.videoViewList[i]
            let x = Double(i % count) * (width + margin) + margin
            let y = Double(i / count) * (height + margin) + margin
            view.frame = CGRect(x: x, y: y, width: width, height: height)
        }
        self.contentScrollView.contentSize = CGSize(width: self.contentScrollView.bounds.width, height: margin + Double(self.videoViewList.count / count + 1) * height + margin)
    }
    
    /*
    // MARK: - Navigation

    // In a storyboard-based application, you will often want to do a little preparation before navigation
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        // Get the new view controller using segue.destination.
        // Pass the selected object to the new view controller.
    }
    */

}

extension VideoCallMainVC: AliRtcEngineDelegate {
    
    func onJoinChannelResult(_ result: Int32, channel: String, elapsed: Int32) {
        "onJoinChannelResult1 result: \(result)".printLog()
    }
    
    func onJoinChannelResult(_ result: Int32, channel: String, userId: String, elapsed: Int32) {
        "onJoinChannelResult2 result: \(result)".printLog()
    }
    
    func onRemoteUser(onLineNotify uid: String, elapsed: Int32) {
        // A remote user comes online. 
        "onRemoteUserOlineNotify uid: \(uid)".printLog()
    }
    
    func onRemoteUserOffLineNotify(_ uid: String, offlineReason reason: AliRtcUserOfflineReason) {
        // A remote user goes offline.
        "onRemoteUserOffLineNotify uid: \(uid) reason: \(reason)".printLog()
    }
    
    
    func onRemoteTrackAvailableNotify(_ uid: String, audioTrack: AliRtcAudioTrack, videoTrack: AliRtcVideoTrack) {
        "onRemoteTrackAvailableNotify uid: \(uid) audioTrack: \(audioTrack)  videoTrack: \(videoTrack)".printLog()
        // Stream status of a remote user。
        if audioTrack != .no {
            let videoView = self.videoViewList.first { $0.uidLabel.text == uid }
            if videoView == nil {
                _ = self.createVideoView(uid: uid)
            }
        }
        if videoTrack != .no {
            var videoView = self.videoViewList.first { $0.uidLabel.text == uid }
            if videoView == nil {
                videoView = self.createVideoView(uid: uid)
            }
            
            let canvas = AliVideoCanvas()
            canvas.view = videoView!.canvasView
            canvas.renderMode = .auto
            canvas.mirrorMode = .onlyFrontCameraPreviewEnabled
            canvas.rotationMode = ._0
            self.rtcEngine?.setRemoteViewConfig(canvas, uid: uid, for: AliRtcVideoTrack.camera)
        }
        else {
            self.rtcEngine?.setRemoteViewConfig(nil, uid: uid, for: AliRtcVideoTrack.camera)
        }
        
        if audioTrack == .no && videoTrack == .no {
            self.removeVideoView(uid: uid)
            self.rtcEngine?.setRemoteViewConfig(nil, uid: uid, for: AliRtcVideoTrack.camera)
        }
    }
    
    func onAuthInfoWillExpire() {
        "onAuthInfoWillExpire".printLog()
        
        /* TODO: Must handle. The token is about to expire. Your app needs to get a new token for the current channel and user, then call refreshAuthInfo. */
    }
    
    func onBye(_ code: Int32) {
        "onBye code: \(code)".printLog()
        
        /* TODO: Must handle. This callback is triggered if another device logs in with the same UserID, kicking the current device out of the channel. */
    }
    
    func onLocalDeviceException(_ deviceType: AliRtcLocalDeviceType, exceptionType: AliRtcLocalDeviceExceptionType, message msg: String?) {
        "onLocalDeviceException deviceType: \(deviceType)  exceptionType: \(exceptionType)".printLog()

        /* TODO: Must handle. We recommend notifying the user of a device error. This callback is triggered when the SDK's internal recovery strategies have failed. */
    }
    
    func onConnectionStatusChange(_ status: AliRtcConnectionStatus, reason: AliRtcConnectionStatusChangeReason) {
        "onConnectionStatusChange status: \(status)  reason: \(reason)".printLog()

        if status == .failed {
            /* TODO: Must handle. We recommend notifying the user. This callback is triggered when the SDK's internal recovery strategies have failed. */
        }
        else {
            /* TODO: Optional. You can add business logic here, typically for analytics or UI updates. */
        }
    }
}

Para obter detalhes sobre o código de exemplo completo, consulte Executar projeto de demonstração do ARTC para iOS.

1. Solicitar permissões

Embora o SDK verifique as permissões necessárias ao iniciar uma chamada, recomenda-se validar previamente o acesso à câmera e ao microfone para garantir uma experiência de usuário fluida.

func checkMicrophonePermission(completion: @escaping (Bool) -> Void) {
    let status = AVCaptureDevice.authorizationStatus(for: .audio)
    
    switch status {
    case .notDetermined:
        AVCaptureDevice.requestAccess(for: .audio) { granted in
            completion(granted)
        }
    case .authorized:
        completion(true)
    default:
        completion(false)
    }
}

func checkCameraPermission(completion: @escaping (Bool) -> Void) {
    let status = AVCaptureDevice.authorizationStatus(for: .video)
    
    switch status {
    case .notDetermined:
        AVCaptureDevice.requestAccess(for: .video) { granted in
            completion(granted)
        }
    case .authorized:
        completion(true)
    default:
        completion(false)
    }
}

// Usage example
checkMicrophonePermission { granted in
    if granted {
        print("Microphone access granted.")
    } else {
        print("Microphone access denied.")
    }
}

checkCameraPermission { granted in
    if granted {
        print("Camera access granted.")
    } else {
        print("Camera access denied.")
    }
}

2. Obter um token de autenticação

A entrada em um canal ARTC exige um token de autenticação para validar a identidade do usuário. Para detalhes sobre a geração do token, consulte Implementar autenticação baseada em token. A criação do token pode ocorrer via método de parâmetro único ou multiparâmetro, e a escolha define qual API joinChannel deve ser invocada.

Para ambientes de produção:

Como a geração do token requer sua AppKey, codificá-la diretamente no lado do cliente representa um risco de segurança. Em produção, recomendamos fortemente gerar o token no servidor e enviá-lo ao cliente.

Para desenvolvimento e depuração:

Durante o desenvolvimento, caso seu servidor de negócios ainda não possua a lógica de geração de tokens, utilize temporariamente a lógica disponível no APIExample para criar um token provisório. O código de referência é o seguinte:

class ARTCTokenHelper: NSObject {

    /**
    * RTC AppId
    */
    public static let AppId = "<RTC AppId>"

    /**
    * RTC AppKey
    */
    public static let AppKey = "<RTC AppKey>"

    /**
    * Generate a multi-parameter token for joining a channel based on channelId, userId, and timestamp.
    */
    public func generateAuthInfoToken(appId: String = ARTCTokenHelper.AppId, appKey: String =  ARTCTokenHelper.AppKey, channelId: String, userId: String, timestamp: Int64) -> String {
        let stringBuilder = appId + appKey + channelId + userId + "\(timestamp)"
        let token = ARTCTokenHelper.GetSHA256(stringBuilder)
        return token
    }

    /**
    * Generate a single-parameter token for joining a channel based on channelId, userId, and nonce.
    */
    public func generateJoinToken(appId: String = ARTCTokenHelper.AppId, appKey: String =  ARTCTokenHelper.AppKey, channelId: String, userId: String, timestamp: Int64, nonce: String = "") -> String {
        let token = self.generateAuthInfoToken(appId: appId, appKey: appKey, channelId: channelId, userId: userId, timestamp: timestamp)

        let tokenJson: [String: Any] = [
            "appid": appId,
            "channelid": channelId,
            "userid": userId,
            "nonce": nonce,
            "timestamp": timestamp,
            "token": token
        ]

        if let jsonData = try? JSONSerialization.data(withJSONObject: tokenJson, options: []),
        let base64Token = jsonData.base64EncodedString() as String? {
            return base64Token
        }

        return ""
    }

    /**
    * Sign a string using SHA256.
    * String signing (SHA256)
    */
    private static func GetSHA256(_ input: String) -> String {
        // Convert input string to data.
        let data = Data(input.utf8)

        // Create a buffer to store the hash result.
        var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))

        // Calculate the SHA-256 hash.
        data.withUnsafeBytes {
            _ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &hash)
        }

        // Convert the hash to a hexadecimal string.
        return hash.map { String(format: "%02hhx", $0) }.joined()
    }

}

3. Importar o módulo ARTC SDK

// Import the ARTC module.
import AliVCSDK_ARTC

4. Criar e inicializar o mecanismo

  • Criar o mecanismo RTC

    Invoque o método sharedInstance para instanciar um objeto AliRtcEngine.

    private var rtcEngine: AliRtcEngine? = nil
    
    // Create the engine and set the delegate.
    let engine = AliRtcEngine.sharedInstance(self, extras:nil)
    ...
    self.rtcEngine = engine
  • Inicializar o mecanismo

    • Execute setChannelProfile para configurar o canal como AliRTCInteractiveLive (modo interativo).

      Conforme suas necessidades de negócio, escolha o modo interativo (adequado para cenários de entretenimento) ou o modo de comunicação (ideal para transmissões um-para-um ou um-para-muitos). A seleção correta garante melhor experiência do usuário e uso eficiente dos recursos de rede.

      Modo

      Publicação

      Assinatura

      Descrição

      Modo interativo

      • Restrito por função. Apenas usuários com a função host podem publicar fluxos.

      • Os participantes podem alternar funções flexivelmente durante a sessão.

      Sem restrições de função. Todos os participantes têm permissão para assinar fluxos.

      • No modo interativo, eventos como entrada ou saída de um host do canal, ou início de publicação de fluxo, são notificados aos viewers em tempo real. Por outro lado, as atividades dos viewers não são notificadas aos hosts, assegurando a continuidade da transmissão.

      • Neste modo, os hosts gerenciam a interação, enquanto os viewers apenas consomem conteúdo. Se houver possibilidade de mudança nos requisitos de negócio, considere adotar o modo interativo como padrão. Sua flexibilidade permite adaptar-se a diferentes demandas de interação ajustando as funções dos usuários.

      Modo de comunicação

      Sem restrições de função. Todos os participantes têm permissão para publicar fluxos.

      Sem restrições de função. Todos os participantes têm permissão para assinar fluxos.

      • No modo de comunicação, os participantes têm ciência da presença uns dos outros.

      • Embora este modo não diferencie funções de usuário, ele equivale funcionalmente à função host do modo interativo. O objetivo é simplificar as operações, permitindo alcançar a funcionalidade desejada com menos chamadas de API.

    • Utilize setClientRole para definir a função do usuário como AliRTCSdkInteractive (host) ou AliRTCSdkLive (viewer). A função host publica e assina fluxos por padrão, enquanto a função viewer apenas assina, tendo a pré-visualização local e a publicação desativadas inicialmente.

      Nota: Ao alternar de host para viewer, o sistema interrompe a publicação dos fluxos locais de áudio e vídeo, mas as assinaturas existentes permanecem ativas. Na transição inversa (viewer para host), o sistema inicia a publicação dos fluxos locais, sem impactar as assinaturas já estabelecidas.

      // Set the channel profile to Interactive Mode. Use AliRtcInteractivelive for all RTC scenarios.
      engine.setChannelProfile(AliRtcChannelProfile.interactivelive)
      // Set the client role. Use AliRtcClientRoleInteractive for users who need to both publish and subscribe. Use AliRtcClientRolelive for users who only subscribe.
      engine.setClientRole(AliRtcClientRole.roleInteractive)
  • Implementar callbacks comuns

    Caso o SDK enfrente problemas durante a operação, ele tentará primeiramente uma recuperação automática através de mecanismos internos de nova tentativa. Para erros insolúveis internamente, o SDK notificará sua aplicação por meio de interfaces de callback predefinidas.

    Abaixo estão os principais callbacks para situações que o SDK não consegue resolver automaticamente, exigindo monitoramento e resposta da sua aplicação:

    Causa da exceção

    Callback e parâmetros

    Solução

    Descrição

    Falha na autenticação

    result em onJoinChannelResult retorna AliRtcErrJoinBadToken.

    A aplicação deve verificar se o token está correto.

    Quando um usuário chama uma API e a autenticação falha, o callback da API retornará um erro de falha de autenticação.

    Token prestes a expirar

    onAuthInfoWillExpire

    Obtenha um novo token e chame refreshAuthInfo para atualizar as informações.

    Um erro de expiração de token pode ocorrer tanto na chamada de uma API quanto durante a execução. O erro é reportado via callbacks da API ou um callback de erro específico.

    Token expirado

    onAuthInfoExpired

    A aplicação precisa entrar novamente no canal.

    Um erro de expiração de token pode ocorrer tanto na chamada de uma API quanto durante a execução. O erro é reportado via callbacks da API ou um callback de erro específico.

    Problema de conexão de rede

    O callback onConnectionStatusChange retorna AliRtcConnectionStatusFailed.

    A aplicação precisa entrar novamente no canal.

    O SDK consegue se recuperar automaticamente de breves desconexões de rede. Se o tempo de desconexão exceder um determinado limiar, ocorrerá timeout. A aplicação deve verificar o status da rede e orientar o usuário a reconectar.

    Removido do canal

    onBye

    • AliRtcOnByeUserReplaced: Verifique se outro usuário entrou com o mesmo userId.

    • AliRtcOnByeBeKickedOut: O usuário foi removido do canal e precisa entrar novamente.

    • AliRtcOnByeChannelTerminated: O canal foi encerrado e o usuário precisa entrar novamente.

    O serviço RTC permite que um administrador remova participantes.

    Exceção de dispositivo local

    onLocalDeviceException

    Verifique as permissões da aplicação e se o hardware está funcionando corretamente.

    Quando ocorre uma exceção de dispositivo local que o SDK não consegue resolver, ele notifica a aplicação via callback. A aplicação deve então intervir para verificar o status do dispositivo.

    extension VideoCallMainVC: AliRtcEngineDelegate {
    
        func onJoinChannelResult(_ result: Int32, channel: String, elapsed: Int32) {
            "onJoinChannelResult1 result: \(result)".printLog()
        }
    
        func onJoinChannelResult(_ result: Int32, channel: String, userId: String, elapsed: Int32) {
            "onJoinChannelResult2 result: \(result)".printLog()
        }
    
        func onRemoteUser(onLineNotify uid: String, elapsed: Int32) {
            // A remote user comes online.
            "onRemoteUserOlineNotify uid: \(uid)".printLog()
        }
    
        func onRemoteUserOffLineNotify(_ uid: String, offlineReason reason: AliRtcUserOfflineReason) {
            // A remote user goes offline.
            "onRemoteUserOffLineNotify uid: \(uid) reason: \(reason)".printLog()
        }
    
        func onRemoteTrackAvailableNotify(_ uid: String, audioTrack: AliRtcAudioTrack, videoTrack: AliRtcVideoTrack) {
            "onRemoteTrackAvailableNotify uid: \(uid) audioTrack: \(audioTrack)  videoTrack: \(videoTrack)".printLog()
        }
    
        func onAuthInfoWillExpire() {
            "onAuthInfoWillExpire".printLog()
    
            /* TODO: Must handle. The token is about to expire. Your app needs to get a new token for the current channel and user, then call refreshAuthInfo. */
        }
    
        func onAuthInfoExpired() {
            "onAuthInfoExpired".printLog()
    
            /* TODO: Must handle. Notify the user that the token has expired, then leave the channel and destroy the engine. */
        }
    
        func onBye(_ code: Int32) {
            "onBye code: \(code)".printLog()
    
            /* TODO: Must handle. This callback is triggered if another device logs in with the same UserID, kicking the current device out of the channel. */
        }
    
        func onLocalDeviceException(_ deviceType: AliRtcLocalDeviceType, exceptionType: AliRtcLocalDeviceExceptionType, message msg: String?) {
            "onLocalDeviceException deviceType: \(deviceType)  exceptionType: \(exceptionType)".printLog()
    
            /* TODO: Must handle. We recommend notifying the user of a device error. This callback is triggered when the SDK's internal recovery strategies have failed.*/
        }
    
        func onConnectionStatusChange(_ status: AliRtcConnectionStatus, reason: AliRtcConnectionStatusChangeReason) {
            "onConnectionStatusChange status: \(status)  reason: \(reason)".printLog()
    
            if status == .failed {
                /* TODO: Must handle. We recommend notifying the user. This callback is triggered when the SDK's internal recovery strategies have failed. */
            }
            else {
                /* TODO: Optional. You can add business logic here, typically for analytics or UI updates. */
            }
        }
    }

5. Definir propriedades de áudio e vídeo

  • Configurar propriedades de áudio

    Use setAudioProfile para estabelecer o modo de codificação de áudio e o cenário desejado.

    // Set the audio profile. The default is high-quality mode (AliRtcEngineHighQualityMode) and music scenario (AliRtcSceneMusicMode).
    engine.setAudioProfile(AliRtcAudioProfile.engineHighQualityMode, audio_scene: AliRtcAudioScenario.sceneMusicMode)
  • Configurar propriedades de vídeo

    Defina os atributos do fluxo de vídeo publicado, como resolução, taxa de bits e taxa de quadros.

    // Set the video encoder configuration.
    let config = AliRtcVideoEncoderConfiguration()
    config.dimensions = CGSize(width: 720, height: 1280)
    config.frameRate = 20
    config.bitrate = 1200
    config.keyFrameInterval = 2000
    config.orientationMode = AliRtcVideoEncoderOrientationMode.adaptive
    engine.setVideoEncoderConfiguration(config)
    engine.setCapturePipelineScaleMode(.post)

6. Definir propriedades de publicação e assinatura

Configure a publicação de fluxos de áudio/vídeo e estabeleça a assinatura automática de todos os fluxos dos usuários como padrão:

  • Invoque publishLocalAudioStream para publicar um fluxo de áudio.

  • Invoque publishLocalVideoStream para publicar um fluxo de vídeo. Para chamadas apenas de áudio, defina este valor como false.

// By default, the SDK publishes the audio stream. Calling publishLocalAudioStream(true) is optional.
engine.publishLocalVideoStream(true)
// By default, the SDK publishes the video stream. For a video call, calling publishLocalVideoStream(true) is optional.
// To stop publishing video for a voice-only call, call publishLocalVideoStream(false).
engine.publishLocalAudioStream(true)

// Set the default to subscribe to all remote audio and video streams.
engine.setDefaultSubscribeAllRemoteAudioStreams(true)
engine.subscribeAllRemoteAudioStreams(true)
engine.setDefaultSubscribeAllRemoteVideoStreams(true)
engine.subscribeAllRemoteVideoStreams(true)
Nota

Por padrão, o SDK publica automaticamente os fluxos locais de áudio e vídeo e assina os fluxos de áudio e vídeo de todos os outros usuários no canal. Utilize os métodos acima para sobrescrever esse comportamento padrão.

7. Iniciar pré-visualização local

  • Chame setLocalViewConfig para configurar a visualização de renderização local e ajustar as propriedades de exibição do vídeo local.

  • Execute o método startPreview para iniciar a pré-visualização do vídeo local.

let videoView = self.createVideoView(uid: self.userId)

let canvas = AliVideoCanvas()
canvas.view = videoView.canvasView
canvas.renderMode = .auto
canvas.mirrorMode = .onlyFrontCameraPreviewEnabled
canvas.rotationMode = ._0

self.rtcEngine?.setLocalViewConfig(canvas, for: AliRtcVideoTrack.camera)
self.rtcEngine?.startPreview()

8. Entrar em um canal

Chame joinChannel para acessar o canal. Se o token foi gerado pelo método de parâmetro único, utilize a operação [joinChannel[3/3]](t2309850.xdita#7ab2c22015zro). O resultado será retornado no callback onJoinChannelResult. Um valor 0 indica sucesso na entrada. Valores diferentes de zero podem indicar um token inválido.

let ret = self.rtcEngine?.joinChannel(joinToken, channelId: nil, userId: nil, name: nil) { [weak self] errCode, channelId, userId, elapsed in
    if errCode == 0 {
        // success

    }
    else {
        // failed
    }
    
    let resultMsg = "\(msg) \n CallbackErrorCode: \(errCode)"
    resultMsg.printLog()
    UIAlertController.showAlertWithMainThread(msg: resultMsg, vc: self!)
}

let resultMsg = "\(msg) \n ReturnErrorCode: \(ret ?? 0)"
resultMsg.printLog()
if ret != 0 {
    UIAlertController.showAlertWithMainThread(msg: resultMsg, vc: self)
}
Nota
  • Após entrar no canal, o SDK publicará e assinará fluxos conforme os parâmetros definidos anteriormente.

  • O SDK realiza publicação e assinatura automaticamente por padrão, reduzindo a quantidade de chamadas de API necessárias no cliente.

9. Configurar a visualização remota

Quando um usuário remoto inicia ou interrompe a publicação de um fluxo, o callback onRemoteTrackAvailableNotify é acionado. Nesse callback, configure ou remova a visualização do usuário remoto. Exemplo de código:

func onRemoteTrackAvailableNotify(_ uid: String, audioTrack: AliRtcAudioTrack, videoTrack: AliRtcVideoTrack) {
    "onRemoteTrackAvailableNotify uid: \(uid) audioTrack: \(audioTrack)  videoTrack: \(videoTrack)".printLog()
    // Stream status of a remote user.
    if audioTrack != .no {
        let videoView = self.videoViewList.first { $0.uidLabel.text == uid }
        if videoView == nil {
            _ = self.createVideoView(uid: uid)
        }
    }
    if videoTrack != .no {
        var videoView = self.videoViewList.first { $0.uidLabel.text == uid }
        if videoView == nil {
            videoView = self.createVideoView(uid: uid)
        }
        
        let canvas = AliVideoCanvas()
        canvas.view = videoView!.canvasView
        canvas.renderMode = .auto
        canvas.mirrorMode = .onlyFrontCameraPreviewEnabled
        canvas.rotationMode = ._0
        self.rtcEngine?.setRemoteViewConfig(canvas, uid: uid, for: AliRtcVideoTrack.camera)
    }
    else {
        self.rtcEngine?.setRemoteViewConfig(nil, uid: uid, for: AliRtcVideoTrack.camera)
    }
    
    if audioTrack == .no && videoTrack == .no {
        self.removeVideoView(uid: uid)
        self.rtcEngine?.setRemoteViewConfig(nil, uid: uid, for: AliRtcVideoTrack.camera)
    }
}

10. Sair do canal e destruir o mecanismo

Ao finalizar a sessão, saia do canal e destrua o mecanismo para liberar recursos:

  1. Chame stopPreview para encerrar a pré-visualização de vídeo.

  2. Chame leaveChannel para sair do canal.

  3. Chame destroy para destruir o mecanismo e liberar os recursos associados.

self.rtcEngine?.stopPreview()
self.rtcEngine?.leaveChannel()
AliRtcEngine.destroy()
self.rtcEngine = nil

11. Demonstração do efeito

image.pngimage.png

Referências

Estrutura de dados

Classe AliRtcEngine