Todos os produtos
Search
Central de documentação

ApsaraVideo Live:Implement a voice chat room on iOS

Última atualização: Jun 30, 2026

Crie aplicações interativas apenas com áudio, como chamadas de voz e salas de bate-papo por voz, integrando o ARTC SDK ao seu projeto iOS.

Conceitos principais

Termo

Definição

ARTC SDK

SDK do ApsaraVideo Real-time Communication (ARTC). Adiciona recursos de áudio e vídeo em tempo real à sua aplicação.

Canal

Espaço virtual onde os usuários interagem em tempo real.

Streamer

Usuário que pode publicar e assinar streams de áudio e vídeo em um canal. Corresponde a AliRtcClientRole.roleInteractive no SDK.

Viewer

Usuário que pode assinar streams de áudio e vídeo em um canal, mas não pode publicar streams. Corresponde a AliRtcClientRole.rolelive no SDK.

Como canais, streamers e viewers interagem

Todos os usuários devem chamar joinChannel para entrar em um canal antes de publicar ou assinar streams.

Cenário

Funções

Comportamento

Chamada apenas de áudio

Todos os usuários são streamers

Cada usuário pode publicar e assinar streams.

Sala de bate-papo por voz

Streamers publicam streams; viewers apenas assinam

Chame setClientRole para atribuir as funções.

Após entrar em um canal:

  • Todos os usuários recebem streams de áudio e vídeo de outros participantes do mesmo canal.

  • Apenas streamers podem publicar streams de áudio e vídeo no canal.

  • Para permitir que um viewer publique um stream, chame setClientRole para alterar a função do usuário para streamer.

Projeto de exemplo

Baixe ou visualize o source de exemplo no projeto open source do ARTC SDK no GitHub.

Pré-requisitos

Antes de começar, verifique se você tem:

  • Xcode 14.0 ou posterior instalado. Use a versão oficial mais recente.

  • CocoaPods 1.9.3 ou posterior instalado.

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

  • Conexão de rede estável.

  • AppID e AppKey do ARTC. Para mais informações, consulte Criar uma aplicação.

  • Projeto configurado com o ARTC SDK, incluindo permissões de áudio e rede. Para mais informações, consulte Implementar uma chamada de áudio e vídeo.

Nota

Use um dispositivo físico para testes. Simuladores podem não oferecer suporte a determinados recursos.

Implemente a interação apenas com áudio

1. Solicite permissões

Solicite as permissões de câmera e microfone antes de iniciar uma chamada. O SDK verifica as permissões automaticamente, mas solicitá-las antecipadamente proporciona uma experiência mais 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. Obtenha um token de autenticação

Entrar em um canal ARTC exige um token de autenticação. É possível gerar tokens pelo método de parâmetro único ou multiparâmetro, o que determina qual API joinChannel chamar. Para detalhes, consulte Implementar autenticação baseada em token.

Ambientes de produção

Gere o token no servidor e envie-o ao cliente. Codificar o AppKey diretamente no lado do cliente representa um risco de segurança.

Desenvolvimento e depuração

Se o servidor ainda não gera tokens, use temporariamente a lógica de geração de tokens do APIExample:

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. Crie e inicialize o mecanismo

Crie o mecanismo RTC

Chame sharedInstance para criar um objeto de mecanismo RTC.

private var rtcEngine: AliRtcEngine? = nil

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

Inicialize o mecanismo

Configure o perfil do canal, a função do usuário e as definições de áudio:

Método

Finalidade

Valor

setChannelProfile

Defina o perfil do canal como transmissão ao vivo interativa

AliRtcChannelProfile.interactivelive

setClientRole

Atribui a função do usuário

Streamer: AliRtcClientRole.roleInteractive; Viewer: AliRtcClientRole.rolelive

setAudioProfile

Defina a qualidade de áudio e o modo de cenário

AliRtcAudioProfile.engineHighQualityMode, AliRtcAudioScenario.sceneMusicMode

// Set the channel profile to interactive live streaming. AliRtcInteractivelive is used for all RTC scenarios.
engine.setChannelProfile(AliRtcChannelProfile.interactivelive)
// Set the role.
if self.isAnchor {
    // For streamer mode, which requires publishing audio and video streams, set the role to AliRtcClientRoleInteractive.
    engine.setClientRole(AliRtcClientRole.roleInteractive)
}
else {
    // For viewer mode, which does not require publishing audio and video streams, set the role to AliRtcClientRolelive.
    engine.setClientRole(AliRtcClientRole.rolelive)
}

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

4. Configure a publicação e a assinatura

Por padrão, o SDK publica e assina automaticamente streams de áudio e vídeo no canal.

  • Depois que a função é definida como viewer, o método publishLocalAudioStream torna-se inválido.

  • A configuração a seguir aplica-se tanto a streamers quanto a viewers.

// Set the audio profile. By default, high-quality mode (AliRtcEngineHighQualityMode) and music mode (AliRtcAudioScenario.sceneMusicMode) are used.
engine.setAudioProfile(AliRtcAudioProfile.engineHighQualityMode, audio_scene: AliRtcAudioScenario.sceneMusicMode)

// For a voice chat scenario, you do not need to publish a video stream.
engine.publishLocalVideoStream(false)

// Set the default subscription to remote audio streams.
engine.setDefaultSubscribeAllRemoteAudioStreams(true)
engine.subscribeAllRemoteAudioStreams(true)

5. Entre em um canal

Chame joinChannel para entrar em um canal e iniciar a interação apenas com áudio.

Nota

Se o token foi gerado pelo método de parâmetro único, chame o método [joinChannel[1/3]](t2309760.xdita#758b964acc7jd) de parâmetro único. Se o token foi gerado pelo método multiparâmetro, chame o método [joinChannel[2/3]](t2309760.xdita#766e40a1cfk4p) multiparâmetro. O callback onJoinChannelResult retorna o resultado. Um resultado igual a 0 indica sucesso. Qualquer outro valor indica falha — verifique se o token é válido.

self.rtcEngine?.joinChannel(joinToken, channelId: nil, userId: nil, name: nil)

6. Encerre a interação

Saia do canal e destrua o mecanismo para liberar recursos:

  1. Chame leaveChannel para sair do canal.

  2. Chame destroy para destruir o mecanismo e liberar recursos.

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

7. (Opcional) Alterne entre as funções de viewer e streamer

Chame setClientRole para alternar um viewer para streamer (para publicar streams) ou um streamer de volta para viewer (para parar de publicar).

// Switch to the streamer role.
self.rtcEngine?.setClientRole(AliRtcClientRole.roleInteractive)

// Switch to the viewer role.
self.rtcEngine?.setClientRole(AliRtcClientRole.rolelive)

Trate os callbacks do mecanismo

O SDK se recupera da maioria das exceções automaticamente. Para erros que não consegue resolver, o SDK notifica sua aplicação por meio de callbacks.

Causa

Callback e parâmetros

Solução

Falha na autenticação

onJoinChannelResult retorna AliRtcErrJoinBadToken

Verifique se o token está correto.

Token prestes a expirar

onWillAuthInfoExpire

Obtenha as informações de autenticação mais recentes e chame refreshAuthInfo.

Token expirado

onAuthInfoExpired

Faça o usuário entrar novamente no canal.

Erro de conectividade de rede

onConnectionStatusChange retorna AliRtcConnectionStatusFailed

O SDK se recupera automaticamente de breves desconexões de rede. Se a desconexão exceder o limiar de tempo limite, verifique o status da rede e faça o usuário entrar novamente no canal.

Desconectado forçadamente

onBye

AliRtcOnByeUserReplaced: Verifique se o ID do usuário está duplicado. AliRtcOnByeBeKickedOut: O serviço removeu o usuário. Entre novamente no canal. AliRtcOnByeChannelTerminated: O canal foi encerrado. Entre novamente no canal.

Exceção no dispositivo local

onLocalDeviceException

Verifique as permissões e o status do hardware 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: You must handle this callback. The token is about to expire. Your application must get a new token for the current channel and user, and then call refreshAuthInfo. */
    }

    func onAuthInfoExpired() {
        "onAuthInfoExpired".printLog()

        /* TODO: You must handle this callback. Prompt the user that the token has expired, and then leave the channel and release the engine. */
    }

    func onBye(_ code: Int32) {
        "onBye code: \(code)".printLog()

        /* TODO: You must handle this callback. Your business may trigger a scenario where different devices with the same user ID compete for access. */
    }

    func onLocalDeviceException(_ deviceType: AliRtcLocalDeviceType, exceptionType: AliRtcLocalDeviceExceptionType, message msg: String?) {
        "onLocalDeviceException deviceType: \(deviceType)  exceptionType: \(exceptionType)".printLog()

        /* TODO: You must handle this callback. We recommend that your application prompts the user about the device error. This callback is triggered only after the SDK has tried all recovery policies and still cannot continue. */
    }

    func onConnectionStatusChange(_ status: AliRtcConnectionStatus, reason: AliRtcConnectionStatusChangeReason) {
        "onConnectionStatusChange status: \(status)  reason: \(reason)".printLog()

        if status == .failed {
            /* TODO: You must handle this callback. We recommend that your application prompts the user. This callback is triggered only after the SDK has tried all recovery policies and still cannot continue. */
        }
        else {
            /* TODO: Optional. Add business logic, usually for data statistics or UI changes. */
        }
    }
}

Operações relacionadas