Tous les produits
Search
Centre de documentation

ApsaraVideo Live:Implémenter des communications audio/vidéo sur iOS

Dernière mise à jour :Aug 19, 2026

Ce guide explique comment intégrer le SDK ARTC dans votre projet iOS pour créer une application audio et vidéo en temps réel, adaptée à des cas d'utilisation tels que la diffusion en direct interactive et les appels vidéo.

Concepts clés

Avant de commencer, examinez les concepts clés suivants :

  • SDK ARTC : SDK d'Alibaba Cloud pour les interactions audio et vidéo en temps réel.

  • GRTN : Global Realtime Transport Network d'Alibaba Cloud, fournissant des services de communication audio et vidéo sécurisés, fiables, de haute qualité et à ultra-faible latence.

  • channel : Salle virtuelle destinée aux interactions audio et vidéo en temps réel.

  • host : Rôle permettant à un utilisateur de publier des flux audio et vidéo dans un channel et de s'abonner aux flux publiés par d'autres hosts.

  • viewer : Rôle permettant à un utilisateur de s'abonner aux flux audio et vidéo dans un channel, sans pouvoir publier de flux.

image
  1. Appelez setChannelProfile pour définir le scénario du channel, puis appelez joinChannel pour rejoindre un channel :

    • Dans un scénario d'appel vidéo, tous les utilisateurs ont le rôle host et peuvent publier et s'abonner aux flux.

    • Dans un scénario de diffusion interactive, vous devez appeler setClientRole pour définir le rôle de l'utilisateur. Définissez le rôle sur host pour les utilisateurs qui vont publier un flux. Si un utilisateur doit uniquement s'abonner à un flux, définissez son rôle sur viewer.

  2. Après avoir rejoint un channel, le rôle de l'utilisateur détermine s'il peut publier ou s'abonner aux flux :

    • Tous les utilisateurs d'un channel peuvent s'abonner à ses flux audio et vidéo.

    • Un host peut publier des flux audio et vidéo dans le channel.

    • Si un viewer doit publier un flux, il doit appeler la méthode setClientRole pour changer son rôle en host.

Exemple de projet

Le SDK ARTC fournit un exemple de projet open source pour les applications audio et vidéo en temps réel.

Prérequis

Avant d'exécuter l'exemple de projet, assurez-vous que votre environnement de développement répond aux exigences suivantes :

  • Outil de développement : Xcode 14.0 ou version ultérieure. Utilisez si possible la dernière version stable.

  • Configuration recommandée : CocoaPods 1.9.3 ou version ultérieure.

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

Remarque

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

  • Réseau : Une connexion réseau stable est requise.

  • Configuration de l'application : Obtenez l'AppID et l'AppKey de votre application ApsaraVideo Real-time Communication. Pour plus d'informations, consultez la rubrique Créer une application.

Créer un projet (facultatif)

Cette section décrit comment créer un nouveau projet et ajouter les autorisations nécessaires pour l'interaction audio et vidéo. Vous pouvez ignorer cette section si vous disposez déjà d'un projet.

  1. Ouvrez Xcode, accédez à File > New > Project, et sélectionnez le modèle App. Sur l'écran suivant, définissez Interface sur Storyboard et Language sur Swift.

image.png

  1. Configurez les paramètres de votre projet, y compris Bundle Identifier, Signing et Minimum Deployments.

Configurer votre projet

Étape 3 : Créer une interface utilisateur

Créez une interface utilisateur adaptée à votre scénario d'interaction en temps réel. Pour cet exemple d'appel vidéo multipersonne, créez un ScrollView. Lorsqu'un utilisateur rejoint l'appel, ajoutez une vue vidéo à ce conteneur. Lorsqu'un utilisateur quitte l'appel, supprimez sa vue vidéo et actualisez la mise en page.

Exemple de code

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)
    }
}

Implémentation

Cette section explique comment utiliser le SDK ARTC pour créer une application audio et vidéo en temps réel de base. Copiez l'exemple de code pour effectuer un test rapide, puis suivez les étapes pour comprendre les appels d'API principaux.

Le diagramme suivant illustre le flux de travail de base pour un appel audio et vidéo en temps réel :

image

Exemple de code pour un scénario d'appel vidéo

Exemple de code

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. */
        }
    }
}

Pour plus de détails sur l'exemple de code complet, consultez la rubrique Exécuter le projet de démonstration ARTC pour iOS.

1. Demander des autorisations

Bien que le SDK vérifie les autorisations nécessaires au démarrage d'un appel, nous vous recommandons de vérifier les autorisations d'accès à la caméra et au microphone avant d'initier un appel afin de garantir une expérience utilisateur 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 connexion à un channel ARTC nécessite un jeton d'authentification pour vérifier l'identité de l'utilisateur. Pour plus de détails sur la génération du jeton, consultez la rubrique Authentification par jeton. Un jeton peut être généré à l'aide d'une méthode à paramètre unique ou d'une méthode à plusieurs paramètres. La méthode utilisée détermine l'API joinChannel que vous devez appeler.

Pour les environnements de production :

Étant donné que la génération d'un jeton nécessite votre AppKey, le coder en dur côté client présente un risque de sécurité. Dans un environnement de production, nous vous recommandons vivement de générer le jeton sur votre serveur et de l'envoyer au client.

Pour le développement et le débogage :

Lors du développement, si votre serveur métier ne dispose pas encore de la logique de génération de jetons, vous pouvez utiliser temporairement la logique de génération de jetons d'APIExample pour créer un jeton temporaire. Le code de référence est le suivant :

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. Importer le module SDK ARTC

// Import the ARTC module.
import AliVCSDK_ARTC

4. Créer et initialiser le moteur

  • Créer le moteur RTC

    Appelez la méthode sharedInstance pour créer une instance d'AliRtcEngine.

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

    • Appelez setChannelProfile pour définir le canal sur AliRTCInteractiveLive (mode interactif).

      Selon vos besoins métier, vous pouvez opter pour le mode interactif, adapté aux scénarios de divertissement en direct, ou pour le mode communication, idéal pour les diffusions un-à-un ou un-à-plusieurs. Le choix du mode approprié garantit une expérience utilisateur fluide et une utilisation efficace des ressources réseau.

      Mode

      Publication

      Abonnement

      Description

      Mode interactif

      • Limité par le rôle. Seuls les utilisateurs ayant le rôle d'hôte peuvent publier des flux.

      • Les participants peuvent changer de rôle de manière flexible tout au long de la session.

      Aucune restriction de rôle. Tous les participants sont autorisés à s'abonner aux flux.

      • En mode interactif, les événements tels que l'arrivée ou le départ d'un hôte dans le canal, ou le début de la publication d'un flux, sont notifiés aux spectateurs en temps réel. Inversement, les activités des spectateurs ne sont pas notifiées aux hôtes, ce qui assure une diffusion ininterrompue.

      • Dans ce mode, les hôtes gèrent l'interaction, tandis que les spectateurs se contentent de consommer le contenu. Si vos besoins métier sont susceptibles d'évoluer, envisagez d'utiliser le mode interactif par défaut. Sa flexibilité vous permet de vous adapter à différentes exigences d'interaction en ajustant les rôles des utilisateurs.

      Mode communication

      Aucune restriction de rôle. Tous les participants sont autorisés à publier des flux.

      Aucune restriction de rôle. Tous les participants sont autorisés à s'abonner aux flux.

      • En mode communication, les participants sont conscients de la présence des autres.

      • Bien que ce mode ne différencie pas les rôles des utilisateurs, il est fonctionnellement équivalent au rôle d'hôte en mode interactif. L'objectif est de simplifier les opérations, permettant aux utilisateurs d'obtenir la fonctionnalité souhaitée avec moins d'appels API.

    • Appelez setClientRole pour définir le rôle de l'utilisateur sur AliRTCSdkInteractive (hôte) ou AliRTCSdkLive (spectateur). Par défaut, le rôle d'hôte publie et s'abonne aux flux, tandis que le rôle de spectateur ne fait que s'abonner, la prévisualisation locale et la publication étant désactivées.

      Remarque : Lorsqu'un utilisateur passe du rôle d'hôte à celui de spectateur, le système arrête la publication des flux audio et vidéo locaux, mais les abonnements existants ne sont pas affectés. Lorsqu'un utilisateur passe du rôle de spectateur à celui d'hôte, le système commence à publier les flux audio et vidéo locaux, sans affecter les abonnements existants.

      // 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)
  • Implémenter les rappels courants

    Si le SDK rencontre un problème lors de son exécution, il tente d'abord de se rétablir automatiquement grâce à ses mécanismes internes de nouvelle tentative. Pour les erreurs qu'il ne peut pas résoudre seul, le SDK notifie votre application via des interfaces de rappel prédéfinies.

    Voici les principaux rappels pour les problèmes que le SDK ne peut pas gérer, auxquels votre application doit écouter et répondre :

    Cause de l'exception

    Rappel et paramètres

    Solution

    Description

    Échec de l'authentification

    Le paramètre result dans onJoinChannelResult renvoie AliRtcErrJoinBadToken.

    L'application doit vérifier si le jeton est correct.

    Lorsqu'un utilisateur appelle une API, si l'authentification échoue, le rappel de l'API renvoie une erreur d'échec d'authentification.

    Expiration imminente du jeton

    onAuthInfoWillExpire

    Récupérez un nouveau jeton et appelez refreshAuthInfo pour mettre à jour les informations.

    Une erreur d'expiration de jeton peut survenir lors de l'appel d'une API ou pendant l'exécution. L'erreur est signalée via les rappels d'API ou un rappel d'erreur distinct.

    Jeton expiré

    onAuthInfoExpired

    L'application doit rejoindre à nouveau le canal.

    Une erreur d'expiration de jeton peut survenir lors de l'appel d'une API ou pendant l'exécution. L'erreur est signalée via les rappels d'API ou un rappel d'erreur distinct.

    Problème de connexion réseau

    Le rappel onConnectionStatusChange renvoie AliRtcConnectionStatusFailed.

    L'application doit rejoindre à nouveau le canal.

    Le SDK peut se rétablir automatiquement après de brèves déconnexions réseau. Si la durée de déconnexion dépasse un seuil, un délai d'attente se produit. L'application doit vérifier l'état du réseau et guider l'utilisateur pour qu'il rejoigne à nouveau le canal.

    Expulsion du canal

    onBye

    • AliRtcOnByeUserReplaced : Vérifiez si un autre utilisateur a rejoint avec le même userId.

    • AliRtcOnByeBeKickedOut : L'utilisateur a été expulsé du canal et doit le rejoindre à nouveau.

    • AliRtcOnByeChannelTerminated : Le canal a été terminé et l'utilisateur doit le rejoindre à nouveau.

    Le service RTC permet à un administrateur de supprimer des participants.

    Exception de l'appareil local

    onLocalDeviceException

    Vérifiez les permissions de l'application et le bon fonctionnement du matériel.

    Lorsqu'une exception de l'appareil local survient et que le SDK ne peut pas la résoudre, il en informe l'application via un rappel. L'application doit alors intervenir pour vérifier l'état 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: 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. Définir les propriétés audio et vidéo

  • Définir les propriétés audio

    Appelez setAudioProfile pour définir le mode d'encodage audio et le scénario.

    // Set the audio profile. The default is high-quality mode (AliRtcEngineHighQualityMode) and music scenario (AliRtcSceneMusicMode).
    engine.setAudioProfile(AliRtcAudioProfile.engineHighQualityMode, audio_scene: AliRtcAudioScenario.sceneMusicMode)
  • Définir les propriétés vidéo

    Définissez les propriétés du flux vidéo publié, telles que la résolution, le débit binaire et la fréquence d'images.

    // 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. Définir les propriétés de publication et d'abonnement

Configurez la publication des flux audio/vidéo et définissez l'abonnement par défaut à tous les flux des utilisateurs :

  • Appelez publishLocalAudioStream pour publier un flux audio.

  • Appelez publishLocalVideoStream pour publier un flux vidéo. Pour un appel audio uniquement, vous pouvez définir cette valeur sur 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)
Remarque

Par défaut, le SDK publie automatiquement les flux audio et vidéo locaux et s'abonne aux flux audio et vidéo de tous les autres utilisateurs du canal. Vous pouvez appeler les méthodes ci-dessus pour remplacer ce comportement par défaut.

7. Démarrer la prévisualisation locale

  • Appelez setLocalViewConfig pour configurer la vue de rendu locale et les propriétés d'affichage vidéo locales.

  • Appelez la méthode startPreview pour démarrer la prévisualisation vidéo locale.

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

Appelez joinChannel pour rejoindre le canal. Si le jeton a été généré à l'aide de la méthode à paramètre unique, appelez l'opération [joinChannel[3/3]](t2309850.xdita#7ab2c22015zro). Le résultat est renvoyé dans le rappel onJoinChannelResult. Une valeur result de 0 indique une jonction réussie. Une valeur non nulle peut indiquer un jeton invalide.

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)
}
Remarque
  • Après avoir rejoint le canal, le SDK publiera et s'abonnera aux flux selon les paramètres définis avant la jonction.

  • Le SDK publie et s'abonne automatiquement par défaut afin de réduire le nombre d'appels API que le client doit effectuer.

9. Définir la vue distante

Lorsqu'un utilisateur distant commence ou arrête de publier un flux, le rappel onRemoteTrackAvailableNotify est déclenché. Dans ce rappel, vous pouvez configurer ou supprimer la vue de l'utilisateur distant. Exemple de code :

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. Quitter le canal et détruire le moteur

Lorsque la session se termine, quittez le canal et détruisez le moteur pour libérer les ressources :

  1. Appelez stopPreview pour arrêter la prévisualisation vidéo.

  2. Appelez leaveChannel pour quitter le canal.

  3. Appelez destroy pour détruire le moteur et libérer les ressources associées.

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

11. Démonstration de l'effet

image.pngimage.png

Références

Structure de données

Classe AliRtcEngine