Tous les produits
Search
Centre de documentation

ApsaraVideo Live:Implémenter un salon de discussion vocale sur iOS

Dernière mise à jour :Aug 08, 2026

Développez des applications interactives audio uniquement, telles que les appels vocaux et les salons de discussion vocale, en intégrant le SDK ARTC à votre projet iOS.

Concepts clés

Terme Définition
SDK ARTC Le SDK d'ApsaraVideo Real-time Communication (ARTC). Il ajoute des fonctionnalités audio et vidéo en temps réel à votre application.
Canal Un espace virtuel où les utilisateurs interagissent en temps réel.
Animateur Utilisateur pouvant publier et s'abonner aux flux audio et vidéo dans un canal. Correspond à AliRtcClientRole.roleInteractive dans le SDK.
Spectateur Utilisateur pouvant s'abonner aux flux audio et vidéo dans un canal, mais ne pouvant pas publier de flux. Correspond à AliRtcClientRole.rolelive dans le SDK.

Interaction entre canaux, animateurs et spectateurs

Tous les utilisateurs appellent joinChannel pour rejoindre un canal avant de publier ou de s'abonner aux flux.

Scénario Rôles Comportement
Appel audio uniquement Tous les utilisateurs sont des animateurs Chaque utilisateur peut publier et s'abonner aux flux.
Salon de discussion vocale Les animateurs publient des flux ; les spectateurs s'abonnent uniquement Appelez setClientRole pour attribuer les rôles.

Après avoir rejoint un canal :

  • Tous les utilisateurs peuvent recevoir les flux audio et vidéo des autres utilisateurs du même canal.

  • Seuls les animateurs peuvent publier des flux audio et vidéo dans le canal.

  • Pour permettre à un spectateur de publier un flux, appelez setClientRole afin de basculer son rôle vers celui d'animateur.

Exemple de projet

Téléchargez ou consultez le code source de l'exemple depuis le projet open source du SDK ARTC sur GitHub.

Prérequis

Avant de commencer, assurez-vous de disposer des éléments suivants :

  • Xcode 14.0 ou version ultérieure installé. Utilisez la dernière version officielle.

  • CocoaPods 1.9.3 ou version ultérieure installé.

  • Un appareil de test physique exécutant iOS 9.0 ou version ultérieure.

  • Une connexion réseau stable.

  • Un AppID et une AppKey ARTC. Pour plus d'informations, consultez la rubrique Créer une application.

  • Un projet configuré avec le SDK ARTC, incluant les autorisations audio et réseau. Pour plus d'informations, consultez la rubrique Implémenter un appel audio et vidéo.

Remarque

Utilisez un appareil physique pour les tests. Les simulateurs peuvent ne pas prendre en charge certaines fonctionnalités.

Implémenter l'interaction audio uniquement

1. Demander les autorisations

Demandez les autorisations d'accès à la caméra et au microphone avant de démarrer un appel. Le SDK vérifie automatiquement les autorisations, mais solliciter l'utilisateur dès le début offre une expérience plus fluide.

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. Obtenir un jeton d'authentification

La jointure d'un canal ARTC nécessite un jeton d'authentification. Les jetons peuvent être générés via une méthode à paramètre unique ou à paramètres multiples, ce qui détermine l'API joinChannel à appeler. Pour plus de détails, consultez la rubrique Implémenter l'authentification basée sur les jetons.

Environnements de production

Générez le jeton sur votre serveur et envoyez-le au client. Coder en dur la AppKey côté client présente un risque de sécurité.

Développement et débogage

Si votre serveur ne génère pas encore de jetons, vous pouvez utiliser temporairement la logique de génération de jetons issue de l'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. Créer et initialiser le moteur

Créer le moteur RTC

Appelez sharedInstance pour créer un objet moteur RTC.

private var rtcEngine: AliRtcEngine? = nil

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

Initialiser le moteur

Configurez le profil du canal, le rôle de l'utilisateur et les paramètres audio :

Méthode Objectif Valeur
setChannelProfile Définir le profil du canal sur le streaming live interactif AliRtcChannelProfile.interactivelive
setClientRole Attribuer le rôle de l'utilisateur Animateur : AliRtcClientRole.roleInteractive ; Spectateur : AliRtcClientRole.rolelive
setAudioProfile Définir la qualité audio et le mode de scénario 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. Configurer la publication et l'abonnement

Par défaut, le SDK publie et s'abonne automatiquement aux flux audio et vidéo dans le canal.

  • Une fois le rôle défini sur spectateur, la méthode publishLocalAudioStream devient invalide.

  • La configuration suivante s'applique aussi bien aux animateurs qu'aux spectateurs.

// 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. Rejoindre un canal

Appelez joinChannel pour rejoindre un canal et démarrer l'interaction audio uniquement.

Remarque

Si le jeton a été généré via la méthode à paramètre unique, appelez la méthode [joinChannel[1/3]](t2309760.xdita#758b964acc7jd) à paramètre unique. Si le jeton a été généré via la méthode à paramètres multiples, appelez la méthode [joinChannel[2/3]](t2309760.xdita#766e40a1cfk4p) à paramètres multiples. Le rappel onJoinChannelResult renvoie le résultat. Une valeur de 0 indique un succès. Toute autre valeur indique un échec — vérifiez la validité du jeton.

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

6. Mettre fin à l'interaction

Quittez le canal et détruisez le moteur pour libérer les ressources :

  1. Appelez leaveChannel pour quitter le canal.

  2. Appelez destroy pour détruire le moteur et libérer les ressources.

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

7. (Facultatif) Basculer entre les rôles de spectateur et d'animateur

Appelez setClientRole pour faire passer un spectateur au rôle d'animateur (pour publier des flux) ou un animateur au rôle de spectateur (pour arrêter la publication).

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

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

Gérer les rappels du moteur

Le SDK récupère automatiquement la plupart des exceptions. Pour les erreurs qu'il ne peut pas résoudre, le SDK notifie votre application via des rappels.

Cause Rappel et paramètres Solution
Échec de l'authentification onJoinChannelResult renvoie AliRtcErrJoinBadToken Vérifiez si le jeton est correct.
Le jeton est sur le point d'expirer onWillAuthInfoExpire Obtenez les dernières informations d'authentification, puis appelez refreshAuthInfo.
Le jeton a expiré onAuthInfoExpired Invitez l'utilisateur à rejoindre à nouveau le canal.
Erreur de connectivité réseau onConnectionStatusChange renvoie AliRtcConnectionStatusFailed Le SDK peut récupérer automatiquement après de brèves déconnexions réseau. Si la déconnexion dépasse le seuil de délai d'attente, vérifiez l'état du réseau et invitez l'utilisateur à rejoindre à nouveau le canal.
Déconnecté de force onBye AliRtcOnByeUserReplaced : Vérifiez si l'ID utilisateur est dupliqué. AliRtcOnByeBeKickedOut : L'utilisateur a été expulsé par le service. Rejoignez à nouveau le canal. AliRtcOnByeChannelTerminated : Le canal a été terminé. Rejoignez à nouveau le canal.
Exception de l'appareil local onLocalDeviceException Vérifiez les autorisations et l'état matériel de l'appareil.
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. */
        }
    }
}

Opérations connexes