stasel / WebRTC

Unofficial distribution of up to date WebRTC framework binaries for iOS and macOS
Other
359 stars 76 forks source link

Sending dtmf issue #13

Closed xyzbilal closed 2 years ago

xyzbilal commented 3 years ago

Hi, I m using latest version of webrtc. I successfully make and receive calls but while Im in call I try to send dtmf to other peer but its not working. My other peer is Janus gateway. I have same app on android and it works fine but on ios side, its not working.

this is my handle class that handles webrtc events.

import Foundation
import WebRTC
class JanusPluginHandle{

    var description: String!

    var started:Bool = false
    var myStream:RTCMediaStream!
    var remoteStream:RTCMediaStream!
    var mySdp:RTCSessionDescription!
    var localSdp:RTCSessionDescription!
    var  pc:RTCPeerConnection!
    var dataChannel:RTCDataChannel!
    var trickle:Bool = true
    var iceDone:Bool = false
       var sdpSent:Bool = false

   let VIDEO_TRACK_ID:String = "1929283"
    let  AUDIO_TRACK_ID:String = "1928882"
    let  LOCAL_MEDIA_ID:String = "1198181"

    var sessionFactory:RTCPeerConnectionFactory!
    let server:JanusServer!
    let plugin:JanusSupportedPluginPackages!
    var id:Int64!
    let  callbacks:JanusPluginCallbacksDelegate!
    var isOffer:Bool = false
    let AUDIO_CODEC_PARAM_BITRATE:String = "maxaveragebitrate"
    let AUDIO_ECHO_CANCELLATION_CONSTRAINT:String = "googEchoCancellation"
    let AUDIO_AUTO_GAIN_CONTROL_CONSTRAINT:String = "googAutoGainControl"
    let AUDIO_HIGH_PASS_FILTER_CONSTRAINT:String = "googHighpassFilter"
    let  AUDIO_NOISE_SUPPRESSION_CONSTRAINT:String = "googNoiseSuppression"
    let  DTLS_SRTP_KEY_AGREEMENT_CONSTRAINT:String = "DtlsSrtpKeyAgreement"

    var  audioConstraints:RTCMediaConstraints!
    var  sdpMediaConstraints:RTCMediaConstraints!
    var  audioSource:RTCAudioSource!
    var localAudioTrack:RTCAudioTrack!

    init(server:JanusServer,  plugin:JanusSupportedPluginPackages,handle_id:Int64,  callbacks:JanusPluginCallbacksDelegate) {
          self.server = server
          self.plugin = plugin
          self.id = handle_id
          self.callbacks = callbacks

       createMediaConstraintsInternal()

        sessionFactory = RTCPeerConnectionFactory()

      }

    private func createMediaConstraintsInternal() {

        let mandatory:[String:String] = [
            AUDIO_ECHO_CANCELLATION_CONSTRAINT:"false",
            AUDIO_AUTO_GAIN_CONTROL_CONSTRAINT: "false",
            AUDIO_HIGH_PASS_FILTER_CONSTRAINT: "false",
            AUDIO_NOISE_SUPPRESSION_CONSTRAINT: "false"
        ]

        audioConstraints = RTCMediaConstraints.init(mandatoryConstraints: mandatory, optionalConstraints: nil)

        let sdpMandatory:[String:String] = [
            "OfferToReceiveAudio": "true",
            "OfferToReceiveVideo": "false"
        ]

        let sdpOptinal:[String:String] = [
            "DtlsSrtpKeyAgreement":"true"
        ]

        sdpMediaConstraints =  RTCMediaConstraints.init(mandatoryConstraints: sdpMandatory, optionalConstraints: sdpOptinal)

    }

    public func onAttached(obj:[String:Any]) {
        callbacks.onAttached(obj: obj)
    }

    public func onMessage(msg:String) {

        if let json = msg.toJson(){
            callbacks.onMessage(msg: json, jsep: nil)
       }

    }

    /**
     * WEBRTC den gelen EVENT mesajları mesajlar bunlar.
     * @param msg
     * @param jsep
     */
    public func onMessage(msg:[String:Any], jsep:[String:Any]?) {
        callbacks.onMessage(msg: msg, jsep: jsep)
    }

    private func onLocalStream(stream:RTCMediaStream) {
        stream.audioTracks.first?.isEnabled = true
        localAudioTrack = stream.audioTracks.first
        callbacks.onLocalStream(stream: stream)
    }

    private func onRemoteStream(stream:RTCMediaStream) {
        callbacks.onRemoteStream(stream: stream)
    }

    public func onDataOpen(data:Data) {
        callbacks.onDataOpen(data: data)
    }

    public func onData(data:Data) {
        callbacks.onData(data: data)
    }

    public func onCleanup() {
        callbacks.onCleanup()
    }

    public func onDetached() {
        callbacks.onDetached();
    }

    public func onMedia() {
        callbacks.onMedia();
    }

    public func sendMessage(obj:PluginHandleSendMessageCallbacksDelegate) {

        server.sendMessage(type: TransactionType.plugin_handle_message, handle: id, callbacks: obj, plugin: plugin)

    }

    public func createOffer( webrtcCallbacks:PluginHandleWebRTCCallbacksDelegate) {
         isOffer = true;
        prepareWebRtc(callbacks: webrtcCallbacks);

    }

    public func createAnswer( webrtcCallbacks:PluginHandleWebRTCCallbacksDelegate) {
        isOffer = false

        prepareWebRtc(callbacks: webrtcCallbacks)

    }

    private func prepareWebRtc( callbacks:PluginHandleWebRTCCallbacksDelegate) {

        if (pc != nil) {

            if (callbacks.getJsep() == nil) {

                createSdpInternal(callbacks: callbacks, isOffer: isOffer)
            } else {

                let jsep = callbacks.getJsep()!
                    let sdpString:String = jsep["sdp"] as! String
                let type:RTCSdpType = RTCSessionDescription.type(for: jsep["type"] as! String)
                let sdp:RTCSessionDescription =  RTCSessionDescription.init(type: type, sdp: sdpString)

                pc.setRemoteDescription(sdp) { (err) in}

                }
        } else {

            trickle = callbacks.getTrickle() != nil ? callbacks.getTrickle()! : false

            streamsDone(webRTCCallbacks: callbacks)

        }
    }

    private func streamsDone(webRTCCallbacks:PluginHandleWebRTCCallbacksDelegate) {

        let rtcConfig =  RTCConfiguration.init()
        rtcConfig.iceServers = server.iceServers
        rtcConfig.bundlePolicy = RTCBundlePolicy.maxBundle
        rtcConfig.rtcpMuxPolicy = RTCRtcpMuxPolicy.require
        rtcConfig.continualGatheringPolicy = RTCContinualGatheringPolicy.gatherContinually
       // rtcConfig.sdpSemantics = .planB

        let source :RTCAudioSource = sessionFactory.audioSource(with: audioConstraints)

        let audioTrack:RTCAudioTrack? = sessionFactory.audioTrack(with: source, trackId: AUDIO_TRACK_ID)

        let stream:RTCMediaStream?  = sessionFactory.mediaStream(withStreamId: LOCAL_MEDIA_ID)

        if (audioTrack != nil){
            stream!.addAudioTrack(audioTrack!)
        myStream = stream
        }

        if (stream != nil){
            onLocalStream(stream: stream!)
        }

        pc = sessionFactory.peerConnection(with: rtcConfig, constraints: audioConstraints, delegate: nil)

        if (myStream != nil){
            pc.add(myStream)
        }

        if  let obj:[String:Any] = webRTCCallbacks.getJsep(){

            let sdp:String = obj["sdp"] as! String

            let type:RTCSdpType = RTCSessionDescription.type(for: obj["type"] as! String)

            let sessionDescription:RTCSessionDescription =  RTCSessionDescription(type: type, sdp: sdp)

                print("  STREAMS DONE  JSEP NULL  DEĞİL")

             pc.setRemoteDescription(sessionDescription) { (err) in

            }
        }else{
            createSdpInternal(callbacks: webRTCCallbacks, isOffer: isOffer)
            print("  STREAMS DONE  JSEP NULL ");
        }

    }

    private func createSdpInternal(callbacks:PluginHandleWebRTCCallbacksDelegate,isOffer:Bool) {
       if (isOffer) {

            print("  CREEATE SDP  OFFER ");
             // pc.createOffer(WebRtcObserver(callbacks), sdpMediaConstraints);

        pc.offer(for: sdpMediaConstraints) { [self] (sdp, err) in
            if(err == nil){ 
                self.pc.setLocalDescription(sdp!) { err in
                    if(err == nil){
                        var obj:[String:Any] = [:]
                        obj["type"] = "offer"
                        obj["sdp"] = sdp?.sdp

                       callbacks.onSuccess(obj: obj)

                    }
                }
            }
        }

        } else {

            print("  CREEATE SDP  ANSWER ");

            pc.answer(for: sdpMediaConstraints) { (sdp, err) in
                if(err == nil){
                    self.pc.setLocalDescription(sdp!) { err in
                        if(err == nil){
                            var obj:[String:Any] = [:]
                            obj["type"] = "answer"
                            obj["sdp"] = sdp?.sdp

                            callbacks.onSuccess(obj: obj)

                        }
                    }
                }
            }

        }
    }

    public func muteMicrophone(_ mute:Bool){

    }

    public func handleRemoteJsep( webrtcCallbacks:PluginHandleWebRTCCallbacksDelegate) {

                  if (sessionFactory == nil) {
                    webrtcCallbacks.onCallbackError(error: "WebRtc PeerFactory is not initialized. Please call initializeMediaContext")

                  }
        if  let jsep = webrtcCallbacks.getJsep() {
                      if (pc == nil) {

                        callbacks.onCallbackError(error: "No peerconnection created, if this is an answer please use createAnswer")

                      }

                          let sdpString = jsep["sdp"] as? String

            let type:RTCSdpType = jsep["type"] as? String == "answer" ? .answer : .offer
            let sdp =  RTCSessionDescription(type: type, sdp: sdpString!)

            pc.setRemoteDescription(sdp) { err in
                print(err)
            }
                    print("HANDLE REMOTE JSP");

              }

    }

    private func prepareRemoteSDP( webrtcCallbacks:PluginHandleWebRTCCallbacksDelegate,jsep:[String:Any]){

        let sdpString:String = jsep["sdp"] as! String

        let type:RTCSdpType = RTCSessionDescription.type(for:jsep["type"] as! String)

        let sdp:RTCSessionDescription =  RTCSessionDescription.init(type: type, sdp: sdpString)

        pc.setRemoteDescription(sdp) { (err) in
            if(err == nil){
             //
            }
        }

    }

    public func hangUp() {

      if (remoteStream != nil) {

            remoteStream = nil
        }
        if (myStream != nil) {

            myStream = nil
        }
        if (pc != nil && pc.signalingState != RTCSignalingState.closed){
            pc.close()
        }

        pc = nil
        started = false
        localSdp = nil
        if (dataChannel != nil){
            dataChannel.close()
        }

        dataChannel = nil
        trickle = true
        iceDone = false
        sdpSent = false
        isOffer = false
        callbacks.onHangup();
    }
    public func disconnect(){

    }
    public func detach() {
        hangUp();
        let obj:[String:Any] = [:]
        server.sendMessage(msg: obj, type: JanusMessageType.detach, handle: id)
    }

    private func onLocalSdp( sdp:RTCSessionDescription, callbacks:PluginHandleWebRTCCallbacksDelegate) {
        print(" ONLOCAL SDP ");
        if (pc != nil) {
            if (localSdp == nil) {
                localSdp = sdp

                pc.setLocalDescription(sdp) { (err) in
                    if(err == nil){
                        //
                    }
                }
            }
            if (!iceDone && !trickle){
                print(" ONLOCAL SDP ice ve trickle ");
                return;
            }

                print(" ONLOCAL SDP  sdp success");
                sdpSent = true;
                var obj:[String:Any] = [:]
                obj["sdp"] = localSdp.description
                obj["type"] = localSdp.type.rawValue
            callbacks.onSuccess(obj: obj)

        }else{
            print(" ONLOCAL  PREPARE WEBRTC BU NU BEN EKLEDIMDI ");
            prepareWebRtc(callbacks: callbacks);
        }

    }

    private func sendTrickleCandidate(candidate:RTCIceCandidate?) {

            var message:[String:Any] = [:]
            var cand:[String:Any] = [:]
            if (candidate == nil){
                cand["completed"] = true
            }else {
                cand["candidate"] = candidate?.sdp
                cand["sdpMid"] = candidate?.sdpMid
                cand["sdpMLineIndex"] = candidate?.sdpMLineIndex
                }
            message["candidate"] = cand

        server.sendMessage(msg: message, type: JanusMessageType.trickle, handle: id)

    }

    private func sendSdp( callbacks:PluginHandleWebRTCCallbacksDelegate) {
        if (localSdp != nil) {
            localSdp = pc.localDescription
            if (!sdpSent) {
                sdpSent = true;

                var obj:[String:Any] = [:]
                obj["sdp"] = localSdp.description
                obj["type"] =  localSdp.type.rawValue
                callbacks.onSuccess(obj:obj)

            }
        }
    }

    public func insertDTMF(_ tone:String){

        if(pc != nil){

            if let dtmfSender = pc.senders.first?.dtmfSender{
                print(dtmfSender.canInsertDtmf)
                    dtmfSender.insertDtmf(tone, duration: 200, interToneGap: 70)
                }

        }
    }
}

my app uses ios side of this packege for call events https://github.com/jonataslaw/flutter-incall-manager/tree/master/ios and callkit for backround handling.

any idea will be appriciated.

xyzbilal commented 2 years ago

adding operation to queue solved my problem.

func insertDTMF(_ tone:String){

        var audioSender: RTCRtpSender?

        for rtpSender in peerConnection.senders {
          if rtpSender.track?.kind == "audio" {
            audioSender = rtpSender
          }
        }

        if let audioSender = audioSender {
           let queue = OperationQueue()
           queue.addOperation({
             audioSender.dtmfSender?.insertDtmf(tone, duration: TimeInterval(0.2),interToneGap: TimeInterval(0.5))
           })
        }

    }
stasel commented 2 years ago

Hi, thanks for sharing the solution here. I'm happy that you managed to sort this out