Sending dtmf issue
Closed this issue · 2 comments
xyzbilal commented
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
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
Hi, thanks for sharing the solution here. I'm happy that you managed to sort this out