首页 > 解决方案 > 我是否使用 Apple 的 CallKit 框架进行 Voip 通话,以及当用户使用 callkit 按钮接受呼叫时,它在后台状态下如何工作?

问题描述

当用户当时从 CallKit 获取时,我在接受按钮单击 Call 时切换根,但不知何故根控制器对象总是发现 nil 并且应用程序崩溃

案例:当应用程序在后台运行并且手机状态被锁定时,就会发生这种情况。

func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {

    endCallTimer()
    guard let call = callManager.callWithUUID(uuid: action.callUUID) else {
        action.fail()
        return
    }


    let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
    let incomingCall = mainStoryboard.instantiateViewController(withIdentifier: "CallConnectedVC") as! IncomingController
    incomingCall.connectToCalling(duration:self.callDict["duration"] as! Int, consumerId: "\(self.callDict["consumerId"] as! Int)", categoryName: self.callDict["categoryTopic"] as! String, categoryImage: "", callId: self.callDict["callId"] as! String)
    let nav = UINavigationController(rootViewController: incomingCall)
    UIApplication.shared.keyWindow?.makeKeyAndVisible()
    UIApplication.shared.keyWindow?.rootViewController = nav


    configureAudioSession()
    call.answer()
    action.fulfill()

}

IncomingController代码 -

import UIKit
import TwilioVideo
import AVFoundation
import SDWebImage
import FirebaseAnalytics



class IncomingController: UIViewController {

    var camera: TVICameraCapturer?

    @IBOutlet weak var img_user: UIImageView!
    @IBOutlet weak var lblName: UILabel!
    @IBOutlet weak var lbltopic: UILabel!
    @IBOutlet weak var lblTimer: UILabel!
    @IBOutlet weak var btn_speaker : UIButton!
    @IBOutlet weak var btn_video: UIButton!
    @IBOutlet weak var btn_mute: UIButton!
    @IBOutlet weak var btn_extendcall: UIButton!
    @IBOutlet weak var btn_endcall: UIButton!

    //Video

    @IBOutlet weak var btn_toogleMic : UIButton!
    @IBOutlet weak var btnConnectAudio : UIButton!
    @IBOutlet weak var btnFlipCamera : UIButton!

    @IBOutlet weak var view_video: UIView!
    @IBOutlet weak var lblviedoName: UILabel!
    @IBOutlet weak var lblvideoTimer: UILabel!
    @IBOutlet weak var lblvideoTopic: UILabel!
    @IBOutlet weak var provider_previewView: TVIVideoView!
    @IBOutlet weak var provider_remoteView: TVIVideoView!
    @IBOutlet weak var provider_previewViewShadow: UIView!
    @IBOutlet weak var btnView : UIView!
    @IBOutlet weak var constrain_btnView: NSLayoutConstraint!

    //UpdatedVideo

    @IBOutlet weak var viewDisableVideo: UIView!
    @IBOutlet weak var lblDisableViedoName: UILabel!
    @IBOutlet weak var lblDisableVideoTimer: UILabel!
    @IBOutlet weak var lblDisableTopic: UILabel!
    @IBOutlet weak var lblDisableText: UILabel!
    @IBOutlet weak var imgUserDisable: UIImageView!

    var NotificationDict = NSMutableDictionary()
    var Calltimer: Timer? = Timer()
    var sec = 60
    var min = 6

    var audioDevice: TVIDefaultAudioDevice = TVIDefaultAudioDevice(block: {
        do {
            try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord, mode: AVAudioSessionModeVoiceChat, options: .mixWithOthers)
            try AVAudioSession.sharedInstance().setPreferredSampleRate(48000)
            try AVAudioSession.sharedInstance().setPreferredIOBufferDuration(0.01)
        } catch {
            print(error)
        }
    })

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.


    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        TwilioVideo.audioDevice = self.audioDevice
        self.prepareLocalMedia()
        connectToCalling(duration:NotificationDict["duration"] as! Int, consumerId: "\(NotificationDict["consumerId"] as! Int)", categoryName: NotificationDict["categoryTopic"] as! String, categoryImage: "", callId: NotificationDict["callId"] as! String)
       }

    //MARK:- Speaker Method
    func setAudioOutputSpeaker(_ enabled: Bool) {
        let session = AVAudioSession.sharedInstance()
        try? session.setCategory(AVAudioSessionCategoryPlayAndRecord)
        try? session.setMode(AVAudioSessionModeVoiceChat)
        if enabled {
            try? session.overrideOutputAudioPort(AVAudioSessionPortOverride.speaker)
        } else {
            try? session.overrideOutputAudioPort(AVAudioSessionPortOverride.none)
        }
        try? session.setActive(true)
    }



    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    //MARK:- Custom Method

    func connectToCalling(duration:Int, consumerId:String, categoryName:String, categoryImage:String, callId:String)
    {
        let Url = "\(Constant.tokenUrl)"
        let Param = ["duration":duration, "consumerId":consumerId, "categoryName":categoryName, "categoryImage":categoryImage, "callId":callId] as [String:Any]

        ApiResponse.onResponseKeyPost(url: Url, parms: Param as NSDictionary, completion: { (dict, errr) in
            print("dict responce",dict)
            if(errr == ""){
                OperationQueue.main.addOperation {
                    accessToken = dict["providerToken"] as! String
                    let connectOptions = TVIConnectOptions.init(token: accessToken) { (builder) in


                        if let videoTrack = ProvideCallObject.localVideoTrack {
                            builder.videoTracks = [videoTrack]
                        }

                        // We will share a local audio track only if ExampleAVAudioEngineDevice is selected.
                        if let audioTrack = ProvideCallObject.localAudioTrack {
                            builder.audioTracks = [audioTrack]
                        }

                        userDef.set("\(dict["roomId"]!)", forKey: "roomId")
                        builder.roomName = "\(dict["roomId"]!)"
                    }
                    ProvideCallObject.Provideroom = TwilioVideo.connect(with: connectOptions, delegate: self)
                }
            }
        })
    }




    @objc func updateCountDown() {

        if btn_endcall.isUserInteractionEnabled == false
        {
            btn_endcall.isUserInteractionEnabled = true
        }

        if sec == 0 {

            if min == 0
            {
                sec = 0
                min = 0
                goToFeedback()
                Calltimer?.invalidate()
            }else
            {
                sec = 59
                min = min - 1
                if min == 0 {
                    SystemSoundID.playFileNamed(fileName: "60 Seconds v2", withExtenstion: "m4a")
                }
            }
        }else
        {
            var timeString = ""
            sec = sec - 1

            if min < 10
            {
                timeString = timeString + "0" + String(min)

                if min == 2 && sec == 0{
                    SystemSoundID.playFileNamed(fileName: "2 Mins v2", withExtenstion: "m4a")
                }
            }
            else
            {
                timeString = timeString + String(min)
            }

            if sec < 10
            {
                timeString  = timeString + ":0" + String(sec)
            }
            else
            {
                timeString  = timeString + ":" + String(sec)
            }

            lblTimer.text = "\(timeString) Free Minutes"
            lblvideoTimer.text = "\(timeString) Free Minutes"
            lblDisableVideoTimer.text = "\(timeString) remaining"

        }
    }


    func prepareLocalMedia() {
        if (ProvideCallObject.localAudioTrack == nil) {
            ProvideCallObject.localAudioTrack = TVILocalAudioTrack.init(options: nil, enabled: true, name: "Microphone")

            if (ProvideCallObject.localAudioTrack == nil) {
                print("Failed to create audio track")
            }
        }
        if (ProvideCallObject.localVideoTrack == nil) {
            self.startPreview()
        }

        changeButtonImage(isVideoEnable: true)
    }

    //Video

    func setupRemoteVideoView() {
        self.provider_previewViewShadow.frame = CGRect(x: self.view_video.bounds.width - 132, y: self.view_video.bounds.height - 239, width: 112, height: 149)
        self.provider_previewView.frame = self.provider_previewViewShadow.bounds
        self.provider_remoteView.bringSubview(toFront: self.provider_previewViewShadow)
        self.provider_remoteView.isHidden = false
    }


    // MARK: Private
    func startPreview() {
        if PlatformUtils.isSimulator {
            return
        }
        camera = TVICameraCapturer(source: .frontCamera, delegate: self)
        ProvideCallObject.localVideoTrack = TVILocalVideoTrack.init(capturer: camera!, enabled: true, constraints: nil, name: "Camera")
        if (ProvideCallObject.localVideoTrack == nil) {
            print("Failed to create video track")
        } else {

            ProvideCallObject.localVideoTrack!.addRenderer(self.provider_previewView)
            let tap = UITapGestureRecognizer(target: self, action: #selector(IncomingController.flipCamera))
            self.provider_previewView.addGestureRecognizer(tap)
        }
    }

    @objc func flipCamera() {

        if (self.camera?.source == .frontCamera) {
            self.camera?.selectSource(.backCameraWide)
        } else {
            self.camera?.selectSource(.frontCamera)
        }
    }


    func cleanupRemoteParticipant() {
        if ((ProvideCallObject.remoteParticipant) != nil) {
            if ((ProvideCallObject.remoteParticipant?.videoTracks.count)! > 0) {
                let remoteVideoTrack = ProvideCallObject.remoteParticipant?.remoteVideoTracks[0].remoteTrack
                remoteVideoTrack?.removeRenderer(self.provider_remoteView!)
                self.provider_remoteView?.isHidden = true
            }
        }
        ProvideCallObject.remoteParticipant = nil
    }
}


// MARK:- TVIRoomDelegate
extension IncomingController : TVIRoomDelegate {

    func callDetails(room_sid:String,callID:String) {

        let params = ["room_sid":room_sid,"callId": callID,"isCallEnd":0] as [String : Any]
        ApiResponse.onResponsePost(url: Constant.callDetailsTwilio, parms: params as NSDictionary) { (response, error) in
        }
    }

    func didConnect(to room: TVIRoom) {

        ProvideCallObject.localParticipant = ProvideCallObject.Provideroom?.localParticipant
        ProvideCallObject.localParticipant?.delegate = self
        Calltimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateCountDown), userInfo: nil, repeats: true)
        NotificationDict["roomsid"] = room.sid


btn_video.isUserInteractionEnabled = true
-----------------------------------------

        if (room.remoteParticipants.count > 0) {
            ProvideCallObject.remoteParticipant = room.remoteParticipants[0]
            ProvideCallObject.remoteParticipant?.delegate = self
            self.callDetails(room_sid: room.sid, callID: NotificationDict["callId"] as! String)
        }

        if !isHeadPhoneAvailabel(){
            self.setAudioOutputSpeaker(true)
        }
    }

    func room(_ room: TVIRoom, didDisconnectWithError error: Error?) {
        self.cleanupRemoteParticipant()
    }

    func room(_ room: TVIRoom, didFailToConnectWithError error: Error) {
    }

    func room(_ room: TVIRoom, participantDidConnect participant: TVIRemoteParticipant) {
        if (ProvideCallObject.remoteParticipant == nil) {
            ProvideCallObject.remoteParticipant = participant
            ProvideCallObject.remoteParticipant?.delegate = self
        }
        //        print("Participant \(participant.identity) connected with \(participant.remoteAudioTracks.count) audio and \(participant.remoteVideoTracks.count) video tracks")
    }

    func room(_ room: TVIRoom, participantDidDisconnect participant: TVIRemoteParticipant) {
        if (ProvideCallObject.remoteParticipant == participant) {
            cleanupRemoteParticipant()
        }
        goToFeedback()
    }
}

// MARK: TVIRemoteParticipantDelegate

extension IncomingController : TVILocalParticipantDelegate {

    func localParticipant(_ participant: TVILocalParticipant, publishedVideoTrack: TVILocalVideoTrackPublication) {
        ProvideCallObject.localVideoTrack = publishedVideoTrack.videoTrack as? TVILocalVideoTrack
    }
}

// MARK: TVIRemoteParticipantDelegate
extension IncomingController : TVIRemoteParticipantDelegate {

    func subscribed(to videoTrack: TVIRemoteVideoTrack,
                    publication: TVIRemoteVideoTrackPublication,
                    for participant: TVIRemoteParticipant) {
        if (ProvideCallObject.remoteParticipant == participant) {
            setupRemoteVideoView()
            videoTrack.addRenderer(self.provider_remoteView!)
        }
    }

    func remoteParticipant(_ participant: TVIRemoteParticipant,
                           enabledVideoTrack publication: TVIRemoteVideoTrackPublication) {

        //        print("Participant \(participant.identity) enabled \(publication.trackName) video track")
        self.viewDisableVideo.isHidden = true
        changeButtonImage(isVideoEnable: true)
    }

    func remoteParticipant(_ participant: TVIRemoteParticipant,
                           disabledVideoTrack publication: TVIRemoteVideoTrackPublication) {
        self.viewDisableVideo.isHidden = false
        changeButtonImage(isVideoEnable: false)
        //        print("Participant \(participant.identity) disabled \(publication.trackName) video track")

    }
}


extension IncomingController : TVICameraCapturerDelegate {
    func cameraCapturer(_ capturer: TVICameraCapturer, didStartWith source: TVICameraCaptureSource) {
        // Layout the camera preview with dimensions appropriate for our orientation.
        self.provider_previewView.shouldMirror = (source == .frontCamera)
    }
}

// MARK: TVIVideoViewDelegate
extension IncomingController : TVIVideoViewDelegate {
    func videoView(_ view: TVIVideoView, videoDimensionsDidChange dimensions: CMVideoDimensions) {
        self.view.setNeedsLayout()
    }
}

所以可以在定义情况下切换或更改根控制器吗?

谢谢

标签: iosswiftcallkit

解决方案


推荐阅读