QuickBlox / quickblox-ios-sdk

QuickBlox iOS SDK for messaging and video calling
https://quickblox.com/developers/IOS
MIT License
397 stars 358 forks source link

VoIP Push notification not work in background and terminate state. #1211

Closed ritesh5553 closed 1 year ago

ritesh5553 commented 4 years ago

Hello,

We have performed chatting, audio, video call functionality in our project. we got text message related notification, but we haven't got VoIP notification for audio/video call once the app goes in background and terminate the state. we have used the latest framework for audio/video calls. and also sometimes not subscription not work. So can you guide us to complete this functionality?

Thanks.

Navya-ios commented 3 years ago

need help with same issue

riteshpatel0 commented 3 years ago

Hello @Navya-ios ,

I have found some way to solve this issue. You can try as below,

-> Have you added PKPushRegistryDelegate method in appdelegate file? and PKPushRegistryDelegate is only in appdelegate file. otherwise you added more than 1 PKPushRegistryDelegate, it will not work.

-> If you will fail to report 2-3 notification, then Apple will block your notification. so you have to first delete your app then reinstall app.

-> If you have added “answerTimeInterval” in didReceiveIncomingPushWith method. then comment it. you must have to incoming call whenever receive VoIP notification.

Navya-ios commented 3 years ago

Hello navya,

I have found some way to solve this issue. You can try as below,

-> Have you added PKPushRegistryDelegate method in appdelegate file? and PKPushRegistryDelegate is only in appdelegate file. otherwise you added more than 1 PKPushRegistryDelegate, it will not work.

-> If you will fail to report 2-3 notification, then Apple will block your notification. so you have to first delete your app then reinstall app.

-> If you have added “answerTimeInterval” in didReceiveIncomingPushWith method. then comment it. you must have to incoming call whenever receive VoIP notification.

Yes PKPushRegistryDelegate methods are in App delegate only, subscription is getting success but incoming call was coming.

Navya-ios commented 3 years ago

Hello @Navya-ios ,

I have found some way to solve this issue. You can try as below,

-> Have you added PKPushRegistryDelegate method in appdelegate file? and PKPushRegistryDelegate is only in appdelegate file. otherwise you added more than 1 PKPushRegistryDelegate, it will not work.

-> If you will fail to report 2-3 notification, then Apple will block your notification. so you have to first delete your app then reinstall app.

-> If you have added “answerTimeInterval” in didReceiveIncomingPushWith method. then comment it. you must have to incoming call whenever receive VoIP notification.

Yes PKPushRegistryDelegate methods are in App delegate only, subscription is getting success but incoming call was coming.

riteshpatel0 commented 3 years ago

What you are writing in didReceiveIncomingPushWith methods, Can you share here.

Navya-ios commented 3 years ago

func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) { print("didReceiveIncomingPushwith payload") let application = UIApplication.shared

    //in case of bad internet we check how long the VOIP Push was delivered for call(1-1)
    //if time delivery is more than “answerTimeInterval” - return
    if type == .voIP,
        payload.dictionaryPayload[UsersConstant.voipEvent] != nil {
        if let timeStampString = payload.dictionaryPayload["timestamp"] as? String,
            let opponentsIDsString = payload.dictionaryPayload["opponentsIDs"] as? String {
            let opponentsIDsArray = opponentsIDsString.components(separatedBy: ",")
            if opponentsIDsArray.count == 2 {
                let formatter = DateFormatter()
                formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
                if let startCallDate = formatter.date(from: timeStampString) {
                    if Date().timeIntervalSince(startCallDate) > QBRTCConfig.answerTimeInterval() {
                        print("[WaitingRoomVC] timeIntervalSinceStartCall > QBRTCConfig.answerTimeInterval")
                        return
                    }
                }
            }
        }
    }

    if type == .voIP,
        payload.dictionaryPayload[UsersConstant.voipEvent] != nil,
        application.applicationState == .background {
        var opponentsIDs: [String]? = nil
        var opponentsNumberIDs: [NSNumber] = []
        var opponentsNamesString = "incoming call. Connecting..."
        var sessionID: String? = nil
        var callUUID = UUID()
        var sessionConferenceType = QBRTCConferenceType.audio
        self.isUpdatedPayload = false

        if let opponentsIDsString = payload.dictionaryPayload["opponentsIDs"] as? String,
            let allOpponentsNamesString = payload.dictionaryPayload["contactIdentifier"] as? String,
            let sessionIDString = payload.dictionaryPayload["sessionID"] as? String,
            let callUUIDPayload = UUID(uuidString: sessionIDString) {
            self.isUpdatedPayload = true
            self.sessionID = sessionIDString
            sessionID = sessionIDString
            callUUID = callUUIDPayload
            if let conferenceTypeString = payload.dictionaryPayload["conferenceType"] as? String {
                sessionConferenceType = conferenceTypeString == "1" ? QBRTCConferenceType.video : QBRTCConferenceType.audio
            }

            let profile = Profile()
            guard profile.isFull == true else {
                return
            }
            let opponentsIDsArray = opponentsIDsString.components(separatedBy: ",")

            var opponentsNumberIDsArray = opponentsIDsArray.compactMap({NSNumber(value: Int($0)!)})
            var allOpponentsNamesArray = allOpponentsNamesString.components(separatedBy: ",")
            for i in 0...opponentsNumberIDsArray.count - 1 {
                if opponentsNumberIDsArray[i].uintValue == profile.ID {
                    opponentsNumberIDsArray.remove(at: i)
                    allOpponentsNamesArray.remove(at: i)
                    break
                }
            }
            opponentsNumberIDs = opponentsNumberIDsArray
            opponentsIDs = opponentsNumberIDs.compactMap({ $0.stringValue })
            opponentsNamesString = allOpponentsNamesArray.joined(separator: ", ")
        }

        let fetchUsersCompletion = { [weak self] (usersIDs: [String]?) -> Void in
            if let opponentsIDs = usersIDs {
                QBRequest.users(withIDs: opponentsIDs, page: nil, successBlock: { [weak self] (respose, page, users) in
                    if users.isEmpty == false {
                        self?.dataSource.update(users: users)
                    }
                }) { (response) in
                    print("[WaitingRoomVC] error fetch usersWithIDs")
                }
            }
        }

        CallKitManager.instance.reportIncomingCall(withUserIDs: opponentsNumberIDs,
                                                   outCallerName: opponentsNamesString,
                                                   session: nil,
                                                   sessionID: sessionID,
                                                   sessionConferenceType: sessionConferenceType,
                                                   uuid: callUUID,
                                                   onAcceptAction: { [weak self] (isAccept) in
                                                    guard let self = self else {
                                                        return
                                                    }

                                                    if let session = self.session {
                                                        if isAccept == true {
                                                            self.openCall(withSession: session,
                                                                          uuid: callUUID,
                                                                          sessionConferenceType: sessionConferenceType)
                                                            print("[WaitingRoomVC]  onAcceptAction")
                                                        } else {
                                                            session.rejectCall(["reject": "busy"])
                                                            print("[WaitingRoomVC] endCallAction")
                                                        }
                                                    } else {
                                                        if isAccept == true {
                                                            self.openCall(withSession: nil,
                                                                          uuid: callUUID,
                                                                          sessionConferenceType: sessionConferenceType)
                                                            print("[WaitingRoomVC]  onAcceptAction")
                                                        } else {

                                                            print("[WaitingRoomVC] endCallAction")
                                                        }
                                                        self.prepareBackgroundTask()
                                                    }
                                                    completion()

            }, completion: { (isOpen) in
                if QBChat.instance.isConnected == false {
                    self.connectToChat { (error) in
                        if error == nil {
                            print("mo error in didreceiveIncoming withpush")
                            fetchUsersCompletion(opponentsIDs)
                        }
                    }
                } else {
                    print("ABChat not connect in didreceiveIncoming withpush")
                    fetchUsersCompletion(opponentsIDs)
                }
                self.prepareBackgroundTask()
                print("[WaitingRoomVC] callKit did presented")
        })
    }
}
Navya-ios commented 3 years ago

What you are writing in didReceiveIncomingPushWith methods, Can you share here.

func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) { print("didReceiveIncomingPushwith payload") let application = UIApplication.shared

//in case of bad internet we check how long the VOIP Push was delivered for call(1-1)
//if time delivery is more than “answerTimeInterval” - return
if type == .voIP,
    payload.dictionaryPayload[UsersConstant.voipEvent] != nil {
    if let timeStampString = payload.dictionaryPayload["timestamp"] as? String,
        let opponentsIDsString = payload.dictionaryPayload["opponentsIDs"] as? String {
        let opponentsIDsArray = opponentsIDsString.components(separatedBy: ",")
        if opponentsIDsArray.count == 2 {
            let formatter = DateFormatter()
            formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
            if let startCallDate = formatter.date(from: timeStampString) {
                if Date().timeIntervalSince(startCallDate) > QBRTCConfig.answerTimeInterval() {
                    print("[WaitingRoomVC] timeIntervalSinceStartCall > QBRTCConfig.answerTimeInterval")
                    return
                }
            }
        }
    }
}

if type == .voIP,
    payload.dictionaryPayload[UsersConstant.voipEvent] != nil,
    application.applicationState == .background {
    var opponentsIDs: [String]? = nil
    var opponentsNumberIDs: [NSNumber] = []
    var opponentsNamesString = "incoming call. Connecting..."
    var sessionID: String? = nil
    var callUUID = UUID()
    var sessionConferenceType = QBRTCConferenceType.audio
    self.isUpdatedPayload = false

    if let opponentsIDsString = payload.dictionaryPayload["opponentsIDs"] as? String,
        let allOpponentsNamesString = payload.dictionaryPayload["contactIdentifier"] as? String,
        let sessionIDString = payload.dictionaryPayload["sessionID"] as? String,
        let callUUIDPayload = UUID(uuidString: sessionIDString) {
        self.isUpdatedPayload = true
        self.sessionID = sessionIDString
        sessionID = sessionIDString
        callUUID = callUUIDPayload
        if let conferenceTypeString = payload.dictionaryPayload["conferenceType"] as? String {
            sessionConferenceType = conferenceTypeString == "1" ? QBRTCConferenceType.video : QBRTCConferenceType.audio
        }

        let profile = Profile()
        guard profile.isFull == true else {
            return
        }
        let opponentsIDsArray = opponentsIDsString.components(separatedBy: ",")

        var opponentsNumberIDsArray = opponentsIDsArray.compactMap({NSNumber(value: Int($0)!)})
        var allOpponentsNamesArray = allOpponentsNamesString.components(separatedBy: ",")
        for i in 0...opponentsNumberIDsArray.count - 1 {
            if opponentsNumberIDsArray[i].uintValue == profile.ID {
                opponentsNumberIDsArray.remove(at: i)
                allOpponentsNamesArray.remove(at: i)
                break
            }
        }
        opponentsNumberIDs = opponentsNumberIDsArray
        opponentsIDs = opponentsNumberIDs.compactMap({ $0.stringValue })
        opponentsNamesString = allOpponentsNamesArray.joined(separator: ", ")
    }

    let fetchUsersCompletion = { [weak self] (usersIDs: [String]?) -> Void in
        if let opponentsIDs = usersIDs {
            QBRequest.users(withIDs: opponentsIDs, page: nil, successBlock: { [weak self] (respose, page, users) in
                if users.isEmpty == false {
                    self?.dataSource.update(users: users)
                }
            }) { (response) in
                print("[WaitingRoomVC] error fetch usersWithIDs")
            }
        }
    }

    CallKitManager.instance.reportIncomingCall(withUserIDs: opponentsNumberIDs,
                                               outCallerName: opponentsNamesString,
                                               session: nil,
                                               sessionID: sessionID,
                                               sessionConferenceType: sessionConferenceType,
                                               uuid: callUUID,
                                               onAcceptAction: { [weak self] (isAccept) in
                                                guard let self = self else {
                                                    return
                                                }

                                                if let session = self.session {
                                                    if isAccept == true {
                                                        self.openCall(withSession: session,
                                                                      uuid: callUUID,
                                                                      sessionConferenceType: sessionConferenceType)
                                                        print("[WaitingRoomVC]  onAcceptAction")
                                                    } else {
                                                        session.rejectCall(["reject": "busy"])
                                                        print("[WaitingRoomVC] endCallAction")
                                                    }
                                                } else {
                                                    if isAccept == true {
                                                        self.openCall(withSession: nil,
                                                                      uuid: callUUID,
                                                                      sessionConferenceType: sessionConferenceType)
                                                        print("[WaitingRoomVC]  onAcceptAction")
                                                    } else {

                                                        print("[WaitingRoomVC] endCallAction")
                                                    }
                                                    self.prepareBackgroundTask()
                                                }
                                                completion()

        }, completion: { (isOpen) in
            if QBChat.instance.isConnected == false {
                self.connectToChat { (error) in
                    if error == nil {
                        print("mo error in didreceiveIncoming withpush")
                        fetchUsersCompletion(opponentsIDs)
                    }
                }
            } else {
                print("ABChat not connect in didreceiveIncoming withpush")
                fetchUsersCompletion(opponentsIDs)
            }
            self.prepareBackgroundTask()
            print("[WaitingRoomVC] callKit did presented")
    })
}

}

riteshpatel0 commented 3 years ago

@Navya-ios

Comment below section,

if type == .voIP, payload.dictionaryPayload[UsersConstant.voipEvent] != nil { if let timeStampString = payload.dictionaryPayload["timestamp"] as? String, let opponentsIDsString = payload.dictionaryPayload["opponentsIDs"] as? String { let opponentsIDsArray = opponentsIDsString.components(separatedBy: ",") if opponentsIDsArray.count == 2 { let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" if let startCallDate = formatter.date(from: timeStampString) { if Date().timeIntervalSince(startCallDate) > QBRTCConfig.answerTimeInterval() { print("[WaitingRoomVC] timeIntervalSinceStartCall > QBRTCConfig.answerTimeInterval") return } } } } }

and Are you getting notification?

Navya-ios commented 3 years ago

@Navya-ios

Comment below section,

if type == .voIP, payload.dictionaryPayload[UsersConstant.voipEvent] != nil { if let timeStampString = payload.dictionaryPayload["timestamp"] as? String, let opponentsIDsString = payload.dictionaryPayload["opponentsIDs"] as? String { let opponentsIDsArray = opponentsIDsString.components(separatedBy: ",") if opponentsIDsArray.count == 2 { let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" if let startCallDate = formatter.date(from: timeStampString) { if Date().timeIntervalSince(startCallDate) > QBRTCConfig.answerTimeInterval() { print("[WaitingRoomVC] timeIntervalSinceStartCall > QBRTCConfig.answerTimeInterval") return } } } } }

and Are you getting notification?

no, im not getting any notification app was in background only

Navya-ios commented 3 years ago

@Navya-ios

Comment below section,

if type == .voIP, payload.dictionaryPayload[UsersConstant.voipEvent] != nil { if let timeStampString = payload.dictionaryPayload["timestamp"] as? String, let opponentsIDsString = payload.dictionaryPayload["opponentsIDs"] as? String { let opponentsIDsArray = opponentsIDsString.components(separatedBy: ",") if opponentsIDsArray.count == 2 { let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" if let startCallDate = formatter.date(from: timeStampString) { if Date().timeIntervalSince(startCallDate) > QBRTCConfig.answerTimeInterval() { print("[WaitingRoomVC] timeIntervalSinceStartCall > QBRTCConfig.answerTimeInterval") return } } } } }

and Are you getting notification?

Can you please tell me the step by step procedure to work for background call?

riteshpatel0 commented 3 years ago

@Navya-ios

How many places to call below method, func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType)

didReceiveIncomingPushWith method is call when your app is in foreground? if not, then first delete the app the reinstall the app.

Navya-ios commented 3 years ago

@Navya-ios

How many places to call below method, func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType)

didReceiveIncomingPushWith method is call when your app is in foreground? if not, then first delete the app the reinstall the app. func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) this is only in one view controller

and didReceiveIncomingPushWith method is call when your app is in foreground? this method is not calling in foreground

riteshpatel0 commented 3 years ago

Please try to delete the app and reinstall the app. then check it. if this method is not called then your notification is blocked by apple due to not reporting incoming call.

Navya-ios commented 3 years ago

Please try to delete the app and reinstall the app. then check it. if this method is not called then your notification is blocked by apple due to not reporting incoming call.

Yes tried but no progress

riteshpatel0 commented 3 years ago

Can you share your chat related files?

Navya-ios commented 3 years ago

Can you share your chat related files?

its for video calling

riteshpatel0 commented 3 years ago

Yes

Navya-ios commented 3 years ago

Yes

I am getting this crash log if i call in foreground. Any idea or suggestion?

2020-09-25 15:53:49.679 rtc::[Signaling Processor] - Did receive signal: call from: 25150912 2020-09-25 15:53:49.700 rtc::[RTCClient] Initializing SSL... 2020-09-25 15:53:49.702 rtc::Create audio track: RTCMediaStreamTrack: audio audioTrack enabled Live 2020-09-25 15:53:49.702 rtc::[CAPT] Init. 2020-09-25 15:53:49.702 rtc::Create video track: RTCMediaStreamTrack: video videoTrack enabled Live 2020-09-25 15:53:49.703 rtc::initialize - QBRTCRecorder 2020-09-25 15:53:49.704 rtc::[SESS]<d44a536b-21bb-419e-84e1-34e2c24272a3, I:25150912, O:[25157650], T:V> Init. 2020-09-25 15:53:49.705 rtc::[TASK]<ID:13, l:session answer time out> Start. 2020-09-25 15:53:49.705 rtc::[RTCClient] <QBRTCClient: 0x2824ac440> created new [SESS]<d44a536b-21bb-419e-84e1-34e2c24272a3, I:25150912, O:[25157650], T:V> didChange : QBRTCSessionState Could not cast value of type 'NSDictionaryM' (0x1ef977050) to 'NSString' (0x1ef97ff18). 2020-09-25 15:53:52.306573+0530 Vivadox[30613:2395236] Could not cast value of type 'NSDictionaryM' (0x1ef977050) to 'NSString' (0x1ef97ff18).

Navya-ios commented 3 years ago

@riteshpatel0 @ritesh5553 @soulfly @dgem

For voIP notification (for call) In admin panel, I am able to get success in subscriptions but not i am not able to get in queue and not receiving any incoming call when app is in background.

riteshpatel0 commented 3 years ago

@Navya-ios

While you are going to subscribe, did you check QBChat service is connected or not?. If QBChat service is not connected then the subscription will not show in the admin panel. and where you are added PKPushRegistryDelegate. if you are not added in appdelegate then VoIP will not work in background and terminate.

Navya-ios commented 3 years ago

@riteshpatel0

We are disconnecting chat service when app goes to background state so, in that case we are not getting video call in background And if we don't disconnect chat service in didEnterBackground we are not getting messages when app is in background

How to overcome this issue?

riteshpatel0 commented 3 years ago

@Navya-ios

Can you share VoIP Call functionality related files?

ghost commented 2 years ago

Hello,

This is Nikolay from QuickBlox support.

Please let me know if the issue is still relevant.

Also, please update the SDK to the latest version: https://github.com/QuickBlox/quickblox-ios-sdk/releases/tag/2.17.10

Additionally, please check our new samples: https://docs.quickblox.com/docs/code-samples#video-calling-samples

vitalyiegorov commented 1 year ago

@muteKey Could you please describe how offline call push notification should work? According to the docs it is not clear who should send this notification(voip)? Is this done on QB backend or each client implementation should send it?