From 6d6bd1efa55b2a536b12e7eae9991c1b7bbe9f60 Mon Sep 17 00:00:00 2001 From: Anthony Minessale Date: Wed, 8 Jun 2016 16:20:18 -0500 Subject: [PATCH 1/7] FS-9242 convert to adapter.js --- html5/verto/js/Makefile | 2 +- html5/verto/js/src/jquery.FSRTC.js | 369 +- html5/verto/js/src/vendor/adapter-latest.js | 3842 +++++++++++++++++ html5/verto/verto_communicator/src/index.html | 1 + .../src/partials/preview.html | 2 +- .../storageService/services/splash_screen.js | 6 +- .../src/storageService/services/storage.js | 1 + .../controllers/DialPadController.js | 4 +- .../controllers/PreviewController.js | 31 +- .../controllers/SettingsController.js | 9 + .../controllers/SplashScreenController.js | 2 +- .../vertoDirectives/directives/videoTag.js | 24 +- .../src/vertoService/services/vertoService.js | 45 +- html5/verto/video_demo/js/verto-min.js | 291 +- html5/verto/video_demo/verto.js | 60 +- src/switch_core_media.c | 5 +- src/switch_rtp.c | 23 +- 17 files changed, 4385 insertions(+), 332 deletions(-) create mode 100644 html5/verto/js/src/vendor/adapter-latest.js diff --git a/html5/verto/js/Makefile b/html5/verto/js/Makefile index e2c8dab068..05aa6617b0 100644 --- a/html5/verto/js/Makefile +++ b/html5/verto/js/Makefile @@ -1,4 +1,4 @@ -JSFILES=src/jquery.FSRTC.js src/jquery.jsonrpcclient.js src/jquery.verto.js +JSFILES=src/jquery.FSRTC.js src/jquery.jsonrpcclient.js src/jquery.verto.js src/vendor/adapter-latest.js all: jsmin verto-min.js diff --git a/html5/verto/js/src/jquery.FSRTC.js b/html5/verto/js/src/jquery.FSRTC.js index 225c1b16ec..5cfd11bd05 100644 --- a/html5/verto/js/src/jquery.FSRTC.js +++ b/html5/verto/js/src/jquery.FSRTC.js @@ -100,22 +100,10 @@ candidateList: [] }; - - if (moz) { - this.constraints = { - offerToReceiveAudio: this.options.useSpeak === "none" ? false : true, - offerToReceiveVideo: this.options.useVideo ? true : false, - }; - } else { - this.constraints = { - optional: [{ - 'DtlsSrtpKeyAgreement': 'true' - }],mandatory: { - OfferToReceiveAudio: this.options.useSpeak === "none" ? false : true, - OfferToReceiveVideo: this.options.useVideo ? true : false, - } - }; - } + this.constraints = { + offerToReceiveAudio: this.options.useSpeak === "none" ? false : true, + offerToReceiveVideo: this.options.useVideo ? true : false, + }; if (self.options.useVideo) { self.options.useVideo.style.display = 'none'; @@ -133,19 +121,11 @@ if (obj) { self.options.useVideo = obj; self.options.localVideo = local; - if (moz) { - self.constraints.offerToReceiveVideo = true; - } else { - self.constraints.mandatory.OfferToReceiveVideo = true; - } + self.constraints.offerToReceiveVideo = true; } else { self.options.useVideo = null; self.options.localVideo = null; - if (moz) { - self.constraints.offerToReceiveVideo = false; - } else { - self.constraints.mandatory.OfferToReceiveVideo = false; - } + self.constraints.offerToReceiveVideo = false; } if (self.options.useVideo) { @@ -193,18 +173,9 @@ }; function setCompat() { - $.FSRTC.moz = !!navigator.mozGetUserMedia; - //navigator.getUserMedia || (navigator.getUserMedia = navigator.mozGetUserMedia || navigator.webkitGetUserMedia || navigator.msGetUserMedia); - if (!navigator.getUserMedia) { - navigator.getUserMedia = navigator.mozGetUserMedia || navigator.webkitGetUserMedia || navigator.msGetUserMedia; - } } function checkCompat() { - if (!navigator.getUserMedia) { - alert('This application cannot function in this browser.'); - return false; - } return true; } @@ -258,6 +229,21 @@ doCallback(self, "onICESDP", msg); } + FSRTCattachMediaStream = function(element, stream) { + if (element && element.id && attachMediaStream) { + attachMediaStream(element, stream); + } else { + if (typeof element.srcObject !== 'undefined') { + element.srcObject = stream; + } else if (typeof element.src !== 'undefined') { + element.src = URL.createObjectURL(stream); + } else { + console.error('Error attaching stream to element.'); + } + } + } + + function onRemoteStream(self, stream) { if (self.options.useVideo) { self.options.useVideo.style.display = 'block'; @@ -266,16 +252,8 @@ var element = self.options.useAudio; console.log("REMOTE STREAM", stream, element); - if (typeof element.srcObject !== 'undefined') { - element.srcObject = stream; - } else if (typeof element.mozSrcObject !== 'undefined') { - element.mozSrcObject = stream; - } else if (typeof element.src !== 'undefined') { - element.src = URL.createObjectURL(stream); - } else { - console.error('Error attaching stream to element.'); - } - + FSRTCattachMediaStream(element, stream); + self.options.useAudio.play(); self.remoteStream = stream; } @@ -306,11 +284,7 @@ if (self.options.useVideo) { self.options.useVideo.style.display = 'none'; - if (moz) { - self.options.useVideo['mozSrcObject'] = null; - } else { - self.options.useVideo['src'] = ''; - } + self.options.useVideo['src'] = ''; } if (self.localStream) { @@ -331,11 +305,7 @@ if (self.options.localVideo) { self.options.localVideo.style.display = 'none'; - if (moz) { - self.options.localVideo['mozSrcObject'] = null; - } else { - self.options.localVideo['src'] = ''; - } + self.options.localVideo['src'] = ''; } if (self.options.localVideoStream) { @@ -426,7 +396,7 @@ function onSuccess(stream) { self.localStream = stream; - self.peer = RTCPeerConnection({ + self.peer = FSRTCPeerConnection({ type: self.type, attachStream: self.localStream, onICE: function(candidate) { @@ -469,8 +439,8 @@ constraints: { audio: false, video: { - mandatory: self.options.videoParams, - optional: [] + //mandatory: self.options.videoParams, + //optional: [] }, }, localVideo: self.options.localVideo, @@ -501,31 +471,29 @@ console.log("Microphone Disabled"); audio = false; } else if (obj.options.videoParams && obj.options.screenShare) {//obj.options.videoParams.chromeMediaSource == 'desktop') { - - //obj.options.videoParams = { - // chromeMediaSource: 'screen', - // maxWidth:screen.width, - // maxHeight:screen.height - // chromeMediaSourceId = sourceId; - // }; - - console.error("SCREEN SHARE"); + console.error("SCREEN SHARE", obj.options.videoParams); audio = false; } else { audio = { - mandatory: {}, - optional: [] + //mandatory: {}, + //optional: [] + advanced: [] }; if (obj.options.useMic !== "any") { - audio.optional = [{sourceId: obj.options.useMic}] + //audio.optional = [{sourceId: obj.options.useMic}] + audio.deviceId = {exact: obj.options.useMic}; } + //FIXME if (obj.options.audioParams) { for (var key in obj.options.audioParams) { var con = {}; - con[key] = obj.options.audioParams[key]; - audio.optional.push(con); + //con[key] = obj.options.audioParams[key]; + if (obj.options.audioParams[key]) { + con.exact = key; + audio.advanced.push(con); + } } } @@ -536,10 +504,8 @@ getUserMedia({ constraints: { audio: false, - video: { - mandatory: obj.options.videoParams, - optional: [] - }, + video: obj.options.videoParams + }, localVideo: obj.options.localVideo, onsuccess: function(e) {self.options.localVideoStream = e; console.log("local video ready");}, @@ -549,33 +515,48 @@ var video = {}; var bestFrameRate = obj.options.videoParams.vertoBestFrameRate; + var minFrameRate = obj.options.videoParams.minFrameRate || 15; delete obj.options.videoParams.vertoBestFrameRate; - video = { - mandatory: obj.options.videoParams, - optional: [] - } - - var useVideo = obj.options.useVideo; - - if (useVideo && obj.options.useCamera && obj.options.useCamera !== "none") { - if (!video.optional) { - video.optional = []; - } - - if (obj.options.useCamera !== "any") { - video.optional.push({sourceId: obj.options.useCamera}); - } - - if (bestFrameRate) { - video.optional.push({minFrameRate: bestFrameRate}); - video.optional.push({maxFrameRate: bestFrameRate}); - } - + if (obj.options.screenShare) { + // fix for chrome to work for now, will need to change once we figure out how to do this in a non-mandatory style constraint. + video = { + mandatory: obj.options.videoParams + }; } else { - console.log("Camera Disabled"); - video = false; - useVideo = false; + + video = { + //mandatory: obj.options.videoParams, + width: {min: obj.options.videoParams.minWidth, max: obj.options.videoParams.maxWidth}, + height: {min: obj.options.videoParams.minHeight, max: obj.options.videoParams.maxHeight} + }; + + + + var useVideo = obj.options.useVideo; + + if (useVideo && obj.options.useCamera && obj.options.useCamera !== "none") { + //if (!video.optional) { + //video.optional = []; + //} + + + if (obj.options.useCamera !== "any") { + //video.optional.push({sourceId: obj.options.useCamera}); + video.deviceId = obj.options.useCamera; + } + + if (bestFrameRate) { + //video.optional.push({minFrameRate: bestFrameRate}); + //video.optional.push({maxFrameRate: bestFrameRate}); + video.frameRate = {ideal: bestFrameRate, min: minFrameRate, max: 30}; + } + + } else { + console.log("Camera Disabled"); + video = false; + useVideo = false; + } } return {audio: audio, video: video, useVideo: useVideo}; @@ -597,14 +578,10 @@ self.localStream = stream; if (screen) { - if (moz) { - self.constraints.OfferToReceiveVideo = false; - } else { - self.constraints.mandatory.OfferToReceiveVideo = false; - } + self.constraints.offerToReceiveVideo = false; } - self.peer = RTCPeerConnection({ + self.peer = FSRTCPeerConnection({ type: self.type, attachStream: self.localStream, onICE: function(candidate) { @@ -671,57 +648,23 @@ // 2013, @muazkh - github.com/muaz-khan // MIT License - https://www.webrtc-experiment.com/licence/ // Documentation - https://github.com/muaz-khan/WebRTC-Experiment/tree/master/RTCPeerConnection - window.moz = !!navigator.mozGetUserMedia; - - function RTCPeerConnection(options) { + + function FSRTCPeerConnection(options) { var gathering = false, done = false; - - var w = window, - PeerConnection = w.mozRTCPeerConnection || w.webkitRTCPeerConnection, - SessionDescription = w.mozRTCSessionDescription || w.RTCSessionDescription, - IceCandidate = w.mozRTCIceCandidate || w.RTCIceCandidate; - - var STUN = { - url: !moz ? 'stun:stun.l.google.com:19302' : 'stun:23.21.150.121' - }; - - var iceServers = null; + var config = {}; + var default_ice = { + urls: ['stun:stun.l.google.com:19302'] + }; if (options.iceServers) { - var tmp = options.iceServers; - - if (typeof(tmp) === "boolean") { - tmp = null; - } - - if (tmp && !(typeof(tmp) == "object" && tmp.constructor === Array)) { - console.warn("iceServers must be an array, reverting to default ice servers"); - tmp = null; - } - - iceServers = { - iceServers: tmp || [STUN] - }; - - if (!moz && !tmp) { - iceServers.iceServers = [STUN]; - } + if (typeof(options.iceServers) === "boolean") { + config.iceServers = [default_ice]; + } else { + config.iceServers = options.iceServers; + } } - var optional = { - optional: [] - }; - - if (!moz) { - optional.optional = [{ - DtlsSrtpKeyAgreement: true - }, - { - RtpDataChannels: options.onChannelMessage ? true : false - }]; - } - - var peer = new PeerConnection(iceServers, optional); + var peer = new window.RTCPeerConnection(config); openOffererChannel(); var x = 0; @@ -736,34 +679,10 @@ } if (options.type == "offer") { - if ((!moz || (!options.sentICESDP && peer.localDescription.sdp.match(/a=candidate/)) && !x && options.onICESDP)) { - options.onICESDP(peer.localDescription); - //x = 1; - /* - x = 1; - peer.createOffer(function(sessionDescription) { - sessionDescription.sdp = serializeSdp(sessionDescription.sdp); - peer.setLocalDescription(sessionDescription); - if (options.onICESDP) { - options.onICESDP(sessionDescription); - } - }, onSdpError, constraints); - */ - } + options.onICESDP(peer.localDescription); } else { if (!x && options.onICESDP) { options.onICESDP(peer.localDescription); - //x = 1; - /* - x = 1; - peer.createAnswer(function(sessionDescription) { - sessionDescription.sdp = serializeSdp(sessionDescription.sdp); - peer.setLocalDescription(sessionDescription); - if (options.onICESDP) { - options.onICESDP(sessionDescription); - } - }, onSdpError, constraints); - */ } } } @@ -821,10 +740,10 @@ //console.debug('on:add:stream', remoteMediaStream); }; - var constraints = options.constraints || { - offerToReceiveAudio: true, - offerToReceiveVideo: true - }; + //var constraints = options.constraints || { + // offerToReceiveAudio: true, + //offerToReceiveVideo: true + //}; // onOfferSDP(RTCSessionDescription) function createOffer() { @@ -834,13 +753,8 @@ sessionDescription.sdp = serializeSdp(sessionDescription.sdp); peer.setLocalDescription(sessionDescription); options.onOfferSDP(sessionDescription); - /* old mozilla behaviour the SDP was already great right away */ - if (moz && options.onICESDP && sessionDescription.sdp.match(/a=candidate/)) { - options.onICESDP(sessionDescription); - options.sentICESDP = 1; - } }, - onSdpError, constraints); + onSdpError, options.constraints); } // onAnswerSDP(RTCSessionDescription) @@ -848,7 +762,7 @@ if (options.type != "answer") return; //options.offerSDP.sdp = addStereo(options.offerSDP.sdp); - peer.setRemoteDescription(new SessionDescription(options.offerSDP), onSdpSuccess, onSdpError); + peer.setRemoteDescription(new window.RTCSessionDescription(options.offerSDP), onSdpSuccess, onSdpError); peer.createAnswer(function(sessionDescription) { sessionDescription.sdp = serializeSdp(sessionDescription.sdp); peer.setLocalDescription(sessionDescription); @@ -856,11 +770,11 @@ options.onAnswerSDP(sessionDescription); } }, - onSdpError, constraints); + onSdpError); } - // if Mozilla Firefox & DataChannel; offer/answer will be created later - if ((options.onChannelMessage && !moz) || !options.onChannelMessage) { + + if ((options.onChannelMessage) || !options.onChannelMessage) { createOffer(); createAnswer(); } @@ -899,9 +813,6 @@ } function serializeSdp(sdp) { - //if (!moz) sdp = setBandwidth(sdp); - //sdp = getInteropSDP(sdp); - //console.debug(sdp); return sdp; } @@ -909,29 +820,18 @@ var channel; function openOffererChannel() { - if (!options.onChannelMessage || (moz && !options.onOfferSDP)) return; + if (!options.onChannelMessage) return; _openOffererChannel(); - if (!moz) return; - navigator.mozGetUserMedia({ - audio: true, - fake: true - }, - function(stream) { - peer.addStream(stream); - createOffer(); - }, - useless); + return; } function _openOffererChannel() { - channel = peer.createDataChannel(options.channel || 'RTCDataChannel', moz ? {} : { + channel = peer.createDataChannel(options.channel || 'RTCDataChannel', { reliable: false }); - if (moz) channel.binaryType = 'blob'; - setChannelEvents(); } @@ -955,8 +855,6 @@ }; } - if (options.onAnswerSDP && moz && options.onChannelMessage) openAnswererChannel(); - function openAnswererChannel() { peer.ondatachannel = function(event) { channel = event.channel; @@ -964,16 +862,7 @@ setChannelEvents(); }; - if (!moz) return; - navigator.mozGetUserMedia({ - audio: true, - fake: true - }, - function(stream) { - peer.addStream(stream); - createAnswer(); - }, - useless); + return; } // fake:true is also available on chrome under a flag! @@ -993,10 +882,10 @@ return { addAnswerSDP: function(sdp, cbSuccess, cbError) { - peer.setRemoteDescription(new SessionDescription(sdp), cbSuccess ? cbSuccess : onSdpSuccess, cbError ? cbError : onSdpError); + peer.setRemoteDescription(new window.RTCSessionDescription(sdp), cbSuccess ? cbSuccess : onSdpSuccess, cbError ? cbError : onSdpError); }, addICE: function(candidate) { - peer.addIceCandidate(new IceCandidate({ + peer.addIceCandidate(new window.RTCIceCandidate({ sdpMLineIndex: candidate.sdpMLineIndex, candidate: candidate.candidate })); @@ -1026,14 +915,14 @@ // getUserMedia var video_constraints = { - mandatory: {}, - optional: [] + //mandatory: {}, + //optional: [] }; function getUserMedia(options) { var n = navigator, media; - n.getMedia = n.webkitGetUserMedia || n.mozGetUserMedia; + n.getMedia = n.getUserMedia; n.getMedia(options.constraints || { audio: true, video: video_constraints @@ -1044,15 +933,8 @@ }); function streaming(stream) { - //var video = options.video; - //var localVideo = options.localVideo; - //if (video) { - // video[moz ? 'mozSrcObject' : 'src'] = moz ? stream : window.webkitURL.createObjectURL(stream); - //video.play(); - //} - if (options.localVideo) { - options.localVideo[moz ? 'mozSrcObject' : 'src'] = moz ? stream : window.webkitURL.createObjectURL(stream); + options.localVideo['src'] = window.URL.createObjectURL(stream); options.localVideo.style.display = 'block'; } @@ -1108,23 +990,26 @@ } var video = { - mandatory: {}, - optional: [] + //mandatory: {}, + //optional: [] } - + //FIXME if (cam) { - video.optional = [{sourceId: cam}]; + //video.optional = [{sourceId: cam}]; + video.deviceId = {exact: cam}; } w = resList[resI][0]; h = resList[resI][1]; resI++; - video.mandatory = { - "minWidth": w, - "minHeight": h, - "maxWidth": w, - "maxHeight": h + video = { + width: w, + height: h + //"minWidth": w, + //"minHeight": h, + //"maxWidth": w, + //"maxHeight": h }; getUserMedia({ diff --git a/html5/verto/js/src/vendor/adapter-latest.js b/html5/verto/js/src/vendor/adapter-latest.js new file mode 100644 index 0000000000..e390add1da --- /dev/null +++ b/html5/verto/js/src/vendor/adapter-latest.js @@ -0,0 +1,3842 @@ +/*! adapterjs - v0.13.3 - 2016-08-03 */ + +// Adapter's interface. +var AdapterJS = AdapterJS || {}; + +// Browserify compatibility +if(typeof exports !== 'undefined') { + module.exports = AdapterJS; +} + +AdapterJS.options = AdapterJS.options || {}; + +// uncomment to get virtual webcams +// AdapterJS.options.getAllCams = true; + +// uncomment to prevent the install prompt when the plugin in not yet installed +// AdapterJS.options.hidePluginInstallPrompt = true; + +// AdapterJS version +AdapterJS.VERSION = '0.13.3'; + +// This function will be called when the WebRTC API is ready to be used +// Whether it is the native implementation (Chrome, Firefox, Opera) or +// the plugin +// You may Override this function to synchronise the start of your application +// with the WebRTC API being ready. +// If you decide not to override use this synchronisation, it may result in +// an extensive CPU usage on the plugin start (once per tab loaded) +// Params: +// - isUsingPlugin: true is the WebRTC plugin is being used, false otherwise +// +AdapterJS.onwebrtcready = AdapterJS.onwebrtcready || function(isUsingPlugin) { + // The WebRTC API is ready. + // Override me and do whatever you want here +}; + +// New interface to store multiple callbacks, private +AdapterJS._onwebrtcreadies = []; + +// Sets a callback function to be called when the WebRTC interface is ready. +// The first argument is the function to callback.\ +// Throws an error if the first argument is not a function +AdapterJS.webRTCReady = function (callback) { + if (typeof callback !== 'function') { + throw new Error('Callback provided is not a function'); + } + + if (true === AdapterJS.onwebrtcreadyDone) { + // All WebRTC interfaces are ready, just call the callback + callback(null !== AdapterJS.WebRTCPlugin.plugin); + } else { + // will be triggered automatically when your browser/plugin is ready. + AdapterJS._onwebrtcreadies.push(callback); + } +}; + +// Plugin namespace +AdapterJS.WebRTCPlugin = AdapterJS.WebRTCPlugin || {}; + +// The object to store plugin information +/* jshint ignore:start */ +AdapterJS.WebRTCPlugin.pluginInfo = { + prefix : 'Tem', + plugName : 'TemWebRTCPlugin', + pluginId : 'plugin0', + type : 'application/x-temwebrtcplugin', + onload : '__TemWebRTCReady0', + portalLink : 'http://skylink.io/plugin/', + downloadLink : null, //set below + companyName: 'Temasys' +}; +if(!!navigator.platform.match(/^Mac/i)) { + AdapterJS.WebRTCPlugin.pluginInfo.downloadLink = 'http://bit.ly/1n77hco'; +} +else if(!!navigator.platform.match(/^Win/i)) { + AdapterJS.WebRTCPlugin.pluginInfo.downloadLink = 'http://bit.ly/1kkS4FN'; +} +/* jshint ignore:end */ + +AdapterJS.WebRTCPlugin.TAGS = { + NONE : 'none', + AUDIO : 'audio', + VIDEO : 'video' +}; + +// Unique identifier of each opened page +AdapterJS.WebRTCPlugin.pageId = Math.random().toString(36).slice(2); + +// Use this whenever you want to call the plugin. +AdapterJS.WebRTCPlugin.plugin = null; + +// Set log level for the plugin once it is ready. +// The different values are +// This is an asynchronous function that will run when the plugin is ready +AdapterJS.WebRTCPlugin.setLogLevel = null; + +// Defines webrtc's JS interface according to the plugin's implementation. +// Define plugin Browsers as WebRTC Interface. +AdapterJS.WebRTCPlugin.defineWebRTCInterface = null; + +// This function detects whether or not a plugin is installed. +// Checks if Not IE (firefox, for example), else if it's IE, +// we're running IE and do something. If not it is not supported. +AdapterJS.WebRTCPlugin.isPluginInstalled = null; + + // Lets adapter.js wait until the the document is ready before injecting the plugin +AdapterJS.WebRTCPlugin.pluginInjectionInterval = null; + +// Inject the HTML DOM object element into the page. +AdapterJS.WebRTCPlugin.injectPlugin = null; + +// States of readiness that the plugin goes through when +// being injected and stated +AdapterJS.WebRTCPlugin.PLUGIN_STATES = { + NONE : 0, // no plugin use + INITIALIZING : 1, // Detected need for plugin + INJECTING : 2, // Injecting plugin + INJECTED: 3, // Plugin element injected but not usable yet + READY: 4 // Plugin ready to be used +}; + +// Current state of the plugin. You cannot use the plugin before this is +// equal to AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY +AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.NONE; + +// True is AdapterJS.onwebrtcready was already called, false otherwise +// Used to make sure AdapterJS.onwebrtcready is only called once +AdapterJS.onwebrtcreadyDone = false; + +// Log levels for the plugin. +// To be set by calling AdapterJS.WebRTCPlugin.setLogLevel +/* +Log outputs are prefixed in some cases. + INFO: Information reported by the plugin. + ERROR: Errors originating from within the plugin. + WEBRTC: Error originating from within the libWebRTC library +*/ +// From the least verbose to the most verbose +AdapterJS.WebRTCPlugin.PLUGIN_LOG_LEVELS = { + NONE : 'NONE', + ERROR : 'ERROR', + WARNING : 'WARNING', + INFO: 'INFO', + VERBOSE: 'VERBOSE', + SENSITIVE: 'SENSITIVE' +}; + +// Does a waiting check before proceeding to load the plugin. +AdapterJS.WebRTCPlugin.WaitForPluginReady = null; + +// This methid will use an interval to wait for the plugin to be ready. +AdapterJS.WebRTCPlugin.callWhenPluginReady = null; + +// !!!! WARNING: DO NOT OVERRIDE THIS FUNCTION. !!! +// This function will be called when plugin is ready. It sends necessary +// details to the plugin. +// The function will wait for the document to be ready and the set the +// plugin state to AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY, +// indicating that it can start being requested. +// This function is not in the IE/Safari condition brackets so that +// TemPluginLoaded function might be called on Chrome/Firefox. +// This function is the only private function that is not encapsulated to +// allow the plugin method to be called. +__TemWebRTCReady0 = function () { + if (document.readyState === 'complete') { + AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY; + AdapterJS.maybeThroughWebRTCReady(); + } else { + var timer = setInterval(function () { + if (document.readyState === 'complete') { + // TODO: update comments, we wait for the document to be ready + clearInterval(timer); + AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY; + AdapterJS.maybeThroughWebRTCReady(); + } + }, 100); + } +}; + +AdapterJS.maybeThroughWebRTCReady = function() { + if (!AdapterJS.onwebrtcreadyDone) { + AdapterJS.onwebrtcreadyDone = true; + + // If new interface for multiple callbacks used + if (AdapterJS._onwebrtcreadies.length) { + AdapterJS._onwebrtcreadies.forEach(function (callback) { + if (typeof(callback) === 'function') { + callback(AdapterJS.WebRTCPlugin.plugin !== null); + } + }); + // Else if no callbacks on new interface assuming user used old(deprecated) way to set callback through AdapterJS.onwebrtcready = ... + } else if (typeof(AdapterJS.onwebrtcready) === 'function') { + AdapterJS.onwebrtcready(AdapterJS.WebRTCPlugin.plugin !== null); + } + } +}; + +// Text namespace +AdapterJS.TEXT = { + PLUGIN: { + REQUIRE_INSTALLATION: 'This website requires you to install a WebRTC-enabling plugin ' + + 'to work on this browser.', + NOT_SUPPORTED: 'Your browser does not support WebRTC.', + BUTTON: 'Install Now' + }, + REFRESH: { + REQUIRE_REFRESH: 'Please refresh page', + BUTTON: 'Refresh Page' + } +}; + +// The result of ice connection states. +// - starting: Ice connection is starting. +// - checking: Ice connection is checking. +// - connected Ice connection is connected. +// - completed Ice connection is connected. +// - done Ice connection has been completed. +// - disconnected Ice connection has been disconnected. +// - failed Ice connection has failed. +// - closed Ice connection is closed. +AdapterJS._iceConnectionStates = { + starting : 'starting', + checking : 'checking', + connected : 'connected', + completed : 'connected', + done : 'completed', + disconnected : 'disconnected', + failed : 'failed', + closed : 'closed' +}; + +//The IceConnection states that has been fired for each peer. +AdapterJS._iceConnectionFiredStates = []; + + +// Check if WebRTC Interface is defined. +AdapterJS.isDefined = null; + +// This function helps to retrieve the webrtc detected browser information. +// This sets: +// - webrtcDetectedBrowser: The browser agent name. +// - webrtcDetectedVersion: The browser version. +// - webrtcMinimumVersion: The minimum browser version still supported by AJS. +// - webrtcDetectedType: The types of webRTC support. +// - 'moz': Mozilla implementation of webRTC. +// - 'webkit': WebKit implementation of webRTC. +// - 'plugin': Using the plugin implementation. +AdapterJS.parseWebrtcDetectedBrowser = function () { + var hasMatch = null; + if ((!!window.opr && !!opr.addons) || + !!window.opera || + navigator.userAgent.indexOf(' OPR/') >= 0) { + // Opera 8.0+ + webrtcDetectedBrowser = 'opera'; + webrtcDetectedType = 'webkit'; + webrtcMinimumVersion = 26; + hasMatch = /OPR\/(\d+)/i.exec(navigator.userAgent) || []; + webrtcDetectedVersion = parseInt(hasMatch[1], 10); + } else if (typeof InstallTrigger !== 'undefined') { + // Firefox 1.0+ + // Bowser and Version set in Google's adapter + webrtcDetectedType = 'moz'; + } else if (Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0) { + // Safari + webrtcDetectedBrowser = 'safari'; + webrtcDetectedType = 'plugin'; + webrtcMinimumVersion = 7; + hasMatch = /version\/(\d+)/i.exec(navigator.userAgent) || []; + webrtcDetectedVersion = parseInt(hasMatch[1], 10); + } else if (/*@cc_on!@*/false || !!document.documentMode) { + // Internet Explorer 6-11 + webrtcDetectedBrowser = 'IE'; + webrtcDetectedType = 'plugin'; + webrtcMinimumVersion = 9; + hasMatch = /\brv[ :]+(\d+)/g.exec(navigator.userAgent) || []; + webrtcDetectedVersion = parseInt(hasMatch[1] || '0', 10); + if (!webrtcDetectedVersion) { + hasMatch = /\bMSIE[ :]+(\d+)/g.exec(navigator.userAgent) || []; + webrtcDetectedVersion = parseInt(hasMatch[1] || '0', 10); + } + } else if (!!window.StyleMedia) { + // Edge 20+ + // Bowser and Version set in Google's adapter + webrtcDetectedType = ''; + } else if (!!window.chrome && !!window.chrome.webstore) { + // Chrome 1+ + // Bowser and Version set in Google's adapter + webrtcDetectedType = 'webkit'; + } else if ((webrtcDetectedBrowser === 'chrome'|| webrtcDetectedBrowser === 'opera') && + !!window.CSS) { + // Blink engine detection + webrtcDetectedBrowser = 'blink'; + // TODO: detected WebRTC version + } + + window.webrtcDetectedBrowser = webrtcDetectedBrowser; + window.webrtcDetectedVersion = webrtcDetectedVersion; + window.webrtcMinimumVersion = webrtcMinimumVersion; +}; + +AdapterJS.addEvent = function(elem, evnt, func) { + if (elem.addEventListener) { // W3C DOM + elem.addEventListener(evnt, func, false); + } else if (elem.attachEvent) {// OLD IE DOM + elem.attachEvent('on'+evnt, func); + } else { // No much to do + elem[evnt] = func; + } +}; + +AdapterJS.renderNotificationBar = function (text, buttonText, buttonLink, openNewTab, displayRefreshBar) { + // only inject once the page is ready + if (document.readyState !== 'complete') { + return; + } + + var w = window; + var i = document.createElement('iframe'); + i.name = 'adapterjs-alert'; + i.style.position = 'fixed'; + i.style.top = '-41px'; + i.style.left = 0; + i.style.right = 0; + i.style.width = '100%'; + i.style.height = '40px'; + i.style.backgroundColor = '#ffffe1'; + i.style.border = 'none'; + i.style.borderBottom = '1px solid #888888'; + i.style.zIndex = '9999999'; + if(typeof i.style.webkitTransition === 'string') { + i.style.webkitTransition = 'all .5s ease-out'; + } else if(typeof i.style.transition === 'string') { + i.style.transition = 'all .5s ease-out'; + } + document.body.appendChild(i); + var c = (i.contentWindow) ? i.contentWindow : + (i.contentDocument.document) ? i.contentDocument.document : i.contentDocument; + c.document.open(); + c.document.write('' + text + ''); + if(buttonText && buttonLink) { + c.document.write(''); + c.document.close(); + + // On click on okay + AdapterJS.addEvent(c.document.getElementById('okay'), 'click', function(e) { + if (!!displayRefreshBar) { + AdapterJS.renderNotificationBar(AdapterJS.TEXT.EXTENSION ? + AdapterJS.TEXT.EXTENSION.REQUIRE_REFRESH : AdapterJS.TEXT.REFRESH.REQUIRE_REFRESH, + AdapterJS.TEXT.REFRESH.BUTTON, 'javascript:location.reload()'); // jshint ignore:line + } + window.open(buttonLink, !!openNewTab ? '_blank' : '_top'); + + e.preventDefault(); + try { + e.cancelBubble = true; + } catch(error) { } + + var pluginInstallInterval = setInterval(function(){ + if(! isIE) { + navigator.plugins.refresh(false); + } + AdapterJS.WebRTCPlugin.isPluginInstalled( + AdapterJS.WebRTCPlugin.pluginInfo.prefix, + AdapterJS.WebRTCPlugin.pluginInfo.plugName, + function() { // plugin now installed + clearInterval(pluginInstallInterval); + AdapterJS.WebRTCPlugin.defineWebRTCInterface(); + }, + function() { + // still no plugin detected, nothing to do + }); + } , 500); + }); + + // On click on Cancel + AdapterJS.addEvent(c.document.getElementById('cancel'), 'click', function(e) { + w.document.body.removeChild(i); + }); + } else { + c.document.close(); + } + setTimeout(function() { + if(typeof i.style.webkitTransform === 'string') { + i.style.webkitTransform = 'translateY(40px)'; + } else if(typeof i.style.transform === 'string') { + i.style.transform = 'translateY(40px)'; + } else { + i.style.top = '0px'; + } + }, 300); +}; + +// ----------------------------------------------------------- +// Detected webrtc implementation. Types are: +// - 'moz': Mozilla implementation of webRTC. +// - 'webkit': WebKit implementation of webRTC. +// - 'plugin': Using the plugin implementation. +webrtcDetectedType = null; + +// Set the settings for creating DataChannels, MediaStream for +// Cross-browser compability. +// - This is only for SCTP based support browsers. +// the 'urls' attribute. +checkMediaDataChannelSettings = + function (peerBrowserAgent, peerBrowserVersion, callback, constraints) { + if (typeof callback !== 'function') { + return; + } + var beOfferer = true; + var isLocalFirefox = webrtcDetectedBrowser === 'firefox'; + // Nightly version does not require MozDontOfferDataChannel for interop + var isLocalFirefoxInterop = webrtcDetectedType === 'moz' && webrtcDetectedVersion > 30; + var isPeerFirefox = peerBrowserAgent === 'firefox'; + var isPeerFirefoxInterop = peerBrowserAgent === 'firefox' && + ((peerBrowserVersion) ? (peerBrowserVersion > 30) : false); + + // Resends an updated version of constraints for MozDataChannel to work + // If other userAgent is firefox and user is firefox, remove MozDataChannel + if ((isLocalFirefox && isPeerFirefox) || (isLocalFirefoxInterop)) { + try { + delete constraints.mandatory.MozDontOfferDataChannel; + } catch (error) { + console.error('Failed deleting MozDontOfferDataChannel'); + console.error(error); + } + } else if ((isLocalFirefox && !isPeerFirefox)) { + constraints.mandatory.MozDontOfferDataChannel = true; + } + if (!isLocalFirefox) { + // temporary measure to remove Moz* constraints in non Firefox browsers + for (var prop in constraints.mandatory) { + if (constraints.mandatory.hasOwnProperty(prop)) { + if (prop.indexOf('Moz') !== -1) { + delete constraints.mandatory[prop]; + } + } + } + } + // Firefox (not interopable) cannot offer DataChannel as it will cause problems to the + // interopability of the media stream + if (isLocalFirefox && !isPeerFirefox && !isLocalFirefoxInterop) { + beOfferer = false; + } + callback(beOfferer, constraints); +}; + +// Handles the differences for all browsers ice connection state output. +// - Tested outcomes are: +// - Chrome (offerer) : 'checking' > 'completed' > 'completed' +// - Chrome (answerer) : 'checking' > 'connected' +// - Firefox (offerer) : 'checking' > 'connected' +// - Firefox (answerer): 'checking' > 'connected' +checkIceConnectionState = function (peerId, iceConnectionState, callback) { + if (typeof callback !== 'function') { + console.warn('No callback specified in checkIceConnectionState. Aborted.'); + return; + } + peerId = (peerId) ? peerId : 'peer'; + + if (!AdapterJS._iceConnectionFiredStates[peerId] || + iceConnectionState === AdapterJS._iceConnectionStates.disconnected || + iceConnectionState === AdapterJS._iceConnectionStates.failed || + iceConnectionState === AdapterJS._iceConnectionStates.closed) { + AdapterJS._iceConnectionFiredStates[peerId] = []; + } + iceConnectionState = AdapterJS._iceConnectionStates[iceConnectionState]; + if (AdapterJS._iceConnectionFiredStates[peerId].indexOf(iceConnectionState) < 0) { + AdapterJS._iceConnectionFiredStates[peerId].push(iceConnectionState); + if (iceConnectionState === AdapterJS._iceConnectionStates.connected) { + setTimeout(function () { + AdapterJS._iceConnectionFiredStates[peerId] + .push(AdapterJS._iceConnectionStates.done); + callback(AdapterJS._iceConnectionStates.done); + }, 1000); + } + callback(iceConnectionState); + } + return; +}; + +// Firefox: +// - Creates iceServer from the url for Firefox. +// - Create iceServer with stun url. +// - Create iceServer with turn url. +// - Ignore the transport parameter from TURN url for FF version <=27. +// - Return null for createIceServer if transport=tcp. +// - FF 27 and above supports transport parameters in TURN url, +// - So passing in the full url to create iceServer. +// Chrome: +// - Creates iceServer from the url for Chrome M33 and earlier. +// - Create iceServer with stun url. +// - Chrome M28 & above uses below TURN format. +// Plugin: +// - Creates Ice Server for Plugin Browsers +// - If Stun - Create iceServer with stun url. +// - Else - Create iceServer with turn url +// - This is a WebRTC Function +createIceServer = null; + +// Firefox: +// - Creates IceServers for Firefox +// - Use .url for FireFox. +// - Multiple Urls support +// Chrome: +// - Creates iceServers from the urls for Chrome M34 and above. +// - .urls is supported since Chrome M34. +// - Multiple Urls support +// Plugin: +// - Creates Ice Servers for Plugin Browsers +// - Multiple Urls support +// - This is a WebRTC Function +createIceServers = null; +//------------------------------------------------------------ + +//The RTCPeerConnection object. +RTCPeerConnection = null; + +// Creates RTCSessionDescription object for Plugin Browsers +RTCSessionDescription = (typeof RTCSessionDescription === 'function') ? + RTCSessionDescription : null; + +// Creates RTCIceCandidate object for Plugin Browsers +RTCIceCandidate = (typeof RTCIceCandidate === 'function') ? + RTCIceCandidate : null; + +// Get UserMedia (only difference is the prefix). +// Code from Adam Barth. +getUserMedia = null; + +// Attach a media stream to an element. +attachMediaStream = null; + +// Re-attach a media stream to an element. +reattachMediaStream = null; + + +// Detected browser agent name. Types are: +// - 'firefox': Firefox browser. +// - 'chrome': Chrome browser. +// - 'opera': Opera browser. +// - 'safari': Safari browser. +// - 'IE' - Internet Explorer browser. +webrtcDetectedBrowser = null; + +// Detected browser version. +webrtcDetectedVersion = null; + +// The minimum browser version still supported by AJS. +webrtcMinimumVersion = null; + +// Check for browser types and react accordingly +if ( navigator.mozGetUserMedia || + navigator.webkitGetUserMedia || + (navigator.mediaDevices && + navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) ) { + + /////////////////////////////////////////////////////////////////// + // INJECTION OF GOOGLE'S ADAPTER.JS CONTENT + +/* jshint ignore:start */ + (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.adapter = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0 ? 'm=' + part : part).trim() + '\r\n'; + }); + }; + + // Returns lines that start with a certain prefix. + SDPUtils.matchPrefix = function(blob, prefix) { + return SDPUtils.splitLines(blob).filter(function(line) { + return line.indexOf(prefix) === 0; + }); + }; + + // Parses an ICE candidate line. Sample input: + // candidate:702786350 2 udp 41819902 8.8.8.8 60769 typ relay raddr 8.8.8.8 + // rport 55996" + SDPUtils.parseCandidate = function(line) { + var parts; + // Parse both variants. + if (line.indexOf('a=candidate:') === 0) { + parts = line.substring(12).split(' '); + } else { + parts = line.substring(10).split(' '); + } + + var candidate = { + foundation: parts[0], + component: parts[1], + protocol: parts[2].toLowerCase(), + priority: parseInt(parts[3], 10), + ip: parts[4], + port: parseInt(parts[5], 10), + // skip parts[6] == 'typ' + type: parts[7] + }; + + for (var i = 8; i < parts.length; i += 2) { + switch (parts[i]) { + case 'raddr': + candidate.relatedAddress = parts[i + 1]; + break; + case 'rport': + candidate.relatedPort = parseInt(parts[i + 1], 10); + break; + case 'tcptype': + candidate.tcpType = parts[i + 1]; + break; + default: // Unknown extensions are silently ignored. + break; + } + } + return candidate; + }; + + // Translates a candidate object into SDP candidate attribute. + SDPUtils.writeCandidate = function(candidate) { + var sdp = []; + sdp.push(candidate.foundation); + sdp.push(candidate.component); + sdp.push(candidate.protocol.toUpperCase()); + sdp.push(candidate.priority); + sdp.push(candidate.ip); + sdp.push(candidate.port); + + var type = candidate.type; + sdp.push('typ'); + sdp.push(type); + if (type !== 'host' && candidate.relatedAddress && + candidate.relatedPort) { + sdp.push('raddr'); + sdp.push(candidate.relatedAddress); // was: relAddr + sdp.push('rport'); + sdp.push(candidate.relatedPort); // was: relPort + } + if (candidate.tcpType && candidate.protocol.toLowerCase() === 'tcp') { + sdp.push('tcptype'); + sdp.push(candidate.tcpType); + } + return 'candidate:' + sdp.join(' '); + }; + + // Parses an rtpmap line, returns RTCRtpCoddecParameters. Sample input: + // a=rtpmap:111 opus/48000/2 + SDPUtils.parseRtpMap = function(line) { + var parts = line.substr(9).split(' '); + var parsed = { + payloadType: parseInt(parts.shift(), 10) // was: id + }; + + parts = parts[0].split('/'); + + parsed.name = parts[0]; + parsed.clockRate = parseInt(parts[1], 10); // was: clockrate + // was: channels + parsed.numChannels = parts.length === 3 ? parseInt(parts[2], 10) : 1; + return parsed; + }; + + // Generate an a=rtpmap line from RTCRtpCodecCapability or + // RTCRtpCodecParameters. + SDPUtils.writeRtpMap = function(codec) { + var pt = codec.payloadType; + if (codec.preferredPayloadType !== undefined) { + pt = codec.preferredPayloadType; + } + return 'a=rtpmap:' + pt + ' ' + codec.name + '/' + codec.clockRate + + (codec.numChannels !== 1 ? '/' + codec.numChannels : '') + '\r\n'; + }; + + // Parses an a=extmap line (headerextension from RFC 5285). Sample input: + // a=extmap:2 urn:ietf:params:rtp-hdrext:toffset + SDPUtils.parseExtmap = function(line) { + var parts = line.substr(9).split(' '); + return { + id: parseInt(parts[0], 10), + uri: parts[1] + }; + }; + + // Generates a=extmap line from RTCRtpHeaderExtensionParameters or + // RTCRtpHeaderExtension. + SDPUtils.writeExtmap = function(headerExtension) { + return 'a=extmap:' + (headerExtension.id || headerExtension.preferredId) + + ' ' + headerExtension.uri + '\r\n'; + }; + + // Parses an ftmp line, returns dictionary. Sample input: + // a=fmtp:96 vbr=on;cng=on + // Also deals with vbr=on; cng=on + SDPUtils.parseFmtp = function(line) { + var parsed = {}; + var kv; + var parts = line.substr(line.indexOf(' ') + 1).split(';'); + for (var j = 0; j < parts.length; j++) { + kv = parts[j].trim().split('='); + parsed[kv[0].trim()] = kv[1]; + } + return parsed; + }; + + // Generates an a=ftmp line from RTCRtpCodecCapability or RTCRtpCodecParameters. + SDPUtils.writeFmtp = function(codec) { + var line = ''; + var pt = codec.payloadType; + if (codec.preferredPayloadType !== undefined) { + pt = codec.preferredPayloadType; + } + if (codec.parameters && Object.keys(codec.parameters).length) { + var params = []; + Object.keys(codec.parameters).forEach(function(param) { + params.push(param + '=' + codec.parameters[param]); + }); + line += 'a=fmtp:' + pt + ' ' + params.join(';') + '\r\n'; + } + return line; + }; + + // Parses an rtcp-fb line, returns RTCPRtcpFeedback object. Sample input: + // a=rtcp-fb:98 nack rpsi + SDPUtils.parseRtcpFb = function(line) { + var parts = line.substr(line.indexOf(' ') + 1).split(' '); + return { + type: parts.shift(), + parameter: parts.join(' ') + }; + }; + // Generate a=rtcp-fb lines from RTCRtpCodecCapability or RTCRtpCodecParameters. + SDPUtils.writeRtcpFb = function(codec) { + var lines = ''; + var pt = codec.payloadType; + if (codec.preferredPayloadType !== undefined) { + pt = codec.preferredPayloadType; + } + if (codec.rtcpFeedback && codec.rtcpFeedback.length) { + // FIXME: special handling for trr-int? + codec.rtcpFeedback.forEach(function(fb) { + lines += 'a=rtcp-fb:' + pt + ' ' + fb.type + + (fb.parameter && fb.parameter.length ? ' ' + fb.parameter : '') + + '\r\n'; + }); + } + return lines; + }; + + // Parses an RFC 5576 ssrc media attribute. Sample input: + // a=ssrc:3735928559 cname:something + SDPUtils.parseSsrcMedia = function(line) { + var sp = line.indexOf(' '); + var parts = { + ssrc: parseInt(line.substr(7, sp - 7), 10) + }; + var colon = line.indexOf(':', sp); + if (colon > -1) { + parts.attribute = line.substr(sp + 1, colon - sp - 1); + parts.value = line.substr(colon + 1); + } else { + parts.attribute = line.substr(sp + 1); + } + return parts; + }; + + // Extracts DTLS parameters from SDP media section or sessionpart. + // FIXME: for consistency with other functions this should only + // get the fingerprint line as input. See also getIceParameters. + SDPUtils.getDtlsParameters = function(mediaSection, sessionpart) { + var lines = SDPUtils.splitLines(mediaSection); + // Search in session part, too. + lines = lines.concat(SDPUtils.splitLines(sessionpart)); + var fpLine = lines.filter(function(line) { + return line.indexOf('a=fingerprint:') === 0; + })[0].substr(14); + // Note: a=setup line is ignored since we use the 'auto' role. + var dtlsParameters = { + role: 'auto', + fingerprints: [{ + algorithm: fpLine.split(' ')[0], + value: fpLine.split(' ')[1] + }] + }; + return dtlsParameters; + }; + + // Serializes DTLS parameters to SDP. + SDPUtils.writeDtlsParameters = function(params, setupType) { + var sdp = 'a=setup:' + setupType + '\r\n'; + params.fingerprints.forEach(function(fp) { + sdp += 'a=fingerprint:' + fp.algorithm + ' ' + fp.value + '\r\n'; + }); + return sdp; + }; + // Parses ICE information from SDP media section or sessionpart. + // FIXME: for consistency with other functions this should only + // get the ice-ufrag and ice-pwd lines as input. + SDPUtils.getIceParameters = function(mediaSection, sessionpart) { + var lines = SDPUtils.splitLines(mediaSection); + // Search in session part, too. + lines = lines.concat(SDPUtils.splitLines(sessionpart)); + var iceParameters = { + usernameFragment: lines.filter(function(line) { + return line.indexOf('a=ice-ufrag:') === 0; + })[0].substr(12), + password: lines.filter(function(line) { + return line.indexOf('a=ice-pwd:') === 0; + })[0].substr(10) + }; + return iceParameters; + }; + + // Serializes ICE parameters to SDP. + SDPUtils.writeIceParameters = function(params) { + return 'a=ice-ufrag:' + params.usernameFragment + '\r\n' + + 'a=ice-pwd:' + params.password + '\r\n'; + }; + + // Parses the SDP media section and returns RTCRtpParameters. + SDPUtils.parseRtpParameters = function(mediaSection) { + var description = { + codecs: [], + headerExtensions: [], + fecMechanisms: [], + rtcp: [] + }; + var lines = SDPUtils.splitLines(mediaSection); + var mline = lines[0].split(' '); + for (var i = 3; i < mline.length; i++) { // find all codecs from mline[3..] + var pt = mline[i]; + var rtpmapline = SDPUtils.matchPrefix( + mediaSection, 'a=rtpmap:' + pt + ' ')[0]; + if (rtpmapline) { + var codec = SDPUtils.parseRtpMap(rtpmapline); + var fmtps = SDPUtils.matchPrefix( + mediaSection, 'a=fmtp:' + pt + ' '); + // Only the first a=fmtp: is considered. + codec.parameters = fmtps.length ? SDPUtils.parseFmtp(fmtps[0]) : {}; + codec.rtcpFeedback = SDPUtils.matchPrefix( + mediaSection, 'a=rtcp-fb:' + pt + ' ') + .map(SDPUtils.parseRtcpFb); + description.codecs.push(codec); + // parse FEC mechanisms from rtpmap lines. + switch (codec.name.toUpperCase()) { + case 'RED': + case 'ULPFEC': + description.fecMechanisms.push(codec.name.toUpperCase()); + break; + default: // only RED and ULPFEC are recognized as FEC mechanisms. + break; + } + } + } + SDPUtils.matchPrefix(mediaSection, 'a=extmap:').forEach(function(line) { + description.headerExtensions.push(SDPUtils.parseExtmap(line)); + }); + // FIXME: parse rtcp. + return description; + }; + + // Generates parts of the SDP media section describing the capabilities / + // parameters. + SDPUtils.writeRtpDescription = function(kind, caps) { + var sdp = ''; + + // Build the mline. + sdp += 'm=' + kind + ' '; + sdp += caps.codecs.length > 0 ? '9' : '0'; // reject if no codecs. + sdp += ' UDP/TLS/RTP/SAVPF '; + sdp += caps.codecs.map(function(codec) { + if (codec.preferredPayloadType !== undefined) { + return codec.preferredPayloadType; + } + return codec.payloadType; + }).join(' ') + '\r\n'; + + sdp += 'c=IN IP4 0.0.0.0\r\n'; + sdp += 'a=rtcp:9 IN IP4 0.0.0.0\r\n'; + + // Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb. + caps.codecs.forEach(function(codec) { + sdp += SDPUtils.writeRtpMap(codec); + sdp += SDPUtils.writeFmtp(codec); + sdp += SDPUtils.writeRtcpFb(codec); + }); + // FIXME: add headerExtensions, fecMechanismş and rtcp. + sdp += 'a=rtcp-mux\r\n'; + return sdp; + }; + + // Parses the SDP media section and returns an array of + // RTCRtpEncodingParameters. + SDPUtils.parseRtpEncodingParameters = function(mediaSection) { + var encodingParameters = []; + var description = SDPUtils.parseRtpParameters(mediaSection); + var hasRed = description.fecMechanisms.indexOf('RED') !== -1; + var hasUlpfec = description.fecMechanisms.indexOf('ULPFEC') !== -1; + + // filter a=ssrc:... cname:, ignore PlanB-msid + var ssrcs = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:') + .map(function(line) { + return SDPUtils.parseSsrcMedia(line); + }) + .filter(function(parts) { + return parts.attribute === 'cname'; + }); + var primarySsrc = ssrcs.length > 0 && ssrcs[0].ssrc; + var secondarySsrc; + + var flows = SDPUtils.matchPrefix(mediaSection, 'a=ssrc-group:FID') + .map(function(line) { + var parts = line.split(' '); + parts.shift(); + return parts.map(function(part) { + return parseInt(part, 10); + }); + }); + if (flows.length > 0 && flows[0].length > 1 && flows[0][0] === primarySsrc) { + secondarySsrc = flows[0][1]; + } + + description.codecs.forEach(function(codec) { + if (codec.name.toUpperCase() === 'RTX' && codec.parameters.apt) { + var encParam = { + ssrc: primarySsrc, + codecPayloadType: parseInt(codec.parameters.apt, 10), + rtx: { + payloadType: codec.payloadType, + ssrc: secondarySsrc + } + }; + encodingParameters.push(encParam); + if (hasRed) { + encParam = JSON.parse(JSON.stringify(encParam)); + encParam.fec = { + ssrc: secondarySsrc, + mechanism: hasUlpfec ? 'red+ulpfec' : 'red' + }; + encodingParameters.push(encParam); + } + } + }); + if (encodingParameters.length === 0 && primarySsrc) { + encodingParameters.push({ + ssrc: primarySsrc + }); + } + + // we support both b=AS and b=TIAS but interpret AS as TIAS. + var bandwidth = SDPUtils.matchPrefix(mediaSection, 'b='); + if (bandwidth.length) { + if (bandwidth[0].indexOf('b=TIAS:') === 0) { + bandwidth = parseInt(bandwidth[0].substr(7), 10); + } else if (bandwidth[0].indexOf('b=AS:') === 0) { + bandwidth = parseInt(bandwidth[0].substr(5), 10); + } + encodingParameters.forEach(function(params) { + params.maxBitrate = bandwidth; + }); + } + return encodingParameters; + }; + + SDPUtils.writeSessionBoilerplate = function() { + // FIXME: sess-id should be an NTP timestamp. + return 'v=0\r\n' + + 'o=thisisadapterortc 8169639915646943137 2 IN IP4 127.0.0.1\r\n' + + 's=-\r\n' + + 't=0 0\r\n'; + }; + + SDPUtils.writeMediaSection = function(transceiver, caps, type, stream) { + var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps); + + // Map ICE parameters (ufrag, pwd) to SDP. + sdp += SDPUtils.writeIceParameters( + transceiver.iceGatherer.getLocalParameters()); + + // Map DTLS parameters to SDP. + sdp += SDPUtils.writeDtlsParameters( + transceiver.dtlsTransport.getLocalParameters(), + type === 'offer' ? 'actpass' : 'active'); + + sdp += 'a=mid:' + transceiver.mid + '\r\n'; + + if (transceiver.rtpSender && transceiver.rtpReceiver) { + sdp += 'a=sendrecv\r\n'; + } else if (transceiver.rtpSender) { + sdp += 'a=sendonly\r\n'; + } else if (transceiver.rtpReceiver) { + sdp += 'a=recvonly\r\n'; + } else { + sdp += 'a=inactive\r\n'; + } + + // FIXME: for RTX there might be multiple SSRCs. Not implemented in Edge yet. + if (transceiver.rtpSender) { + var msid = 'msid:' + stream.id + ' ' + + transceiver.rtpSender.track.id + '\r\n'; + sdp += 'a=' + msid; + sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc + + ' ' + msid; + } + // FIXME: this should be written by writeRtpDescription. + sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc + + ' cname:' + SDPUtils.localCName + '\r\n'; + return sdp; + }; + + // Gets the direction from the mediaSection or the sessionpart. + SDPUtils.getDirection = function(mediaSection, sessionpart) { + // Look for sendrecv, sendonly, recvonly, inactive, default to sendrecv. + var lines = SDPUtils.splitLines(mediaSection); + for (var i = 0; i < lines.length; i++) { + switch (lines[i]) { + case 'a=sendrecv': + case 'a=sendonly': + case 'a=recvonly': + case 'a=inactive': + return lines[i].substr(2); + default: + // FIXME: What should happen here? + } + } + if (sessionpart) { + return SDPUtils.getDirection(sessionpart); + } + return 'sendrecv'; + }; + + // Expose public methods. + module.exports = SDPUtils; + + },{}],2:[function(require,module,exports){ + /* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ + + 'use strict'; + + // Shimming starts here. + (function() { + // Utils. + var logging = require('./utils').log; + var browserDetails = require('./utils').browserDetails; + // Export to the adapter global object visible in the browser. + module.exports.browserDetails = browserDetails; + module.exports.extractVersion = require('./utils').extractVersion; + module.exports.disableLog = require('./utils').disableLog; + + // Uncomment the line below if you want logging to occur, including logging + // for the switch statement below. Can also be turned on in the browser via + // adapter.disableLog(false), but then logging from the switch statement below + // will not appear. + // require('./utils').disableLog(false); + + // Browser shims. + var chromeShim = require('./chrome/chrome_shim') || null; + var edgeShim = require('./edge/edge_shim') || null; + var firefoxShim = require('./firefox/firefox_shim') || null; + var safariShim = require('./safari/safari_shim') || null; + + // Shim browser if found. + switch (browserDetails.browser) { + case 'opera': // fallthrough as it uses chrome shims + case 'chrome': + if (!chromeShim || !chromeShim.shimPeerConnection) { + logging('Chrome shim is not included in this adapter release.'); + return; + } + logging('adapter.js shimming chrome.'); + // Export to the adapter global object visible in the browser. + module.exports.browserShim = chromeShim; + + chromeShim.shimGetUserMedia(); + chromeShim.shimMediaStream(); + chromeShim.shimSourceObject(); + chromeShim.shimPeerConnection(); + chromeShim.shimOnTrack(); + break; + case 'firefox': + if (!firefoxShim || !firefoxShim.shimPeerConnection) { + logging('Firefox shim is not included in this adapter release.'); + return; + } + logging('adapter.js shimming firefox.'); + // Export to the adapter global object visible in the browser. + module.exports.browserShim = firefoxShim; + + firefoxShim.shimGetUserMedia(); + firefoxShim.shimSourceObject(); + firefoxShim.shimPeerConnection(); + firefoxShim.shimOnTrack(); + break; + case 'edge': + if (!edgeShim || !edgeShim.shimPeerConnection) { + logging('MS edge shim is not included in this adapter release.'); + return; + } + logging('adapter.js shimming edge.'); + // Export to the adapter global object visible in the browser. + module.exports.browserShim = edgeShim; + + edgeShim.shimGetUserMedia(); + edgeShim.shimPeerConnection(); + break; + case 'safari': + if (!safariShim) { + logging('Safari shim is not included in this adapter release.'); + return; + } + logging('adapter.js shimming safari.'); + // Export to the adapter global object visible in the browser. + module.exports.browserShim = safariShim; + + safariShim.shimGetUserMedia(); + break; + default: + logging('Unsupported browser!'); + } + })(); + + },{"./chrome/chrome_shim":3,"./edge/edge_shim":5,"./firefox/firefox_shim":7,"./safari/safari_shim":9,"./utils":10}],3:[function(require,module,exports){ + + /* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ + 'use strict'; + var logging = require('../utils.js').log; + var browserDetails = require('../utils.js').browserDetails; + + var chromeShim = { + shimMediaStream: function() { + window.MediaStream = window.MediaStream || window.webkitMediaStream; + }, + + shimOnTrack: function() { + if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in + window.RTCPeerConnection.prototype)) { + Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', { + get: function() { + return this._ontrack; + }, + set: function(f) { + var self = this; + if (this._ontrack) { + this.removeEventListener('track', this._ontrack); + this.removeEventListener('addstream', this._ontrackpoly); + } + this.addEventListener('track', this._ontrack = f); + this.addEventListener('addstream', this._ontrackpoly = function(e) { + // onaddstream does not fire when a track is added to an existing + // stream. But stream.onaddtrack is implemented so we use that. + e.stream.addEventListener('addtrack', function(te) { + var event = new Event('track'); + event.track = te.track; + event.receiver = {track: te.track}; + event.streams = [e.stream]; + self.dispatchEvent(event); + }); + e.stream.getTracks().forEach(function(track) { + var event = new Event('track'); + event.track = track; + event.receiver = {track: track}; + event.streams = [e.stream]; + this.dispatchEvent(event); + }.bind(this)); + }.bind(this)); + } + }); + } + }, + + shimSourceObject: function() { + if (typeof window === 'object') { + if (window.HTMLMediaElement && + !('srcObject' in window.HTMLMediaElement.prototype)) { + // Shim the srcObject property, once, when HTMLMediaElement is found. + Object.defineProperty(window.HTMLMediaElement.prototype, 'srcObject', { + get: function() { + return this._srcObject; + }, + set: function(stream) { + var self = this; + // Use _srcObject as a private property for this shim + this._srcObject = stream; + if (this.src) { + URL.revokeObjectURL(this.src); + } + + if (!stream) { + this.src = ''; + return; + } + this.src = URL.createObjectURL(stream); + // We need to recreate the blob url when a track is added or + // removed. Doing it manually since we want to avoid a recursion. + stream.addEventListener('addtrack', function() { + if (self.src) { + URL.revokeObjectURL(self.src); + } + self.src = URL.createObjectURL(stream); + }); + stream.addEventListener('removetrack', function() { + if (self.src) { + URL.revokeObjectURL(self.src); + } + self.src = URL.createObjectURL(stream); + }); + } + }); + } + } + }, + + shimPeerConnection: function() { + // The RTCPeerConnection object. + window.RTCPeerConnection = function(pcConfig, pcConstraints) { + // Translate iceTransportPolicy to iceTransports, + // see https://code.google.com/p/webrtc/issues/detail?id=4869 + logging('PeerConnection'); + if (pcConfig && pcConfig.iceTransportPolicy) { + pcConfig.iceTransports = pcConfig.iceTransportPolicy; + } + + var pc = new webkitRTCPeerConnection(pcConfig, pcConstraints); + var origGetStats = pc.getStats.bind(pc); + pc.getStats = function(selector, successCallback, errorCallback) { + var self = this; + var args = arguments; + + // If selector is a function then we are in the old style stats so just + // pass back the original getStats format to avoid breaking old users. + if (arguments.length > 0 && typeof selector === 'function') { + return origGetStats(selector, successCallback); + } + + var fixChromeStats_ = function(response) { + var standardReport = {}; + var reports = response.result(); + reports.forEach(function(report) { + var standardStats = { + id: report.id, + timestamp: report.timestamp, + type: report.type + }; + report.names().forEach(function(name) { + standardStats[name] = report.stat(name); + }); + standardReport[standardStats.id] = standardStats; + }); + + return standardReport; + }; + + // shim getStats with maplike support + var makeMapStats = function(stats, legacyStats) { + var map = new Map(Object.keys(stats).map(function(key) { + return[key, stats[key]]; + })); + legacyStats = legacyStats || stats; + Object.keys(legacyStats).forEach(function(key) { + map[key] = legacyStats[key]; + }); + return map; + }; + + if (arguments.length >= 2) { + var successCallbackWrapper_ = function(response) { + args[1](makeMapStats(fixChromeStats_(response))); + }; + + return origGetStats.apply(this, [successCallbackWrapper_, + arguments[0]]); + } + + // promise-support + return new Promise(function(resolve, reject) { + if (args.length === 1 && typeof selector === 'object') { + origGetStats.apply(self, [ + function(response) { + resolve(makeMapStats(fixChromeStats_(response))); + }, reject]); + } else { + // Preserve legacy chrome stats only on legacy access of stats obj + origGetStats.apply(self, [ + function(response) { + resolve(makeMapStats(fixChromeStats_(response), + response.result())); + }, reject]); + } + }).then(successCallback, errorCallback); + }; + + return pc; + }; + window.RTCPeerConnection.prototype = webkitRTCPeerConnection.prototype; + + // wrap static methods. Currently just generateCertificate. + if (webkitRTCPeerConnection.generateCertificate) { + Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', { + get: function() { + return webkitRTCPeerConnection.generateCertificate; + } + }); + } + + ['createOffer', 'createAnswer'].forEach(function(method) { + var nativeMethod = webkitRTCPeerConnection.prototype[method]; + webkitRTCPeerConnection.prototype[method] = function() { + var self = this; + if (arguments.length < 1 || (arguments.length === 1 && + typeof arguments[0] === 'object')) { + var opts = arguments.length === 1 ? arguments[0] : undefined; + return new Promise(function(resolve, reject) { + nativeMethod.apply(self, [resolve, reject, opts]); + }); + } + return nativeMethod.apply(this, arguments); + }; + }); + + // add promise support -- natively available in Chrome 51 + if (browserDetails.version < 51) { + ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'] + .forEach(function(method) { + var nativeMethod = webkitRTCPeerConnection.prototype[method]; + webkitRTCPeerConnection.prototype[method] = function() { + var args = arguments; + var self = this; + var promise = new Promise(function(resolve, reject) { + nativeMethod.apply(self, [args[0], resolve, reject]); + }); + if (args.length < 2) { + return promise; + } + return promise.then(function() { + args[1].apply(null, []); + }, + function(err) { + if (args.length >= 3) { + args[2].apply(null, [err]); + } + }); + }; + }); + } + + // shim implicit creation of RTCSessionDescription/RTCIceCandidate + ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'] + .forEach(function(method) { + var nativeMethod = webkitRTCPeerConnection.prototype[method]; + webkitRTCPeerConnection.prototype[method] = function() { + arguments[0] = new ((method === 'addIceCandidate') ? + RTCIceCandidate : RTCSessionDescription)(arguments[0]); + return nativeMethod.apply(this, arguments); + }; + }); + + // support for addIceCandidate(null) + var nativeAddIceCandidate = + RTCPeerConnection.prototype.addIceCandidate; + RTCPeerConnection.prototype.addIceCandidate = function() { + return arguments[0] === null ? Promise.resolve() + : nativeAddIceCandidate.apply(this, arguments); + }; + } + }; + + + // Expose public methods. + module.exports = { + shimMediaStream: chromeShim.shimMediaStream, + shimOnTrack: chromeShim.shimOnTrack, + shimSourceObject: chromeShim.shimSourceObject, + shimPeerConnection: chromeShim.shimPeerConnection, + shimGetUserMedia: require('./getusermedia') + }; + + },{"../utils.js":10,"./getusermedia":4}],4:[function(require,module,exports){ + /* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ + 'use strict'; + var logging = require('../utils.js').log; + + // Expose public methods. + module.exports = function() { + var constraintsToChrome_ = function(c) { + if (typeof c !== 'object' || c.mandatory || c.optional) { + return c; + } + var cc = {}; + Object.keys(c).forEach(function(key) { + if (key === 'require' || key === 'advanced' || key === 'mediaSource') { + return; + } + var r = (typeof c[key] === 'object') ? c[key] : {ideal: c[key]}; + if (r.exact !== undefined && typeof r.exact === 'number') { + r.min = r.max = r.exact; + } + var oldname_ = function(prefix, name) { + if (prefix) { + return prefix + name.charAt(0).toUpperCase() + name.slice(1); + } + return (name === 'deviceId') ? 'sourceId' : name; + }; + if (r.ideal !== undefined) { + cc.optional = cc.optional || []; + var oc = {}; + if (typeof r.ideal === 'number') { + oc[oldname_('min', key)] = r.ideal; + cc.optional.push(oc); + oc = {}; + oc[oldname_('max', key)] = r.ideal; + cc.optional.push(oc); + } else { + oc[oldname_('', key)] = r.ideal; + cc.optional.push(oc); + } + } + if (r.exact !== undefined && typeof r.exact !== 'number') { + cc.mandatory = cc.mandatory || {}; + cc.mandatory[oldname_('', key)] = r.exact; + } else { + ['min', 'max'].forEach(function(mix) { + if (r[mix] !== undefined) { + cc.mandatory = cc.mandatory || {}; + cc.mandatory[oldname_(mix, key)] = r[mix]; + } + }); + } + }); + if (c.advanced) { + cc.optional = (cc.optional || []).concat(c.advanced); + } + return cc; + }; + + var shimConstraints_ = function(constraints, func) { + constraints = JSON.parse(JSON.stringify(constraints)); + if (constraints && constraints.audio) { + constraints.audio = constraintsToChrome_(constraints.audio); + } + if (constraints && typeof constraints.video === 'object') { + // Shim facingMode for mobile, where it defaults to "user". + var face = constraints.video.facingMode; + face = face && ((typeof face === 'object') ? face : {ideal: face}); + + if ((face && (face.exact === 'user' || face.exact === 'environment' || + face.ideal === 'user' || face.ideal === 'environment')) && + !(navigator.mediaDevices.getSupportedConstraints && + navigator.mediaDevices.getSupportedConstraints().facingMode)) { + delete constraints.video.facingMode; + if (face.exact === 'environment' || face.ideal === 'environment') { + // Look for "back" in label, or use last cam (typically back cam). + return navigator.mediaDevices.enumerateDevices() + .then(function(devices) { + devices = devices.filter(function(d) { + return d.kind === 'videoinput'; + }); + var back = devices.find(function(d) { + return d.label.toLowerCase().indexOf('back') !== -1; + }) || (devices.length && devices[devices.length - 1]); + if (back) { + constraints.video.deviceId = face.exact ? {exact: back.deviceId} : + {ideal: back.deviceId}; + } + constraints.video = constraintsToChrome_(constraints.video); + logging('chrome: ' + JSON.stringify(constraints)); + return func(constraints); + }); + } + } + constraints.video = constraintsToChrome_(constraints.video); + } + logging('chrome: ' + JSON.stringify(constraints)); + return func(constraints); + }; + + var shimError_ = function(e) { + return { + name: { + PermissionDeniedError: 'NotAllowedError', + ConstraintNotSatisfiedError: 'OverconstrainedError' + }[e.name] || e.name, + message: e.message, + constraint: e.constraintName, + toString: function() { + return this.name + (this.message && ': ') + this.message; + } + }; + }; + + var getUserMedia_ = function(constraints, onSuccess, onError) { + shimConstraints_(constraints, function(c) { + navigator.webkitGetUserMedia(c, onSuccess, function(e) { + onError(shimError_(e)); + }); + }); + }; + + navigator.getUserMedia = getUserMedia_; + + // Returns the result of getUserMedia as a Promise. + var getUserMediaPromise_ = function(constraints) { + return new Promise(function(resolve, reject) { + navigator.getUserMedia(constraints, resolve, reject); + }); + }; + + if (!navigator.mediaDevices) { + navigator.mediaDevices = { + getUserMedia: getUserMediaPromise_, + enumerateDevices: function() { + return new Promise(function(resolve) { + var kinds = {audio: 'audioinput', video: 'videoinput'}; + return MediaStreamTrack.getSources(function(devices) { + resolve(devices.map(function(device) { + return {label: device.label, + kind: kinds[device.kind], + deviceId: device.id, + groupId: ''}; + })); + }); + }); + } + }; + } + + // A shim for getUserMedia method on the mediaDevices object. + // TODO(KaptenJansson) remove once implemented in Chrome stable. + if (!navigator.mediaDevices.getUserMedia) { + navigator.mediaDevices.getUserMedia = function(constraints) { + return getUserMediaPromise_(constraints); + }; + } else { + // Even though Chrome 45 has navigator.mediaDevices and a getUserMedia + // function which returns a Promise, it does not accept spec-style + // constraints. + var origGetUserMedia = navigator.mediaDevices.getUserMedia. + bind(navigator.mediaDevices); + navigator.mediaDevices.getUserMedia = function(cs) { + return shimConstraints_(cs, function(c) { + return origGetUserMedia(c).catch(function(e) { + return Promise.reject(shimError_(e)); + }); + }); + }; + } + + // Dummy devicechange event methods. + // TODO(KaptenJansson) remove once implemented in Chrome stable. + if (typeof navigator.mediaDevices.addEventListener === 'undefined') { + navigator.mediaDevices.addEventListener = function() { + logging('Dummy mediaDevices.addEventListener called.'); + }; + } + if (typeof navigator.mediaDevices.removeEventListener === 'undefined') { + navigator.mediaDevices.removeEventListener = function() { + logging('Dummy mediaDevices.removeEventListener called.'); + }; + } + }; + + },{"../utils.js":10}],5:[function(require,module,exports){ + /* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ + 'use strict'; + + var SDPUtils = require('sdp'); + + var edgeShim = { + shimPeerConnection: function() { + if (window.RTCIceGatherer) { + // ORTC defines an RTCIceCandidate object but no constructor. + // Not implemented in Edge. + if (!window.RTCIceCandidate) { + window.RTCIceCandidate = function(args) { + return args; + }; + } + // ORTC does not have a session description object but + // other browsers (i.e. Chrome) that will support both PC and ORTC + // in the future might have this defined already. + if (!window.RTCSessionDescription) { + window.RTCSessionDescription = function(args) { + return args; + }; + } + } + + window.RTCPeerConnection = function(config) { + var self = this; + + var _eventTarget = document.createDocumentFragment(); + ['addEventListener', 'removeEventListener', 'dispatchEvent'] + .forEach(function(method) { + self[method] = _eventTarget[method].bind(_eventTarget); + }); + + this.onicecandidate = null; + this.onaddstream = null; + this.ontrack = null; + this.onremovestream = null; + this.onsignalingstatechange = null; + this.oniceconnectionstatechange = null; + this.onnegotiationneeded = null; + this.ondatachannel = null; + + this.localStreams = []; + this.remoteStreams = []; + this.getLocalStreams = function() { + return self.localStreams; + }; + this.getRemoteStreams = function() { + return self.remoteStreams; + }; + + this.localDescription = new RTCSessionDescription({ + type: '', + sdp: '' + }); + this.remoteDescription = new RTCSessionDescription({ + type: '', + sdp: '' + }); + this.signalingState = 'stable'; + this.iceConnectionState = 'new'; + this.iceGatheringState = 'new'; + + this.iceOptions = { + gatherPolicy: 'all', + iceServers: [] + }; + if (config && config.iceTransportPolicy) { + switch (config.iceTransportPolicy) { + case 'all': + case 'relay': + this.iceOptions.gatherPolicy = config.iceTransportPolicy; + break; + case 'none': + // FIXME: remove once implementation and spec have added this. + throw new TypeError('iceTransportPolicy "none" not supported'); + default: + // don't set iceTransportPolicy. + break; + } + } + this.usingBundle = config && config.bundlePolicy === 'max-bundle'; + + if (config && config.iceServers) { + // Edge does not like + // 1) stun: + // 2) turn: that does not have all of turn:host:port?transport=udp + // 3) turn: with ipv6 addresses + var iceServers = JSON.parse(JSON.stringify(config.iceServers)); + this.iceOptions.iceServers = iceServers.filter(function(server) { + if (server && server.urls) { + var urls = server.urls; + if (typeof urls === 'string') { + urls = [urls]; + } + urls = urls.filter(function(url) { + return url.indexOf('turn:') === 0 && + url.indexOf('transport=udp') !== -1 && + url.indexOf('turn:[') === -1; + })[0]; + return !!urls; + } + return false; + }); + } + + // per-track iceGathers, iceTransports, dtlsTransports, rtpSenders, ... + // everything that is needed to describe a SDP m-line. + this.transceivers = []; + + // since the iceGatherer is currently created in createOffer but we + // must not emit candidates until after setLocalDescription we buffer + // them in this array. + this._localIceCandidatesBuffer = []; + }; + + window.RTCPeerConnection.prototype._emitBufferedCandidates = function() { + var self = this; + var sections = SDPUtils.splitSections(self.localDescription.sdp); + // FIXME: need to apply ice candidates in a way which is async but + // in-order + this._localIceCandidatesBuffer.forEach(function(event) { + var end = !event.candidate || Object.keys(event.candidate).length === 0; + if (end) { + for (var j = 1; j < sections.length; j++) { + if (sections[j].indexOf('\r\na=end-of-candidates\r\n') === -1) { + sections[j] += 'a=end-of-candidates\r\n'; + } + } + } else if (event.candidate.candidate.indexOf('typ endOfCandidates') + === -1) { + sections[event.candidate.sdpMLineIndex + 1] += + 'a=' + event.candidate.candidate + '\r\n'; + } + self.localDescription.sdp = sections.join(''); + self.dispatchEvent(event); + if (self.onicecandidate !== null) { + self.onicecandidate(event); + } + if (!event.candidate && self.iceGatheringState !== 'complete') { + var complete = self.transceivers.every(function(transceiver) { + return transceiver.iceGatherer && + transceiver.iceGatherer.state === 'completed'; + }); + if (complete) { + self.iceGatheringState = 'complete'; + } + } + }); + this._localIceCandidatesBuffer = []; + }; + + window.RTCPeerConnection.prototype.addStream = function(stream) { + // Clone is necessary for local demos mostly, attaching directly + // to two different senders does not work (build 10547). + this.localStreams.push(stream.clone()); + this._maybeFireNegotiationNeeded(); + }; + + window.RTCPeerConnection.prototype.removeStream = function(stream) { + var idx = this.localStreams.indexOf(stream); + if (idx > -1) { + this.localStreams.splice(idx, 1); + this._maybeFireNegotiationNeeded(); + } + }; + + window.RTCPeerConnection.prototype.getSenders = function() { + return this.transceivers.filter(function(transceiver) { + return !!transceiver.rtpSender; + }) + .map(function(transceiver) { + return transceiver.rtpSender; + }); + }; + + window.RTCPeerConnection.prototype.getReceivers = function() { + return this.transceivers.filter(function(transceiver) { + return !!transceiver.rtpReceiver; + }) + .map(function(transceiver) { + return transceiver.rtpReceiver; + }); + }; + + // Determines the intersection of local and remote capabilities. + window.RTCPeerConnection.prototype._getCommonCapabilities = + function(localCapabilities, remoteCapabilities) { + var commonCapabilities = { + codecs: [], + headerExtensions: [], + fecMechanisms: [] + }; + localCapabilities.codecs.forEach(function(lCodec) { + for (var i = 0; i < remoteCapabilities.codecs.length; i++) { + var rCodec = remoteCapabilities.codecs[i]; + if (lCodec.name.toLowerCase() === rCodec.name.toLowerCase() && + lCodec.clockRate === rCodec.clockRate && + lCodec.numChannels === rCodec.numChannels) { + // push rCodec so we reply with offerer payload type + commonCapabilities.codecs.push(rCodec); + + // determine common feedback mechanisms + rCodec.rtcpFeedback = rCodec.rtcpFeedback.filter(function(fb) { + for (var j = 0; j < lCodec.rtcpFeedback.length; j++) { + if (lCodec.rtcpFeedback[j].type === fb.type && + lCodec.rtcpFeedback[j].parameter === fb.parameter) { + return true; + } + } + return false; + }); + // FIXME: also need to determine .parameters + // see https://github.com/openpeer/ortc/issues/569 + break; + } + } + }); + + localCapabilities.headerExtensions + .forEach(function(lHeaderExtension) { + for (var i = 0; i < remoteCapabilities.headerExtensions.length; + i++) { + var rHeaderExtension = remoteCapabilities.headerExtensions[i]; + if (lHeaderExtension.uri === rHeaderExtension.uri) { + commonCapabilities.headerExtensions.push(rHeaderExtension); + break; + } + } + }); + + // FIXME: fecMechanisms + return commonCapabilities; + }; + + // Create ICE gatherer, ICE transport and DTLS transport. + window.RTCPeerConnection.prototype._createIceAndDtlsTransports = + function(mid, sdpMLineIndex) { + var self = this; + var iceGatherer = new RTCIceGatherer(self.iceOptions); + var iceTransport = new RTCIceTransport(iceGatherer); + iceGatherer.onlocalcandidate = function(evt) { + var event = new Event('icecandidate'); + event.candidate = {sdpMid: mid, sdpMLineIndex: sdpMLineIndex}; + + var cand = evt.candidate; + var end = !cand || Object.keys(cand).length === 0; + // Edge emits an empty object for RTCIceCandidateComplete‥ + if (end) { + // polyfill since RTCIceGatherer.state is not implemented in + // Edge 10547 yet. + if (iceGatherer.state === undefined) { + iceGatherer.state = 'completed'; + } + + // Emit a candidate with type endOfCandidates to make the samples + // work. Edge requires addIceCandidate with this empty candidate + // to start checking. The real solution is to signal + // end-of-candidates to the other side when getting the null + // candidate but some apps (like the samples) don't do that. + event.candidate.candidate = + 'candidate:1 1 udp 1 0.0.0.0 9 typ endOfCandidates'; + } else { + // RTCIceCandidate doesn't have a component, needs to be added + cand.component = iceTransport.component === 'RTCP' ? 2 : 1; + event.candidate.candidate = SDPUtils.writeCandidate(cand); + } + + // update local description. + var sections = SDPUtils.splitSections(self.localDescription.sdp); + if (event.candidate.candidate.indexOf('typ endOfCandidates') + === -1) { + sections[event.candidate.sdpMLineIndex + 1] += + 'a=' + event.candidate.candidate + '\r\n'; + } else { + sections[event.candidate.sdpMLineIndex + 1] += + 'a=end-of-candidates\r\n'; + } + self.localDescription.sdp = sections.join(''); + + var complete = self.transceivers.every(function(transceiver) { + return transceiver.iceGatherer && + transceiver.iceGatherer.state === 'completed'; + }); + + // Emit candidate if localDescription is set. + // Also emits null candidate when all gatherers are complete. + switch (self.iceGatheringState) { + case 'new': + self._localIceCandidatesBuffer.push(event); + if (end && complete) { + self._localIceCandidatesBuffer.push( + new Event('icecandidate')); + } + break; + case 'gathering': + self._emitBufferedCandidates(); + self.dispatchEvent(event); + if (self.onicecandidate !== null) { + self.onicecandidate(event); + } + if (complete) { + self.dispatchEvent(new Event('icecandidate')); + if (self.onicecandidate !== null) { + self.onicecandidate(new Event('icecandidate')); + } + self.iceGatheringState = 'complete'; + } + break; + case 'complete': + // should not happen... currently! + break; + default: // no-op. + break; + } + }; + iceTransport.onicestatechange = function() { + self._updateConnectionState(); + }; + + var dtlsTransport = new RTCDtlsTransport(iceTransport); + dtlsTransport.ondtlsstatechange = function() { + self._updateConnectionState(); + }; + dtlsTransport.onerror = function() { + // onerror does not set state to failed by itself. + dtlsTransport.state = 'failed'; + self._updateConnectionState(); + }; + + return { + iceGatherer: iceGatherer, + iceTransport: iceTransport, + dtlsTransport: dtlsTransport + }; + }; + + // Start the RTP Sender and Receiver for a transceiver. + window.RTCPeerConnection.prototype._transceive = function(transceiver, + send, recv) { + var params = this._getCommonCapabilities(transceiver.localCapabilities, + transceiver.remoteCapabilities); + if (send && transceiver.rtpSender) { + params.encodings = transceiver.sendEncodingParameters; + params.rtcp = { + cname: SDPUtils.localCName + }; + if (transceiver.recvEncodingParameters.length) { + params.rtcp.ssrc = transceiver.recvEncodingParameters[0].ssrc; + } + transceiver.rtpSender.send(params); + } + if (recv && transceiver.rtpReceiver) { + params.encodings = transceiver.recvEncodingParameters; + params.rtcp = { + cname: transceiver.cname + }; + if (transceiver.sendEncodingParameters.length) { + params.rtcp.ssrc = transceiver.sendEncodingParameters[0].ssrc; + } + transceiver.rtpReceiver.receive(params); + } + }; + + window.RTCPeerConnection.prototype.setLocalDescription = + function(description) { + var self = this; + var sections; + var sessionpart; + if (description.type === 'offer') { + // FIXME: What was the purpose of this empty if statement? + // if (!this._pendingOffer) { + // } else { + if (this._pendingOffer) { + // VERY limited support for SDP munging. Limited to: + // * changing the order of codecs + sections = SDPUtils.splitSections(description.sdp); + sessionpart = sections.shift(); + sections.forEach(function(mediaSection, sdpMLineIndex) { + var caps = SDPUtils.parseRtpParameters(mediaSection); + self._pendingOffer[sdpMLineIndex].localCapabilities = caps; + }); + this.transceivers = this._pendingOffer; + delete this._pendingOffer; + } + } else if (description.type === 'answer') { + sections = SDPUtils.splitSections(self.remoteDescription.sdp); + sessionpart = sections.shift(); + var isIceLite = SDPUtils.matchPrefix(sessionpart, + 'a=ice-lite').length > 0; + sections.forEach(function(mediaSection, sdpMLineIndex) { + var transceiver = self.transceivers[sdpMLineIndex]; + var iceGatherer = transceiver.iceGatherer; + var iceTransport = transceiver.iceTransport; + var dtlsTransport = transceiver.dtlsTransport; + var localCapabilities = transceiver.localCapabilities; + var remoteCapabilities = transceiver.remoteCapabilities; + var rejected = mediaSection.split('\n', 1)[0] + .split(' ', 2)[1] === '0'; + + if (!rejected) { + var remoteIceParameters = SDPUtils.getIceParameters( + mediaSection, sessionpart); + if (isIceLite) { + var cands = SDPUtils.matchPrefix(mediaSection, 'a=candidate:') + .map(function(cand) { + return SDPUtils.parseCandidate(cand); + }) + .filter(function(cand) { + return cand.component === '1'; + }); + // ice-lite only includes host candidates in the SDP so we can + // use setRemoteCandidates (which implies an + // RTCIceCandidateComplete) + if (cands.length) { + iceTransport.setRemoteCandidates(cands); + } + } + var remoteDtlsParameters = SDPUtils.getDtlsParameters( + mediaSection, sessionpart); + if (isIceLite) { + remoteDtlsParameters.role = 'server'; + } + + if (!self.usingBundle || sdpMLineIndex === 0) { + iceTransport.start(iceGatherer, remoteIceParameters, + isIceLite ? 'controlling' : 'controlled'); + dtlsTransport.start(remoteDtlsParameters); + } + + // Calculate intersection of capabilities. + var params = self._getCommonCapabilities(localCapabilities, + remoteCapabilities); + + // Start the RTCRtpSender. The RTCRtpReceiver for this + // transceiver has already been started in setRemoteDescription. + self._transceive(transceiver, + params.codecs.length > 0, + false); + } + }); + } + + this.localDescription = { + type: description.type, + sdp: description.sdp + }; + switch (description.type) { + case 'offer': + this._updateSignalingState('have-local-offer'); + break; + case 'answer': + this._updateSignalingState('stable'); + break; + default: + throw new TypeError('unsupported type "' + description.type + + '"'); + } + + // If a success callback was provided, emit ICE candidates after it + // has been executed. Otherwise, emit callback after the Promise is + // resolved. + var hasCallback = arguments.length > 1 && + typeof arguments[1] === 'function'; + if (hasCallback) { + var cb = arguments[1]; + window.setTimeout(function() { + cb(); + if (self.iceGatheringState === 'new') { + self.iceGatheringState = 'gathering'; + } + self._emitBufferedCandidates(); + }, 0); + } + var p = Promise.resolve(); + p.then(function() { + if (!hasCallback) { + if (self.iceGatheringState === 'new') { + self.iceGatheringState = 'gathering'; + } + // Usually candidates will be emitted earlier. + window.setTimeout(self._emitBufferedCandidates.bind(self), 500); + } + }); + return p; + }; + + window.RTCPeerConnection.prototype.setRemoteDescription = + function(description) { + var self = this; + var stream = new MediaStream(); + var receiverList = []; + var sections = SDPUtils.splitSections(description.sdp); + var sessionpart = sections.shift(); + var isIceLite = SDPUtils.matchPrefix(sessionpart, + 'a=ice-lite').length > 0; + this.usingBundle = SDPUtils.matchPrefix(sessionpart, + 'a=group:BUNDLE ').length > 0; + sections.forEach(function(mediaSection, sdpMLineIndex) { + var lines = SDPUtils.splitLines(mediaSection); + var mline = lines[0].substr(2).split(' '); + var kind = mline[0]; + var rejected = mline[1] === '0'; + var direction = SDPUtils.getDirection(mediaSection, sessionpart); + + var transceiver; + var iceGatherer; + var iceTransport; + var dtlsTransport; + var rtpSender; + var rtpReceiver; + var sendEncodingParameters; + var recvEncodingParameters; + var localCapabilities; + + var track; + // FIXME: ensure the mediaSection has rtcp-mux set. + var remoteCapabilities = SDPUtils.parseRtpParameters(mediaSection); + var remoteIceParameters; + var remoteDtlsParameters; + if (!rejected) { + remoteIceParameters = SDPUtils.getIceParameters(mediaSection, + sessionpart); + remoteDtlsParameters = SDPUtils.getDtlsParameters(mediaSection, + sessionpart); + remoteDtlsParameters.role = 'client'; + } + recvEncodingParameters = + SDPUtils.parseRtpEncodingParameters(mediaSection); + + var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:'); + if (mid.length) { + mid = mid[0].substr(6); + } else { + mid = SDPUtils.generateIdentifier(); + } + + var cname; + // Gets the first SSRC. Note that with RTX there might be multiple + // SSRCs. + var remoteSsrc = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:') + .map(function(line) { + return SDPUtils.parseSsrcMedia(line); + }) + .filter(function(obj) { + return obj.attribute === 'cname'; + })[0]; + if (remoteSsrc) { + cname = remoteSsrc.value; + } + + var isComplete = SDPUtils.matchPrefix(mediaSection, + 'a=end-of-candidates', sessionpart).length > 0; + var cands = SDPUtils.matchPrefix(mediaSection, 'a=candidate:') + .map(function(cand) { + return SDPUtils.parseCandidate(cand); + }) + .filter(function(cand) { + return cand.component === '1'; + }); + if (description.type === 'offer' && !rejected) { + var transports = self.usingBundle && sdpMLineIndex > 0 ? { + iceGatherer: self.transceivers[0].iceGatherer, + iceTransport: self.transceivers[0].iceTransport, + dtlsTransport: self.transceivers[0].dtlsTransport + } : self._createIceAndDtlsTransports(mid, sdpMLineIndex); + + if (isComplete) { + transports.iceTransport.setRemoteCandidates(cands); + } + + localCapabilities = RTCRtpReceiver.getCapabilities(kind); + sendEncodingParameters = [{ + ssrc: (2 * sdpMLineIndex + 2) * 1001 + }]; + + rtpReceiver = new RTCRtpReceiver(transports.dtlsTransport, kind); + + track = rtpReceiver.track; + receiverList.push([track, rtpReceiver]); + // FIXME: not correct when there are multiple streams but that is + // not currently supported in this shim. + stream.addTrack(track); + + // FIXME: look at direction. + if (self.localStreams.length > 0 && + self.localStreams[0].getTracks().length >= sdpMLineIndex) { + var localTrack; + if (kind === 'audio') { + localTrack = self.localStreams[0].getAudioTracks()[0]; + } else if (kind === 'video') { + localTrack = self.localStreams[0].getVideoTracks()[0]; + } + if (localTrack) { + rtpSender = new RTCRtpSender(localTrack, + transports.dtlsTransport); + } + } + + self.transceivers[sdpMLineIndex] = { + iceGatherer: transports.iceGatherer, + iceTransport: transports.iceTransport, + dtlsTransport: transports.dtlsTransport, + localCapabilities: localCapabilities, + remoteCapabilities: remoteCapabilities, + rtpSender: rtpSender, + rtpReceiver: rtpReceiver, + kind: kind, + mid: mid, + cname: cname, + sendEncodingParameters: sendEncodingParameters, + recvEncodingParameters: recvEncodingParameters + }; + // Start the RTCRtpReceiver now. The RTPSender is started in + // setLocalDescription. + self._transceive(self.transceivers[sdpMLineIndex], + false, + direction === 'sendrecv' || direction === 'sendonly'); + } else if (description.type === 'answer' && !rejected) { + transceiver = self.transceivers[sdpMLineIndex]; + iceGatherer = transceiver.iceGatherer; + iceTransport = transceiver.iceTransport; + dtlsTransport = transceiver.dtlsTransport; + rtpSender = transceiver.rtpSender; + rtpReceiver = transceiver.rtpReceiver; + sendEncodingParameters = transceiver.sendEncodingParameters; + localCapabilities = transceiver.localCapabilities; + + self.transceivers[sdpMLineIndex].recvEncodingParameters = + recvEncodingParameters; + self.transceivers[sdpMLineIndex].remoteCapabilities = + remoteCapabilities; + self.transceivers[sdpMLineIndex].cname = cname; + + if ((isIceLite || isComplete) && cands.length) { + iceTransport.setRemoteCandidates(cands); + } + if (!self.usingBundle || sdpMLineIndex === 0) { + iceTransport.start(iceGatherer, remoteIceParameters, + 'controlling'); + dtlsTransport.start(remoteDtlsParameters); + } + + self._transceive(transceiver, + direction === 'sendrecv' || direction === 'recvonly', + direction === 'sendrecv' || direction === 'sendonly'); + + if (rtpReceiver && + (direction === 'sendrecv' || direction === 'sendonly')) { + track = rtpReceiver.track; + receiverList.push([track, rtpReceiver]); + stream.addTrack(track); + } else { + // FIXME: actually the receiver should be created later. + delete transceiver.rtpReceiver; + } + } + }); + + this.remoteDescription = { + type: description.type, + sdp: description.sdp + }; + switch (description.type) { + case 'offer': + this._updateSignalingState('have-remote-offer'); + break; + case 'answer': + this._updateSignalingState('stable'); + break; + default: + throw new TypeError('unsupported type "' + description.type + + '"'); + } + if (stream.getTracks().length) { + self.remoteStreams.push(stream); + window.setTimeout(function() { + var event = new Event('addstream'); + event.stream = stream; + self.dispatchEvent(event); + if (self.onaddstream !== null) { + window.setTimeout(function() { + self.onaddstream(event); + }, 0); + } + + receiverList.forEach(function(item) { + var track = item[0]; + var receiver = item[1]; + var trackEvent = new Event('track'); + trackEvent.track = track; + trackEvent.receiver = receiver; + trackEvent.streams = [stream]; + self.dispatchEvent(event); + if (self.ontrack !== null) { + window.setTimeout(function() { + self.ontrack(trackEvent); + }, 0); + } + }); + }, 0); + } + if (arguments.length > 1 && typeof arguments[1] === 'function') { + window.setTimeout(arguments[1], 0); + } + return Promise.resolve(); + }; + + window.RTCPeerConnection.prototype.close = function() { + this.transceivers.forEach(function(transceiver) { + /* not yet + if (transceiver.iceGatherer) { + transceiver.iceGatherer.close(); + } + */ + if (transceiver.iceTransport) { + transceiver.iceTransport.stop(); + } + if (transceiver.dtlsTransport) { + transceiver.dtlsTransport.stop(); + } + if (transceiver.rtpSender) { + transceiver.rtpSender.stop(); + } + if (transceiver.rtpReceiver) { + transceiver.rtpReceiver.stop(); + } + }); + // FIXME: clean up tracks, local streams, remote streams, etc + this._updateSignalingState('closed'); + }; + + // Update the signaling state. + window.RTCPeerConnection.prototype._updateSignalingState = + function(newState) { + this.signalingState = newState; + var event = new Event('signalingstatechange'); + this.dispatchEvent(event); + if (this.onsignalingstatechange !== null) { + this.onsignalingstatechange(event); + } + }; + + // Determine whether to fire the negotiationneeded event. + window.RTCPeerConnection.prototype._maybeFireNegotiationNeeded = + function() { + // Fire away (for now). + var event = new Event('negotiationneeded'); + this.dispatchEvent(event); + if (this.onnegotiationneeded !== null) { + this.onnegotiationneeded(event); + } + }; + + // Update the connection state. + window.RTCPeerConnection.prototype._updateConnectionState = function() { + var self = this; + var newState; + var states = { + 'new': 0, + closed: 0, + connecting: 0, + checking: 0, + connected: 0, + completed: 0, + failed: 0 + }; + this.transceivers.forEach(function(transceiver) { + states[transceiver.iceTransport.state]++; + states[transceiver.dtlsTransport.state]++; + }); + // ICETransport.completed and connected are the same for this purpose. + states.connected += states.completed; + + newState = 'new'; + if (states.failed > 0) { + newState = 'failed'; + } else if (states.connecting > 0 || states.checking > 0) { + newState = 'connecting'; + } else if (states.disconnected > 0) { + newState = 'disconnected'; + } else if (states.new > 0) { + newState = 'new'; + } else if (states.connected > 0 || states.completed > 0) { + newState = 'connected'; + } + + if (newState !== self.iceConnectionState) { + self.iceConnectionState = newState; + var event = new Event('iceconnectionstatechange'); + this.dispatchEvent(event); + if (this.oniceconnectionstatechange !== null) { + this.oniceconnectionstatechange(event); + } + } + }; + + window.RTCPeerConnection.prototype.createOffer = function() { + var self = this; + if (this._pendingOffer) { + throw new Error('createOffer called while there is a pending offer.'); + } + var offerOptions; + if (arguments.length === 1 && typeof arguments[0] !== 'function') { + offerOptions = arguments[0]; + } else if (arguments.length === 3) { + offerOptions = arguments[2]; + } + + var tracks = []; + var numAudioTracks = 0; + var numVideoTracks = 0; + // Default to sendrecv. + if (this.localStreams.length) { + numAudioTracks = this.localStreams[0].getAudioTracks().length; + numVideoTracks = this.localStreams[0].getVideoTracks().length; + } + // Determine number of audio and video tracks we need to send/recv. + if (offerOptions) { + // Reject Chrome legacy constraints. + if (offerOptions.mandatory || offerOptions.optional) { + throw new TypeError( + 'Legacy mandatory/optional constraints not supported.'); + } + if (offerOptions.offerToReceiveAudio !== undefined) { + numAudioTracks = offerOptions.offerToReceiveAudio; + } + if (offerOptions.offerToReceiveVideo !== undefined) { + numVideoTracks = offerOptions.offerToReceiveVideo; + } + } + if (this.localStreams.length) { + // Push local streams. + this.localStreams[0].getTracks().forEach(function(track) { + tracks.push({ + kind: track.kind, + track: track, + wantReceive: track.kind === 'audio' ? + numAudioTracks > 0 : numVideoTracks > 0 + }); + if (track.kind === 'audio') { + numAudioTracks--; + } else if (track.kind === 'video') { + numVideoTracks--; + } + }); + } + // Create M-lines for recvonly streams. + while (numAudioTracks > 0 || numVideoTracks > 0) { + if (numAudioTracks > 0) { + tracks.push({ + kind: 'audio', + wantReceive: true + }); + numAudioTracks--; + } + if (numVideoTracks > 0) { + tracks.push({ + kind: 'video', + wantReceive: true + }); + numVideoTracks--; + } + } + + var sdp = SDPUtils.writeSessionBoilerplate(); + var transceivers = []; + tracks.forEach(function(mline, sdpMLineIndex) { + // For each track, create an ice gatherer, ice transport, + // dtls transport, potentially rtpsender and rtpreceiver. + var track = mline.track; + var kind = mline.kind; + var mid = SDPUtils.generateIdentifier(); + + var transports = self.usingBundle && sdpMLineIndex > 0 ? { + iceGatherer: transceivers[0].iceGatherer, + iceTransport: transceivers[0].iceTransport, + dtlsTransport: transceivers[0].dtlsTransport + } : self._createIceAndDtlsTransports(mid, sdpMLineIndex); + + var localCapabilities = RTCRtpSender.getCapabilities(kind); + var rtpSender; + var rtpReceiver; + + // generate an ssrc now, to be used later in rtpSender.send + var sendEncodingParameters = [{ + ssrc: (2 * sdpMLineIndex + 1) * 1001 + }]; + if (track) { + rtpSender = new RTCRtpSender(track, transports.dtlsTransport); + } + + if (mline.wantReceive) { + rtpReceiver = new RTCRtpReceiver(transports.dtlsTransport, kind); + } + + transceivers[sdpMLineIndex] = { + iceGatherer: transports.iceGatherer, + iceTransport: transports.iceTransport, + dtlsTransport: transports.dtlsTransport, + localCapabilities: localCapabilities, + remoteCapabilities: null, + rtpSender: rtpSender, + rtpReceiver: rtpReceiver, + kind: kind, + mid: mid, + sendEncodingParameters: sendEncodingParameters, + recvEncodingParameters: null + }; + }); + if (this.usingBundle) { + sdp += 'a=group:BUNDLE ' + transceivers.map(function(t) { + return t.mid; + }).join(' ') + '\r\n'; + } + tracks.forEach(function(mline, sdpMLineIndex) { + var transceiver = transceivers[sdpMLineIndex]; + sdp += SDPUtils.writeMediaSection(transceiver, + transceiver.localCapabilities, 'offer', self.localStreams[0]); + }); + + this._pendingOffer = transceivers; + var desc = new RTCSessionDescription({ + type: 'offer', + sdp: sdp + }); + if (arguments.length && typeof arguments[0] === 'function') { + window.setTimeout(arguments[0], 0, desc); + } + return Promise.resolve(desc); + }; + + window.RTCPeerConnection.prototype.createAnswer = function() { + var self = this; + + var sdp = SDPUtils.writeSessionBoilerplate(); + if (this.usingBundle) { + sdp += 'a=group:BUNDLE ' + this.transceivers.map(function(t) { + return t.mid; + }).join(' ') + '\r\n'; + } + this.transceivers.forEach(function(transceiver) { + // Calculate intersection of capabilities. + var commonCapabilities = self._getCommonCapabilities( + transceiver.localCapabilities, + transceiver.remoteCapabilities); + + sdp += SDPUtils.writeMediaSection(transceiver, commonCapabilities, + 'answer', self.localStreams[0]); + }); + + var desc = new RTCSessionDescription({ + type: 'answer', + sdp: sdp + }); + if (arguments.length && typeof arguments[0] === 'function') { + window.setTimeout(arguments[0], 0, desc); + } + return Promise.resolve(desc); + }; + + window.RTCPeerConnection.prototype.addIceCandidate = function(candidate) { + if (candidate === null) { + this.transceivers.forEach(function(transceiver) { + transceiver.iceTransport.addRemoteCandidate({}); + }); + } else { + var mLineIndex = candidate.sdpMLineIndex; + if (candidate.sdpMid) { + for (var i = 0; i < this.transceivers.length; i++) { + if (this.transceivers[i].mid === candidate.sdpMid) { + mLineIndex = i; + break; + } + } + } + var transceiver = this.transceivers[mLineIndex]; + if (transceiver) { + var cand = Object.keys(candidate.candidate).length > 0 ? + SDPUtils.parseCandidate(candidate.candidate) : {}; + // Ignore Chrome's invalid candidates since Edge does not like them. + if (cand.protocol === 'tcp' && (cand.port === 0 || cand.port === 9)) { + return; + } + // Ignore RTCP candidates, we assume RTCP-MUX. + if (cand.component !== '1') { + return; + } + // A dirty hack to make samples work. + if (cand.type === 'endOfCandidates') { + cand = {}; + } + transceiver.iceTransport.addRemoteCandidate(cand); + + // update the remoteDescription. + var sections = SDPUtils.splitSections(this.remoteDescription.sdp); + sections[mLineIndex + 1] += (cand.type ? candidate.candidate.trim() + : 'a=end-of-candidates') + '\r\n'; + this.remoteDescription.sdp = sections.join(''); + } + } + if (arguments.length > 1 && typeof arguments[1] === 'function') { + window.setTimeout(arguments[1], 0); + } + return Promise.resolve(); + }; + + window.RTCPeerConnection.prototype.getStats = function() { + var promises = []; + this.transceivers.forEach(function(transceiver) { + ['rtpSender', 'rtpReceiver', 'iceGatherer', 'iceTransport', + 'dtlsTransport'].forEach(function(method) { + if (transceiver[method]) { + promises.push(transceiver[method].getStats()); + } + }); + }); + var cb = arguments.length > 1 && typeof arguments[1] === 'function' && + arguments[1]; + return new Promise(function(resolve) { + // shim getStats with maplike support + var results = new Map(); + Promise.all(promises).then(function(res) { + res.forEach(function(result) { + Object.keys(result).forEach(function(id) { + results.set(id, result[id]); + results[id] = result[id]; + }); + }); + if (cb) { + window.setTimeout(cb, 0, results); + } + resolve(results); + }); + }); + }; + } + }; + + // Expose public methods. + module.exports = { + shimPeerConnection: edgeShim.shimPeerConnection, + shimGetUserMedia: require('./getusermedia') + }; + + },{"./getusermedia":6,"sdp":1}],6:[function(require,module,exports){ + /* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ + 'use strict'; + + // Expose public methods. + module.exports = function() { + var shimError_ = function(e) { + return { + name: {PermissionDeniedError: 'NotAllowedError'}[e.name] || e.name, + message: e.message, + constraint: e.constraint, + toString: function() { + return this.name; + } + }; + }; + + // getUserMedia error shim. + var origGetUserMedia = navigator.mediaDevices.getUserMedia. + bind(navigator.mediaDevices); + navigator.mediaDevices.getUserMedia = function(c) { + return origGetUserMedia(c).catch(function(e) { + return Promise.reject(shimError_(e)); + }); + }; + }; + + },{}],7:[function(require,module,exports){ + /* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ + 'use strict'; + + var browserDetails = require('../utils').browserDetails; + + var firefoxShim = { + shimOnTrack: function() { + if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in + window.RTCPeerConnection.prototype)) { + Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', { + get: function() { + return this._ontrack; + }, + set: function(f) { + if (this._ontrack) { + this.removeEventListener('track', this._ontrack); + this.removeEventListener('addstream', this._ontrackpoly); + } + this.addEventListener('track', this._ontrack = f); + this.addEventListener('addstream', this._ontrackpoly = function(e) { + e.stream.getTracks().forEach(function(track) { + var event = new Event('track'); + event.track = track; + event.receiver = {track: track}; + event.streams = [e.stream]; + this.dispatchEvent(event); + }.bind(this)); + }.bind(this)); + } + }); + } + }, + + shimSourceObject: function() { + // Firefox has supported mozSrcObject since FF22, unprefixed in 42. + if (typeof window === 'object') { + if (window.HTMLMediaElement && + !('srcObject' in window.HTMLMediaElement.prototype)) { + // Shim the srcObject property, once, when HTMLMediaElement is found. + Object.defineProperty(window.HTMLMediaElement.prototype, 'srcObject', { + get: function() { + return this.mozSrcObject; + }, + set: function(stream) { + this.mozSrcObject = stream; + } + }); + } + } + }, + + shimPeerConnection: function() { + if (typeof window !== 'object' || !(window.RTCPeerConnection || + window.mozRTCPeerConnection)) { + return; // probably media.peerconnection.enabled=false in about:config + } + // The RTCPeerConnection object. + if (!window.RTCPeerConnection) { + window.RTCPeerConnection = function(pcConfig, pcConstraints) { + if (browserDetails.version < 38) { + // .urls is not supported in FF < 38. + // create RTCIceServers with a single url. + if (pcConfig && pcConfig.iceServers) { + var newIceServers = []; + for (var i = 0; i < pcConfig.iceServers.length; i++) { + var server = pcConfig.iceServers[i]; + if (server.hasOwnProperty('urls')) { + for (var j = 0; j < server.urls.length; j++) { + var newServer = { + url: server.urls[j] + }; + if (server.urls[j].indexOf('turn') === 0) { + newServer.username = server.username; + newServer.credential = server.credential; + } + newIceServers.push(newServer); + } + } else { + newIceServers.push(pcConfig.iceServers[i]); + } + } + pcConfig.iceServers = newIceServers; + } + } + return new mozRTCPeerConnection(pcConfig, pcConstraints); + }; + window.RTCPeerConnection.prototype = mozRTCPeerConnection.prototype; + + // wrap static methods. Currently just generateCertificate. + if (mozRTCPeerConnection.generateCertificate) { + Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', { + get: function() { + return mozRTCPeerConnection.generateCertificate; + } + }); + } + + window.RTCSessionDescription = mozRTCSessionDescription; + window.RTCIceCandidate = mozRTCIceCandidate; + } + + // shim away need for obsolete RTCIceCandidate/RTCSessionDescription. + ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'] + .forEach(function(method) { + var nativeMethod = RTCPeerConnection.prototype[method]; + RTCPeerConnection.prototype[method] = function() { + arguments[0] = new ((method === 'addIceCandidate') ? + RTCIceCandidate : RTCSessionDescription)(arguments[0]); + return nativeMethod.apply(this, arguments); + }; + }); + + // support for addIceCandidate(null) + var nativeAddIceCandidate = + RTCPeerConnection.prototype.addIceCandidate; + RTCPeerConnection.prototype.addIceCandidate = function() { + return arguments[0] === null ? Promise.resolve() + : nativeAddIceCandidate.apply(this, arguments); + }; + + // shim getStats with maplike support + var makeMapStats = function(stats) { + var map = new Map(); + Object.keys(stats).forEach(function(key) { + map.set(key, stats[key]); + map[key] = stats[key]; + }); + return map; + }; + + var nativeGetStats = RTCPeerConnection.prototype.getStats; + RTCPeerConnection.prototype.getStats = function(selector, onSucc, onErr) { + return nativeGetStats.apply(this, [selector || null]) + .then(function(stats) { + return makeMapStats(stats); + }) + .then(onSucc, onErr); + }; + } + }; + + // Expose public methods. + module.exports = { + shimOnTrack: firefoxShim.shimOnTrack, + shimSourceObject: firefoxShim.shimSourceObject, + shimPeerConnection: firefoxShim.shimPeerConnection, + shimGetUserMedia: require('./getusermedia') + }; + + },{"../utils":10,"./getusermedia":8}],8:[function(require,module,exports){ + /* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ + 'use strict'; + + var logging = require('../utils').log; + var browserDetails = require('../utils').browserDetails; + + // Expose public methods. + module.exports = function() { + var shimError_ = function(e) { + return { + name: { + SecurityError: 'NotAllowedError', + PermissionDeniedError: 'NotAllowedError' + }[e.name] || e.name, + message: { + 'The operation is insecure.': 'The request is not allowed by the ' + + 'user agent or the platform in the current context.' + }[e.message] || e.message, + constraint: e.constraint, + toString: function() { + return this.name + (this.message && ': ') + this.message; + } + }; + }; + + // getUserMedia constraints shim. + var getUserMedia_ = function(constraints, onSuccess, onError) { + var constraintsToFF37_ = function(c) { + if (typeof c !== 'object' || c.require) { + return c; + } + var require = []; + Object.keys(c).forEach(function(key) { + if (key === 'require' || key === 'advanced' || key === 'mediaSource') { + return; + } + var r = c[key] = (typeof c[key] === 'object') ? + c[key] : {ideal: c[key]}; + if (r.min !== undefined || + r.max !== undefined || r.exact !== undefined) { + require.push(key); + } + if (r.exact !== undefined) { + if (typeof r.exact === 'number') { + r. min = r.max = r.exact; + } else { + c[key] = r.exact; + } + delete r.exact; + } + if (r.ideal !== undefined) { + c.advanced = c.advanced || []; + var oc = {}; + if (typeof r.ideal === 'number') { + oc[key] = {min: r.ideal, max: r.ideal}; + } else { + oc[key] = r.ideal; + } + c.advanced.push(oc); + delete r.ideal; + if (!Object.keys(r).length) { + delete c[key]; + } + } + }); + if (require.length) { + c.require = require; + } + return c; + }; + constraints = JSON.parse(JSON.stringify(constraints)); + if (browserDetails.version < 38) { + logging('spec: ' + JSON.stringify(constraints)); + if (constraints.audio) { + constraints.audio = constraintsToFF37_(constraints.audio); + } + if (constraints.video) { + constraints.video = constraintsToFF37_(constraints.video); + } + logging('ff37: ' + JSON.stringify(constraints)); + } + return navigator.mozGetUserMedia(constraints, onSuccess, function(e) { + onError(shimError_(e)); + }); + }; + + // Returns the result of getUserMedia as a Promise. + var getUserMediaPromise_ = function(constraints) { + return new Promise(function(resolve, reject) { + getUserMedia_(constraints, resolve, reject); + }); + }; + + // Shim for mediaDevices on older versions. + if (!navigator.mediaDevices) { + navigator.mediaDevices = {getUserMedia: getUserMediaPromise_, + addEventListener: function() { }, + removeEventListener: function() { } + }; + } + navigator.mediaDevices.enumerateDevices = + navigator.mediaDevices.enumerateDevices || function() { + return new Promise(function(resolve) { + var infos = [ + {kind: 'audioinput', deviceId: 'default', label: '', groupId: ''}, + {kind: 'videoinput', deviceId: 'default', label: '', groupId: ''} + ]; + resolve(infos); + }); + }; + + if (browserDetails.version < 41) { + // Work around http://bugzil.la/1169665 + var orgEnumerateDevices = + navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices); + navigator.mediaDevices.enumerateDevices = function() { + return orgEnumerateDevices().then(undefined, function(e) { + if (e.name === 'NotFoundError') { + return []; + } + throw e; + }); + }; + } + if (browserDetails.version < 49) { + var origGetUserMedia = navigator.mediaDevices.getUserMedia. + bind(navigator.mediaDevices); + navigator.mediaDevices.getUserMedia = function(c) { + return origGetUserMedia(c).catch(function(e) { + return Promise.reject(shimError_(e)); + }); + }; + } + navigator.getUserMedia = function(constraints, onSuccess, onError) { + if (browserDetails.version < 44) { + return getUserMedia_(constraints, onSuccess, onError); + } + // Replace Firefox 44+'s deprecation warning with unprefixed version. + console.warn('navigator.getUserMedia has been replaced by ' + + 'navigator.mediaDevices.getUserMedia'); + navigator.mediaDevices.getUserMedia(constraints).then(onSuccess, onError); + }; + }; + + },{"../utils":10}],9:[function(require,module,exports){ + /* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + 'use strict'; + var safariShim = { + // TODO: DrAlex, should be here, double check against LayoutTests + // shimOnTrack: function() { }, + + // TODO: once the back-end for the mac port is done, add. + // TODO: check for webkitGTK+ + // shimPeerConnection: function() { }, + + shimGetUserMedia: function() { + navigator.getUserMedia = navigator.webkitGetUserMedia; + } + }; + + // Expose public methods. + module.exports = { + shimGetUserMedia: safariShim.shimGetUserMedia + // TODO + // shimOnTrack: safariShim.shimOnTrack, + // shimPeerConnection: safariShim.shimPeerConnection + }; + + },{}],10:[function(require,module,exports){ + /* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ + 'use strict'; + + var logDisabled_ = true; + + // Utility methods. + var utils = { + disableLog: function(bool) { + if (typeof bool !== 'boolean') { + return new Error('Argument type: ' + typeof bool + + '. Please use a boolean.'); + } + logDisabled_ = bool; + return (bool) ? 'adapter.js logging disabled' : + 'adapter.js logging enabled'; + }, + + log: function() { + if (typeof window === 'object') { + if (logDisabled_) { + return; + } + if (typeof console !== 'undefined' && typeof console.log === 'function') { + console.log.apply(console, arguments); + } + } + }, + + /** + * Extract browser version out of the provided user agent string. + * + * @param {!string} uastring userAgent string. + * @param {!string} expr Regular expression used as match criteria. + * @param {!number} pos position in the version string to be returned. + * @return {!number} browser version. + */ + extractVersion: function(uastring, expr, pos) { + var match = uastring.match(expr); + return match && match.length >= pos && parseInt(match[pos], 10); + }, + + /** + * Browser detector. + * + * @return {object} result containing browser and version + * properties. + */ + detectBrowser: function() { + // Returned result object. + var result = {}; + result.browser = null; + result.version = null; + + // Fail early if it's not a browser + if (typeof window === 'undefined' || !window.navigator) { + result.browser = 'Not a browser.'; + return result; + } + + // Firefox. + if (navigator.mozGetUserMedia) { + result.browser = 'firefox'; + result.version = this.extractVersion(navigator.userAgent, + /Firefox\/([0-9]+)\./, 1); + + // all webkit-based browsers + } else if (navigator.webkitGetUserMedia) { + // Chrome, Chromium, Webview, Opera, all use the chrome shim for now + if (window.webkitRTCPeerConnection) { + result.browser = 'chrome'; + result.version = this.extractVersion(navigator.userAgent, + /Chrom(e|ium)\/([0-9]+)\./, 2); + + // Safari or unknown webkit-based + // for the time being Safari has support for MediaStreams but not webRTC + } else { + // Safari UA substrings of interest for reference: + // - webkit version: AppleWebKit/602.1.25 (also used in Op,Cr) + // - safari UI version: Version/9.0.3 (unique to Safari) + // - safari UI webkit version: Safari/601.4.4 (also used in Op,Cr) + // + // if the webkit version and safari UI webkit versions are equals, + // ... this is a stable version. + // + // only the internal webkit version is important today to know if + // media streams are supported + // + if (navigator.userAgent.match(/Version\/(\d+).(\d+)/)) { + result.browser = 'safari'; + result.version = this.extractVersion(navigator.userAgent, + /AppleWebKit\/([0-9]+)\./, 1); + + // unknown webkit-based browser + } else { + result.browser = 'Unsupported webkit-based browser ' + + 'with GUM support but no WebRTC support.'; + return result; + } + } + + // Edge. + } else if (navigator.mediaDevices && + navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) { + result.browser = 'edge'; + result.version = this.extractVersion(navigator.userAgent, + /Edge\/(\d+).(\d+)$/, 2); + + // Default fallthrough: not supported. + } else { + result.browser = 'Not a supported browser.'; + return result; + } + + return result; + } + }; + + // Export. + module.exports = { + log: utils.log, + disableLog: utils.disableLog, + browserDetails: utils.detectBrowser(), + extractVersion: utils.extractVersion + }; + + },{}]},{},[2])(2) +}); +/* jshint ignore:end */ + + // END OF INJECTION OF GOOGLE'S ADAPTER.JS CONTENT + /////////////////////////////////////////////////////////////////// + + AdapterJS.parseWebrtcDetectedBrowser(); + + /////////////////////////////////////////////////////////////////// + // EXTENSION FOR CHROME, FIREFOX AND EDGE + // Includes legacy functions + // -- createIceServer + // -- createIceServers + // -- MediaStreamTrack.getSources + // + // and additional shims + // -- attachMediaStream + // -- reattachMediaStream + // -- requestUserMedia + // -- a call to AdapterJS.maybeThroughWebRTCReady (notifies WebRTC is ready) + + // Add support for legacy functions createIceServer and createIceServers + if ( navigator.mozGetUserMedia ) { + // Shim for MediaStreamTrack.getSources. + MediaStreamTrack.getSources = function(successCb) { + setTimeout(function() { + var infos = [ + { kind: 'audio', id: 'default', label:'', facing:'' }, + { kind: 'video', id: 'default', label:'', facing:'' } + ]; + successCb(infos); + }, 0); + }; + + createIceServer = function (url, username, password) { + console.warn('createIceServer is deprecated. It should be replaced with an application level implementation.'); + // Note: Google's import of AJS will auto-reverse to 'url': '...' for FF < 38 + + var iceServer = null; + var urlParts = url.split(':'); + if (urlParts[0].indexOf('stun') === 0) { + iceServer = { urls : [url] }; + } else if (urlParts[0].indexOf('turn') === 0) { + if (webrtcDetectedVersion < 27) { + var turnUrlParts = url.split('?'); + if (turnUrlParts.length === 1 || + turnUrlParts[1].indexOf('transport=udp') === 0) { + iceServer = { + urls : [turnUrlParts[0]], + credential : password, + username : username + }; + } + } else { + iceServer = { + urls : [url], + credential : password, + username : username + }; + } + } + return iceServer; + }; + + createIceServers = function (urls, username, password) { + console.warn('createIceServers is deprecated. It should be replaced with an application level implementation.'); + + var iceServers = []; + for (i = 0; i < urls.length; i++) { + var iceServer = createIceServer(urls[i], username, password); + if (iceServer !== null) { + iceServers.push(iceServer); + } + } + return iceServers; + }; + } else if ( navigator.webkitGetUserMedia ) { + createIceServer = function (url, username, password) { + console.warn('createIceServer is deprecated. It should be replaced with an application level implementation.'); + + var iceServer = null; + var urlParts = url.split(':'); + if (urlParts[0].indexOf('stun') === 0) { + iceServer = { 'url' : url }; + } else if (urlParts[0].indexOf('turn') === 0) { + iceServer = { + 'url' : url, + 'credential' : password, + 'username' : username + }; + } + return iceServer; + }; + + createIceServers = function (urls, username, password) { + console.warn('createIceServers is deprecated. It should be replaced with an application level implementation.'); + + var iceServers = []; + if (webrtcDetectedVersion >= 34) { + iceServers = { + 'urls' : urls, + 'credential' : password, + 'username' : username + }; + } else { + for (i = 0; i < urls.length; i++) { + var iceServer = createIceServer(urls[i], username, password); + if (iceServer !== null) { + iceServers.push(iceServer); + } + } + } + return iceServers; + }; + } + + // adapter.js by Google currently doesn't suppport + // attachMediaStream and reattachMediaStream for Egde + if (navigator.mediaDevices && navigator.userAgent.match( + /Edge\/(\d+).(\d+)$/)) { + getUserMedia = window.getUserMedia = navigator.getUserMedia.bind(navigator); + attachMediaStream = function(element, stream) { + element.srcObject = stream; + return element; + }; + reattachMediaStream = function(to, from) { + to.srcObject = from.srcObject; + return to; + }; + } + + // Need to override attachMediaStream and reattachMediaStream + // to support the plugin's logic + if (attachMediaStream) { + attachMediaStream_base = attachMediaStream; + attachMediaStream = function (element, stream) { + if ((webrtcDetectedBrowser === 'chrome' || + webrtcDetectedBrowser === 'opera') && + !stream) { + // Chrome does not support "src = null" + element.src = ''; + } else { + attachMediaStream_base(element, stream); + } + return element; + }; + } + if (reattachMediaStream) { + reattachMediaStream_base = reattachMediaStream; + reattachMediaStream = function (to, from) { + reattachMediaStream_base(to, from); + return to; + }; + } + + // Propagate attachMediaStream and gUM in window and AdapterJS + window.attachMediaStream = attachMediaStream; + window.reattachMediaStream = reattachMediaStream; + window.getUserMedia = getUserMedia; + AdapterJS.attachMediaStream = attachMediaStream; + AdapterJS.reattachMediaStream = reattachMediaStream; + AdapterJS.getUserMedia = getUserMedia; + + // Removed Google defined promises when promise is not defined + if (typeof Promise === 'undefined') { + requestUserMedia = null; + } + + AdapterJS.maybeThroughWebRTCReady(); + + // END OF EXTENSION OF CHROME, FIREFOX AND EDGE + /////////////////////////////////////////////////////////////////// + +} else { // TRY TO USE PLUGIN + + /////////////////////////////////////////////////////////////////// + // WEBRTC PLUGIN SHIM + // Will automatically check if the plugin is available and inject it + // into the DOM if it is. + // When the plugin is not available, will prompt a banner to suggest installing it + // Use AdapterJS.options.hidePluginInstallPrompt to prevent this banner from popping + // + // Shims the follwing: + // -- getUserMedia + // -- MediaStreamTrack + // -- MediaStreamTrack.getSources + // -- RTCPeerConnection + // -- RTCSessionDescription + // -- RTCIceCandidate + // -- createIceServer + // -- createIceServers + // -- attachMediaStream + // -- reattachMediaStream + // -- webrtcDetectedBrowser + // -- webrtcDetectedVersion + + // IE 9 is not offering an implementation of console.log until you open a console + if (typeof console !== 'object' || typeof console.log !== 'function') { + /* jshint -W020 */ + console = {} || console; + // Implemented based on console specs from MDN + // You may override these functions + console.log = function (arg) {}; + console.info = function (arg) {}; + console.error = function (arg) {}; + console.dir = function (arg) {}; + console.exception = function (arg) {}; + console.trace = function (arg) {}; + console.warn = function (arg) {}; + console.count = function (arg) {}; + console.debug = function (arg) {}; + console.count = function (arg) {}; + console.time = function (arg) {}; + console.timeEnd = function (arg) {}; + console.group = function (arg) {}; + console.groupCollapsed = function (arg) {}; + console.groupEnd = function (arg) {}; + /* jshint +W020 */ + } + AdapterJS.parseWebrtcDetectedBrowser(); + isIE = webrtcDetectedBrowser === 'IE'; + + /* jshint -W035 */ + AdapterJS.WebRTCPlugin.WaitForPluginReady = function() { + while (AdapterJS.WebRTCPlugin.pluginState !== AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY) { + /* empty because it needs to prevent the function from running. */ + } + }; + /* jshint +W035 */ + + AdapterJS.WebRTCPlugin.callWhenPluginReady = function (callback) { + if (AdapterJS.WebRTCPlugin.pluginState === AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY) { + // Call immediately if possible + // Once the plugin is set, the code will always take this path + callback(); + } else { + // otherwise start a 100ms interval + var checkPluginReadyState = setInterval(function () { + if (AdapterJS.WebRTCPlugin.pluginState === AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY) { + clearInterval(checkPluginReadyState); + callback(); + } + }, 100); + } + }; + + AdapterJS.WebRTCPlugin.setLogLevel = function(logLevel) { + AdapterJS.WebRTCPlugin.callWhenPluginReady(function() { + AdapterJS.WebRTCPlugin.plugin.setLogLevel(logLevel); + }); + }; + + AdapterJS.WebRTCPlugin.injectPlugin = function () { + // only inject once the page is ready + if (document.readyState !== 'complete') { + return; + } + + // Prevent multiple injections + if (AdapterJS.WebRTCPlugin.pluginState !== AdapterJS.WebRTCPlugin.PLUGIN_STATES.INITIALIZING) { + return; + } + + AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.INJECTING; + + if (webrtcDetectedBrowser === 'IE' && webrtcDetectedVersion <= 10) { + var frag = document.createDocumentFragment(); + AdapterJS.WebRTCPlugin.plugin = document.createElement('div'); + AdapterJS.WebRTCPlugin.plugin.innerHTML = '' + + ' ' + + ' ' + + ' ' + + '' + + '' + + // uncomment to be able to use virtual cams + (AdapterJS.options.getAllCams ? '':'') + + + ''; + while (AdapterJS.WebRTCPlugin.plugin.firstChild) { + frag.appendChild(AdapterJS.WebRTCPlugin.plugin.firstChild); + } + document.body.appendChild(frag); + + // Need to re-fetch the plugin + AdapterJS.WebRTCPlugin.plugin = + document.getElementById(AdapterJS.WebRTCPlugin.pluginInfo.pluginId); + } else { + // Load Plugin + AdapterJS.WebRTCPlugin.plugin = document.createElement('object'); + AdapterJS.WebRTCPlugin.plugin.id = + AdapterJS.WebRTCPlugin.pluginInfo.pluginId; + // IE will only start the plugin if it's ACTUALLY visible + if (isIE) { + AdapterJS.WebRTCPlugin.plugin.width = '1px'; + AdapterJS.WebRTCPlugin.plugin.height = '1px'; + } else { // The size of the plugin on Safari should be 0x0px + // so that the autorisation prompt is at the top + AdapterJS.WebRTCPlugin.plugin.width = '0px'; + AdapterJS.WebRTCPlugin.plugin.height = '0px'; + } + AdapterJS.WebRTCPlugin.plugin.type = AdapterJS.WebRTCPlugin.pluginInfo.type; + AdapterJS.WebRTCPlugin.plugin.innerHTML = '' + + '' + + ' ' + + (AdapterJS.options.getAllCams ? '':'') + + '' + + ''; + document.body.appendChild(AdapterJS.WebRTCPlugin.plugin); + } + + + AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.INJECTED; + }; + + AdapterJS.WebRTCPlugin.isPluginInstalled = + function (comName, plugName, installedCb, notInstalledCb) { + if (!isIE) { + var pluginArray = navigator.plugins; + for (var i = 0; i < pluginArray.length; i++) { + if (pluginArray[i].name.indexOf(plugName) >= 0) { + installedCb(); + return; + } + } + notInstalledCb(); + } else { + try { + var axo = new ActiveXObject(comName + '.' + plugName); + } catch (e) { + notInstalledCb(); + return; + } + installedCb(); + } + }; + + AdapterJS.WebRTCPlugin.defineWebRTCInterface = function () { + if (AdapterJS.WebRTCPlugin.pluginState === + AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY) { + console.error('AdapterJS - WebRTC interface has already been defined'); + return; + } + + AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.INITIALIZING; + + AdapterJS.isDefined = function (variable) { + return variable !== null && variable !== undefined; + }; + + createIceServer = function (url, username, password) { + var iceServer = null; + var urlParts = url.split(':'); + if (urlParts[0].indexOf('stun') === 0) { + iceServer = { + 'url' : url, + 'hasCredentials' : false + }; + } else if (urlParts[0].indexOf('turn') === 0) { + iceServer = { + 'url' : url, + 'hasCredentials' : true, + 'credential' : password, + 'username' : username + }; + } + return iceServer; + }; + + createIceServers = function (urls, username, password) { + var iceServers = []; + for (var i = 0; i < urls.length; ++i) { + iceServers.push(createIceServer(urls[i], username, password)); + } + return iceServers; + }; + + RTCSessionDescription = function (info) { + AdapterJS.WebRTCPlugin.WaitForPluginReady(); + return AdapterJS.WebRTCPlugin.plugin. + ConstructSessionDescription(info.type, info.sdp); + }; + + RTCPeerConnection = function (servers, constraints) { + // Validate server argumenr + if (!(servers === undefined || + servers === null || + Array.isArray(servers.iceServers))) { + throw new Error('Failed to construct \'RTCPeerConnection\': Malformed RTCConfiguration'); + } + + // Validate constraints argument + if (typeof constraints !== 'undefined' && constraints !== null) { + var invalidConstraits = false; + invalidConstraits |= typeof constraints !== 'object'; + invalidConstraits |= constraints.hasOwnProperty('mandatory') && + constraints.mandatory !== undefined && + constraints.mandatory !== null && + constraints.mandatory.constructor !== Object; + invalidConstraits |= constraints.hasOwnProperty('optional') && + constraints.optional !== undefined && + constraints.optional !== null && + !Array.isArray(constraints.optional); + if (invalidConstraits) { + throw new Error('Failed to construct \'RTCPeerConnection\': Malformed constraints object'); + } + } + + // Call relevant PeerConnection constructor according to plugin version + AdapterJS.WebRTCPlugin.WaitForPluginReady(); + + // RTCPeerConnection prototype from the old spec + var iceServers = null; + if (servers && Array.isArray(servers.iceServers)) { + iceServers = servers.iceServers; + for (var i = 0; i < iceServers.length; i++) { + // Legacy plugin versions compatibility + if (iceServers[i].urls && !iceServers[i].url) { + iceServers[i].url = iceServers[i].urls; + } + iceServers[i].hasCredentials = AdapterJS. + isDefined(iceServers[i].username) && + AdapterJS.isDefined(iceServers[i].credential); + } + } + + if (AdapterJS.WebRTCPlugin.plugin.PEER_CONNECTION_VERSION && + AdapterJS.WebRTCPlugin.plugin.PEER_CONNECTION_VERSION > 1) { + // RTCPeerConnection prototype from the new spec + if (iceServers) { + servers.iceServers = iceServers; + } + return AdapterJS.WebRTCPlugin.plugin.PeerConnection(servers); + } else { + var mandatory = (constraints && constraints.mandatory) ? + constraints.mandatory : null; + var optional = (constraints && constraints.optional) ? + constraints.optional : null; + return AdapterJS.WebRTCPlugin.plugin. + PeerConnection(AdapterJS.WebRTCPlugin.pageId, + iceServers, mandatory, optional); + } + }; + + MediaStreamTrack = function(){}; + MediaStreamTrack.getSources = function (callback) { + AdapterJS.WebRTCPlugin.callWhenPluginReady(function() { + AdapterJS.WebRTCPlugin.plugin.GetSources(callback); + }); + }; + + getUserMedia = function (constraints, successCallback, failureCallback) { + constraints.audio = constraints.audio || false; + constraints.video = constraints.video || false; + + AdapterJS.WebRTCPlugin.callWhenPluginReady(function() { + AdapterJS.WebRTCPlugin.plugin. + getUserMedia(constraints, successCallback, failureCallback); + }); + }; + window.navigator.getUserMedia = getUserMedia; + + // Defined mediaDevices when promises are available + if ( !navigator.mediaDevices && + typeof Promise !== 'undefined') { + requestUserMedia = function(constraints) { + return new Promise(function(resolve, reject) { + getUserMedia(constraints, resolve, reject); + }); + }; + navigator.mediaDevices = {getUserMedia: requestUserMedia, + enumerateDevices: function() { + return new Promise(function(resolve) { + var kinds = {audio: 'audioinput', video: 'videoinput'}; + return MediaStreamTrack.getSources(function(devices) { + resolve(devices.map(function(device) { + return {label: device.label, + kind: kinds[device.kind], + id: device.id, + deviceId: device.id, + groupId: ''}; + })); + }); + }); + }}; + } + + attachMediaStream = function (element, stream) { + if (!element || !element.parentNode) { + return; + } + + var streamId; + if (stream === null) { + streamId = ''; + } else { + if (typeof stream.enableSoundTracks !== 'undefined') { + stream.enableSoundTracks(true); + } + streamId = stream.id; + } + + var elementId = element.id.length === 0 ? Math.random().toString(36).slice(2) : element.id; + var nodeName = element.nodeName.toLowerCase(); + if (nodeName !== 'object') { // not a plugin tag yet + var tag; + switch(nodeName) { + case 'audio': + tag = AdapterJS.WebRTCPlugin.TAGS.AUDIO; + break; + case 'video': + tag = AdapterJS.WebRTCPlugin.TAGS.VIDEO; + break; + default: + tag = AdapterJS.WebRTCPlugin.TAGS.NONE; + } + + var frag = document.createDocumentFragment(); + var temp = document.createElement('div'); + var classHTML = ''; + if (element.className) { + classHTML = 'class="' + element.className + '" '; + } else if (element.attributes && element.attributes['class']) { + classHTML = 'class="' + element.attributes['class'].value + '" '; + } + + temp.innerHTML = '' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ''; + while (temp.firstChild) { + frag.appendChild(temp.firstChild); + } + + var height = ''; + var width = ''; + if (element.clientWidth || element.clientHeight) { + width = element.clientWidth; + height = element.clientHeight; + } + else if (element.width || element.height) { + width = element.width; + height = element.height; + } + + element.parentNode.insertBefore(frag, element); + frag = document.getElementById(elementId); + frag.width = width; + frag.height = height; + element.parentNode.removeChild(element); + } else { // already an tag, just change the stream id + var children = element.children; + for (var i = 0; i !== children.length; ++i) { + if (children[i].name === 'streamId') { + children[i].value = streamId; + break; + } + } + element.setStreamId(streamId); + } + var newElement = document.getElementById(elementId); + AdapterJS.forwardEventHandlers(newElement, element, Object.getPrototypeOf(element)); + + return newElement; + }; + + reattachMediaStream = function (to, from) { + var stream = null; + var children = from.children; + for (var i = 0; i !== children.length; ++i) { + if (children[i].name === 'streamId') { + AdapterJS.WebRTCPlugin.WaitForPluginReady(); + stream = AdapterJS.WebRTCPlugin.plugin + .getStreamWithId(AdapterJS.WebRTCPlugin.pageId, children[i].value); + break; + } + } + if (stream !== null) { + return attachMediaStream(to, stream); + } else { + console.log('Could not find the stream associated with this element'); + } + }; + + // Propagate attachMediaStream and gUM in window and AdapterJS + window.attachMediaStream = attachMediaStream; + window.reattachMediaStream = reattachMediaStream; + window.getUserMedia = getUserMedia; + AdapterJS.attachMediaStream = attachMediaStream; + AdapterJS.reattachMediaStream = reattachMediaStream; + AdapterJS.getUserMedia = getUserMedia; + + AdapterJS.forwardEventHandlers = function (destElem, srcElem, prototype) { + properties = Object.getOwnPropertyNames( prototype ); + for(var prop in properties) { + if (prop) { + propName = properties[prop]; + + if (typeof propName.slice === 'function' && + propName.slice(0,2) === 'on' && + typeof srcElem[propName] === 'function') { + AdapterJS.addEvent(destElem, propName.slice(2), srcElem[propName]); + } + } + } + var subPrototype = Object.getPrototypeOf(prototype); + if(!!subPrototype) { + AdapterJS.forwardEventHandlers(destElem, srcElem, subPrototype); + } + }; + + RTCIceCandidate = function (candidate) { + if (!candidate.sdpMid) { + candidate.sdpMid = ''; + } + + AdapterJS.WebRTCPlugin.WaitForPluginReady(); + return AdapterJS.WebRTCPlugin.plugin.ConstructIceCandidate( + candidate.sdpMid, candidate.sdpMLineIndex, candidate.candidate + ); + }; + + // inject plugin + AdapterJS.addEvent(document, 'readystatechange', AdapterJS.WebRTCPlugin.injectPlugin); + AdapterJS.WebRTCPlugin.injectPlugin(); + }; + + // This function will be called if the plugin is needed (browser different + // from Chrome or Firefox), but the plugin is not installed. + AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCb = AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCb || + function() { + AdapterJS.addEvent(document, + 'readystatechange', + AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCbPriv); + AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCbPriv(); + }; + + AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCbPriv = function () { + if (AdapterJS.options.hidePluginInstallPrompt) { + return; + } + + var downloadLink = AdapterJS.WebRTCPlugin.pluginInfo.downloadLink; + if(downloadLink) { // if download link + var popupString; + if (AdapterJS.WebRTCPlugin.pluginInfo.portalLink) { // is portal link + popupString = 'This website requires you to install the ' + + ' ' + AdapterJS.WebRTCPlugin.pluginInfo.companyName + + ' WebRTC Plugin' + + ' to work on this browser.'; + } else { // no portal link, just print a generic explanation + popupString = AdapterJS.TEXT.PLUGIN.REQUIRE_INSTALLATION; + } + + AdapterJS.renderNotificationBar(popupString, AdapterJS.TEXT.PLUGIN.BUTTON, downloadLink); + } else { // no download link, just print a generic explanation + AdapterJS.renderNotificationBar(AdapterJS.TEXT.PLUGIN.NOT_SUPPORTED); + } + }; + + // Try to detect the plugin and act accordingly + AdapterJS.WebRTCPlugin.isPluginInstalled( + AdapterJS.WebRTCPlugin.pluginInfo.prefix, + AdapterJS.WebRTCPlugin.pluginInfo.plugName, + AdapterJS.WebRTCPlugin.defineWebRTCInterface, + AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCb); + + // END OF WEBRTC PLUGIN SHIM + /////////////////////////////////////////////////////////////////// +} diff --git a/html5/verto/verto_communicator/src/index.html b/html5/verto/verto_communicator/src/index.html index 2f4e2af1f5..16e11f5cee 100644 --- a/html5/verto/verto_communicator/src/index.html +++ b/html5/verto/verto_communicator/src/index.html @@ -108,6 +108,7 @@ + diff --git a/html5/verto/verto_communicator/src/partials/preview.html b/html5/verto/verto_communicator/src/partials/preview.html index 9c58c30d0b..68b4523e72 100644 --- a/html5/verto/verto_communicator/src/partials/preview.html +++ b/html5/verto/verto_communicator/src/partials/preview.html @@ -7,7 +7,7 @@
-
+
diff --git a/html5/verto/verto_communicator/src/storageService/services/splash_screen.js b/html5/verto/verto_communicator/src/storageService/services/splash_screen.js index ae77d000e7..9aa4414bf5 100644 --- a/html5/verto/verto_communicator/src/storageService/services/splash_screen.js +++ b/html5/verto/verto_communicator/src/storageService/services/splash_screen.js @@ -14,11 +14,7 @@ 'status': 'success', 'message': $translate.instant('BROWSER_COMPATIBILITY') }; - navigator.getUserMedia = navigator.getUserMedia || - navigator.webkitGetUserMedia || - navigator.mozGetUserMedia; - - if (!navigator.getUserMedia) { + if (!navigator.mediaDevices.getUserMedia) { result['status'] = 'error'; result['message'] = $translate.instant('BROWSER_WITHOUT_WEBRTC'); reject(result); diff --git a/html5/verto/verto_communicator/src/storageService/services/storage.js b/html5/verto/verto_communicator/src/storageService/services/storage.js index 501b7b7ab6..8aff5ee61a 100644 --- a/html5/verto/verto_communicator/src/storageService/services/storage.js +++ b/html5/verto/verto_communicator/src/storageService/services/storage.js @@ -23,6 +23,7 @@ mutedMic: false, preview: true, selectedVideo: null, + selectedVideoName: null, selectedAudio: null, selectedShare: null, selectedSpeaker: null, diff --git a/html5/verto/verto_communicator/src/vertoControllers/controllers/DialPadController.js b/html5/verto/verto_communicator/src/vertoControllers/controllers/DialPadController.js index 50bb6ea75b..7800ea3e6f 100644 --- a/html5/verto/verto_communicator/src/vertoControllers/controllers/DialPadController.js +++ b/html5/verto/verto_communicator/src/vertoControllers/controllers/DialPadController.js @@ -4,8 +4,8 @@ angular .module('vertoControllers') .controller('DialPadController', ['$rootScope', '$scope', - '$http', '$location', 'toastr', 'verto', 'storage', 'CallHistory', 'eventQueue', - function($rootScope, $scope, $http, $location, toastr, verto, storage, CallHistory, eventQueue) { + '$http', '$location', 'toastr', 'verto', 'storage', 'CallHistory', 'eventQueue', '$timeout', + function($rootScope, $scope, $http, $location, toastr, verto, storage, CallHistory, eventQueue, $timeout) { console.debug('Executing DialPadController.'); eventQueue.process(); diff --git a/html5/verto/verto_communicator/src/vertoControllers/controllers/PreviewController.js b/html5/verto/verto_communicator/src/vertoControllers/controllers/PreviewController.js index 7a8945ec02..1379409cfe 100644 --- a/html5/verto/verto_communicator/src/vertoControllers/controllers/PreviewController.js +++ b/html5/verto/verto_communicator/src/vertoControllers/controllers/PreviewController.js @@ -11,7 +11,10 @@ $scope.storage = storage; console.debug('Executing PreviewController.'); var localVideo = document.getElementById('videopreview'); - var volumes = document.querySelector('#mic-meter .volumes').children; + var volumes = document.querySelector('#mic-meter .volumes'); + if (volumes) { + volumes = volumes.children; + } $scope.localVideo = function() { var constraints = { @@ -31,10 +34,13 @@ }); }; + var audioContext = null; + if (typeof AudioContext !== "undefined") { + audioContext = new AudioContext(); + } - var audioContext = new AudioContext(); var mediaStreamSource = null; - var meter; + var meter = null; var streamObj = {}; function stopMedia(stream) { @@ -55,13 +61,12 @@ } streamObj = stream; - localVideo.src = window.URL.createObjectURL(stream); - - mediaStreamSource = audioContext.createMediaStreamSource(stream); - meter = createAudioMeter(audioContext); - mediaStreamSource.connect(meter); - - renderMic(); + FSRTCattachMediaStream(localVideo, stream); + if (audioContext) { + mediaStreamSource = audioContext.createMediaStreamSource(stream); + meter = createAudioMeter(audioContext); + mediaStreamSource.connect(meter); + }; } function renderMic() { @@ -109,8 +114,10 @@ $scope.endPreview = function() { localVideo.src = null; - meter.shutdown(); - meter.onaudioprocess = null; + if (audioContext) { + meter.shutdown(); + meter.onaudioprocess = null; + }; stopMedia(streamObj); $location.path('/dialpad'); storage.data.preview = false; diff --git a/html5/verto/verto_communicator/src/vertoControllers/controllers/SettingsController.js b/html5/verto/verto_communicator/src/vertoControllers/controllers/SettingsController.js index c52cdb8c82..a7da6f4beb 100644 --- a/html5/verto/verto_communicator/src/vertoControllers/controllers/SettingsController.js +++ b/html5/verto/verto_communicator/src/vertoControllers/controllers/SettingsController.js @@ -33,6 +33,15 @@ }); $scope.ok = function() { + console.log('Camera Selected is', $scope.mydata.selectedVideo, $scope.verto.data.videoDevices); + + angular.forEach(verto.data.videoDevices, function(video) { + console.log('checking video ', video); + if (video.id == $scope.mydata.selectedVideo) { + $scope.mydata.selectedVideoName = video.label; + console.log('Setting selectedVideoName to ', video.label); + } + }) if ($scope.mydata.selectedSpeaker != storage.data.selectedSpeaker) { $rootScope.$emit('changedSpeaker', $scope.mydata.selectedSpeaker); } diff --git a/html5/verto/verto_communicator/src/vertoControllers/controllers/SplashScreenController.js b/html5/verto/verto_communicator/src/vertoControllers/controllers/SplashScreenController.js index 6fd32f9de7..65ab68d93b 100644 --- a/html5/verto/verto_communicator/src/vertoControllers/controllers/SplashScreenController.js +++ b/html5/verto/verto_communicator/src/vertoControllers/controllers/SplashScreenController.js @@ -74,7 +74,7 @@ $rootScope.$on('progress.complete', function(ev, current_progress) { $scope.message = 'Complete'; if(verto.data.connected) { - if (storage.data.preview) { + if (0 && storage.data.preview) { $location.path('/preview'); } else { diff --git a/html5/verto/verto_communicator/src/vertoDirectives/directives/videoTag.js b/html5/verto/verto_communicator/src/vertoDirectives/directives/videoTag.js index e40adc879a..15fd7e1a01 100644 --- a/html5/verto/verto_communicator/src/vertoDirectives/directives/videoTag.js +++ b/html5/verto/verto_communicator/src/vertoDirectives/directives/videoTag.js @@ -13,17 +13,35 @@ .module('vertoDirectives') .directive('videoTag', function() { + function link(scope, element, attrs) { // Moving the video tag to the new place inside the incall page. console.log('Moving the video to element.'); - jQuery('video').removeClass('hide').appendTo(element); - jQuery('video').css('display', 'block'); + var videoElem = jQuery('#webcam'); + + var newParent = document.getElementsByClassName('video-tag-wrapper'); + newParent[0].appendChild(document.getElementById('webcam')); + + $("#webcam").resize(function() { + updateVideoSize(); + }); + + $(window).resize(function() { + updateVideoSize(); + }); + + updateVideoSize(); + + videoElem.removeClass('hide'); + videoElem.css('display', 'block'); + scope.callActive("", {useVideo: true}); element.on('$destroy', function() { // Move the video back to the body. console.log('Moving the video back to body.'); - jQuery('video').addClass('hide').appendTo(jQuery('body')); + videoElem.addClass('hide').appendTo(jQuery('body')); + $(window).unbind('resize'); }); } diff --git a/html5/verto/verto_communicator/src/vertoService/services/vertoService.js b/html5/verto/verto_communicator/src/vertoService/services/vertoService.js index b926c77f9e..404c0c4a9f 100644 --- a/html5/verto/verto_communicator/src/vertoService/services/vertoService.js +++ b/html5/verto/verto_communicator/src/vertoService/services/vertoService.js @@ -113,6 +113,35 @@ var framerate = [{ label: '30 FPS' }, ]; +var updateReq; + +var updateVideoSize = function(ms) { + if (!ms) ms = 500; + + clearTimeout(updateReq); + updateReq = setTimeout(function () { + var videoElem = jQuery('#webcam'); + videoElem.width(""); + videoElem.height(""); + + var w = videoElem.width(); + var h = videoElem.height(); + var new_w, new_h; + var aspect = 1920 / 1080; + var videoContainer = jQuery('div.video-wrapper'); + if (w > h) { + new_w = videoContainer.width(); + new_h = Math.round(videoContainer.width() / aspect); + } else { + new_h = videoContainer.height(); + new_w = Math.round(videoContainer.height() / aspect); + } + videoElem.width(new_w); + videoElem.height(new_h); + console.log('Setting video size to ' + new_w + '/' + new_h); + }, ms); +} + var vertoService = angular.module('vertoService', ['ngCookies']); vertoService.service('verto', ['$rootScope', '$cookieStore', '$location', 'storage', @@ -317,7 +346,13 @@ vertoService.service('verto', ['$rootScope', '$cookieStore', '$location', 'stora // Verify if selected devices are valid var videoFlag = data.videoDevices.some(function(device) { - return device.id == storage.data.selectedVideo; + console.log('Evaluating device ', device); + if (device.label == storage.data.selectedVideoName) { + console.log('Matched video selection by name: ', device.label); + storage.data.selectedVideo = device.id; + return true; + } + return device.id == storage.data.selectedVideo && storage.data.selectedVideo !== "none"; }); var shareFlag = data.shareDevices.some(function(device) { @@ -332,7 +367,10 @@ vertoService.service('verto', ['$rootScope', '$cookieStore', '$location', 'stora return device.id == storage.data.selectedSpeaker; }); - if (!videoFlag) storage.data.selectedVideo = data.videoDevices[0].id; + console.log('Storage Video: ', storage.data.selectedVideo); + console.log('Video Flag: ', videoFlag) + + if (!videoFlag) storage.data.selectedVideo = data.videoDevices[data.videoDevices.length - 1].id; if (!shareFlag) storage.data.selectedShare = data.shareDevices[0].id; if (!audioFlag) storage.data.selectedAudio = data.audioDevices[0].id; if (!speakerFlag && data.speakerDevices.length > 0) storage.data.selectedSpeaker = data.speakerDevices[0].id; @@ -484,7 +522,6 @@ vertoService.service('verto', ['$rootScope', '$cookieStore', '$location', 'stora data.liveArray.onChange = function(obj, args) { // console.log('liveArray.onChange', obj, args); - switch (args.action) { case 'bootObj': $rootScope.$emit('members.boot', args.data); @@ -560,6 +597,7 @@ vertoService.service('verto', ['$rootScope', '$cookieStore', '$location', 'stora console.log("conference-liveArray-join"); stopConference(); startConference(v, dialog, params.pvtData); + updateVideoSize(); } break; case "conference-liveArray-part": @@ -616,6 +654,7 @@ vertoService.service('verto', ['$rootScope', '$cookieStore', '$location', 'stora console.debug('Talking to:', d.cidString()); data.callState = 'active'; callActive(d.lastState.name, d.params); + updateVideoSize(); break; case "hangup": console.debug('Call ended with cause: ' + d.cause); diff --git a/html5/verto/video_demo/js/verto-min.js b/html5/verto/video_demo/js/verto-min.js index dbe0017137..52f165af51 100644 --- a/html5/verto/video_demo/js/verto-min.js +++ b/html5/verto/video_demo/js/verto-min.js @@ -6,15 +6,13 @@ function getCodecPayloadType(sdpLine){var pattern=new RegExp('a=rtpmap:(\\d+) \\ function setDefaultCodec(mLine,payload){var elements=mLine.split(' ');var newLine=[];var index=0;for(var i=0;iw&&$.FSRTC.validRes[i][1]>h){w=$.FSRTC.validRes[i][0];h=$.FSRTC.validRes[i][1];}} return[w,h];} -var resList=[[320,180],[320,240],[640,360],[640,480],[1280,720],[1920,1080]];var resI=0;var ttl=0;var checkRes=function(cam,func){if(resI>=resList.length){var res={'validRes':$.FSRTC.validRes,'bestResSupported':$.FSRTC.bestResSupported()};localStorage.setItem("res_"+cam,$.toJSON(res));if(func)return func(res);return;} -var video={mandatory:{},optional:[]} -if(cam){video.optional=[{sourceId:cam}];} -w=resList[resI][0];h=resList[resI][1];resI++;video.mandatory={"minWidth":w,"minHeight":h,"maxWidth":w,"maxHeight":h};getUserMedia({constraints:{audio:ttl++==0,video:video},onsuccess:function(e){e.getTracks().forEach(function(track){track.stop();});console.info(w+"x"+h+" supported.");$.FSRTC.validRes.push([w,h]);checkRes(cam,func);},onerror:function(e){console.error(w+"x"+h+" not supported.");checkRes(cam,func);}});} +var resList=[[160,120],[320,180],[320,240],[640,360],[640,480],[1280,720],[1920,1080]];var resI=0;var ttl=0;var checkRes=function(cam,func){if(resI>=resList.length){var res={'validRes':$.FSRTC.validRes,'bestResSupported':$.FSRTC.bestResSupported()};localStorage.setItem("res_"+cam,$.toJSON(res));if(func)return func(res);return;} +var video={} +if(cam){video.deviceId={exact:cam};} +w=resList[resI][0];h=resList[resI][1];resI++;video={width:w,height:h};getUserMedia({constraints:{audio:ttl++==0,video:video},onsuccess:function(e){e.getTracks().forEach(function(track){track.stop();});console.info(w+"x"+h+" supported.");$.FSRTC.validRes.push([w,h]);checkRes(cam,func);},onerror:function(e){console.error(w+"x"+h+" not supported.");checkRes(cam,func);}});} $.FSRTC.getValidRes=function(cam,func){var used=[];var cached=localStorage.getItem("res_"+cam);if(cached){var cache=$.parseJSON(cached);if(cache){$.FSRTC.validRes=cache.validRes;console.log("CACHED RES FOR CAM "+cam,cache);}else{console.error("INVALID CACHE");} return func?func(cache):null;} $.FSRTC.validRes=[];resI=0;checkRes(cam,func);} @@ -254,7 +247,9 @@ if(!dialog.params.remote_caller_id_name){dialog.params.remote_caller_id_name="No if(!dialog.params.remote_caller_id_number){dialog.params.remote_caller_id_number="UNKNOWN";} RTCcallbacks.onMessage=function(rtc,msg){console.debug(msg);};RTCcallbacks.onAnswerSDP=function(rtc,sdp){console.error("answer sdp",sdp);};}else{dialog.params.remote_caller_id_name="Outbound Call";dialog.params.remote_caller_id_number=dialog.params.destination_number;} RTCcallbacks.onICESDP=function(rtc){console.log("RECV "+rtc.type+" SDP",rtc.mediaData.SDP);if(dialog.state==$.verto.enum.state.requesting||dialog.state==$.verto.enum.state.answering||dialog.state==$.verto.enum.state.active){location.reload();return;} -if(rtc.type=="offer"){if(dialog.state==$.verto.enum.state.active){dialog.setState($.verto.enum.state.requesting);dialog.sendMethod("verto.attach",{sdp:rtc.mediaData.SDP});}else{dialog.setState($.verto.enum.state.requesting);dialog.sendMethod("verto.invite",{sdp:rtc.mediaData.SDP});}}else{dialog.setState($.verto.enum.state.answering);dialog.sendMethod(dialog.attach?"verto.attach":"verto.answer",{sdp:dialog.rtc.mediaData.SDP});}};RTCcallbacks.onICE=function(rtc){if(rtc.type=="offer"){console.log("offer",rtc.mediaData.candidate);return;}};RTCcallbacks.onStream=function(rtc,stream){console.log("stream started");};RTCcallbacks.onError=function(e){console.error("ERROR:",e);dialog.hangup({cause:"Device or Permission Error"});};dialog.rtc=new $.FSRTC({callbacks:RTCcallbacks,localVideo:dialog.screenShare?null:dialog.localVideo,useVideo:dialog.params.useVideo?dialog.videoStream:null,useAudio:dialog.audioStream,useStereo:dialog.params.useStereo,videoParams:dialog.params.videoParams,audioParams:verto.options.audioParams,iceServers:verto.options.iceServers,screenShare:dialog.screenShare,useCamera:dialog.useCamera,useMic:dialog.useMic,useSpeak:dialog.useSpeak});dialog.rtc.verto=dialog.verto;if(dialog.direction==$.verto.enum.direction.inbound){if(dialog.attach){dialog.answer();}else{dialog.ring();}}};$.verto.dialog.prototype.invite=function(){var dialog=this;dialog.rtc.call();};$.verto.dialog.prototype.sendMethod=function(method,obj){var dialog=this;obj.dialogParams={};for(var i in dialog.params){if(i=="sdp"&&method!="verto.invite"&&method!="verto.attach"){continue;} +if(rtc.type=="offer"){if(dialog.state==$.verto.enum.state.active){dialog.setState($.verto.enum.state.requesting);dialog.sendMethod("verto.attach",{sdp:rtc.mediaData.SDP});}else{dialog.setState($.verto.enum.state.requesting);dialog.sendMethod("verto.invite",{sdp:rtc.mediaData.SDP});}}else{dialog.setState($.verto.enum.state.answering);dialog.sendMethod(dialog.attach?"verto.attach":"verto.answer",{sdp:dialog.rtc.mediaData.SDP});}};RTCcallbacks.onICE=function(rtc){if(rtc.type=="offer"){console.log("offer",rtc.mediaData.candidate);return;}};RTCcallbacks.onStream=function(rtc,stream){if(dialog.verto.options.permissionCallback&&typeof dialog.verto.options.permissionCallback.onGranted==='function'){dialog.verto.options.permissionCallback.onGranted();} +console.log("stream started");};RTCcallbacks.onError=function(e){if(dialog.verto.options.permissionCallback&&typeof dialog.verto.options.permissionCallback.onDenied==='function'){dialog.verto.options.permissionCallback.onDenied();} +console.error("ERROR:",e);dialog.hangup({cause:"Device or Permission Error"});};dialog.rtc=new $.FSRTC({callbacks:RTCcallbacks,localVideo:dialog.screenShare?null:dialog.localVideo,useVideo:dialog.params.useVideo?dialog.videoStream:null,useAudio:dialog.audioStream,useStereo:dialog.params.useStereo,videoParams:dialog.params.videoParams,audioParams:verto.options.audioParams,iceServers:verto.options.iceServers,screenShare:dialog.screenShare,useCamera:dialog.useCamera,useMic:dialog.useMic,useSpeak:dialog.useSpeak});dialog.rtc.verto=dialog.verto;if(dialog.direction==$.verto.enum.direction.inbound){if(dialog.attach){dialog.answer();}else{dialog.ring();}}};$.verto.dialog.prototype.invite=function(){var dialog=this;dialog.rtc.call();};$.verto.dialog.prototype.sendMethod=function(method,obj){var dialog=this;obj.dialogParams={};for(var i in dialog.params){if(i=="sdp"&&method!="verto.invite"&&method!="verto.attach"){continue;} obj.dialogParams[i]=dialog.params[i];} dialog.verto.rpcClient.call(method,obj,function(e){dialog.processReply(method,true,e);},function(e){dialog.processReply(method,false,e);});};function checkStateChange(oldS,newS){if(newS==$.verto.enum.state.purge||$.verto.enum.states[oldS.name][newS.name]){return true;} return false;} @@ -299,4 +294,218 @@ $.verto.videoDevices=vid;$.verto.audioInDevices=aud_in;console.info("Audio Devic navigator.mediaDevices.enumerateDevices().then(function(devices){devices.forEach(function(device){console.log(device);console.log(device.kind+": "+device.label+" id = "+device.deviceId);if(device.kind==="videoinput"){vid.push({id:device.deviceId,kind:"video",label:device.label});}else if(device.kind==="audioinput"){aud_in.push({id:device.deviceId,kind:"audio_in",label:device.label});}else if(device.kind==="audiooutput"){aud_out.push({id:device.deviceId,kind:"audio_out",label:device.label});}});$.verto.videoDevices=vid;$.verto.audioInDevices=aud_in;$.verto.audioOutDevices=aud_out;console.info("Audio IN Devices",$.verto.audioInDevices);console.info("Audio Out Devices",$.verto.audioOutDevices);console.info("Video Devices",$.verto.videoDevices);runtime(true);}).catch(function(err){console.log(" Device Enumeration ERROR: "+err.name+": "+err.message);runtime(false);});}};$.verto.refreshDevices=function(runtime){checkDevices(runtime);} $.verto.init=function(obj,runtime){if(!obj){obj={};} if(!obj.skipPermCheck&&!obj.skipDeviceCheck){$.FSRTC.checkPerms(function(status){checkDevices(runtime);},true,true);}else if(obj.skipPermCheck&&!obj.skipDeviceCheck){checkDevices(runtime);}else if(!obj.skipPermCheck&&obj.skipDeviceCheck){$.FSRTC.checkPerms(function(status){runtime(status);},true,true);}else{runtime(null);}} -$.verto.genUUID=function(){return generateGUID();}})(jQuery); \ No newline at end of file +$.verto.genUUID=function(){return generateGUID();}})(jQuery);var AdapterJS=AdapterJS||{};if(typeof exports!=='undefined'){module.exports=AdapterJS;} +AdapterJS.options=AdapterJS.options||{};AdapterJS.VERSION='0.13.3';AdapterJS.onwebrtcready=AdapterJS.onwebrtcready||function(isUsingPlugin){};AdapterJS._onwebrtcreadies=[];AdapterJS.webRTCReady=function(callback){if(typeof callback!=='function'){throw new Error('Callback provided is not a function');} +if(true===AdapterJS.onwebrtcreadyDone){callback(null!==AdapterJS.WebRTCPlugin.plugin);}else{AdapterJS._onwebrtcreadies.push(callback);}};AdapterJS.WebRTCPlugin=AdapterJS.WebRTCPlugin||{};AdapterJS.WebRTCPlugin.pluginInfo={prefix:'Tem',plugName:'TemWebRTCPlugin',pluginId:'plugin0',type:'application/x-temwebrtcplugin',onload:'__TemWebRTCReady0',portalLink:'http://skylink.io/plugin/',downloadLink:null,companyName:'Temasys'};if(!!navigator.platform.match(/^Mac/i)){AdapterJS.WebRTCPlugin.pluginInfo.downloadLink='http://bit.ly/1n77hco';} +else if(!!navigator.platform.match(/^Win/i)){AdapterJS.WebRTCPlugin.pluginInfo.downloadLink='http://bit.ly/1kkS4FN';} +AdapterJS.WebRTCPlugin.TAGS={NONE:'none',AUDIO:'audio',VIDEO:'video'};AdapterJS.WebRTCPlugin.pageId=Math.random().toString(36).slice(2);AdapterJS.WebRTCPlugin.plugin=null;AdapterJS.WebRTCPlugin.setLogLevel=null;AdapterJS.WebRTCPlugin.defineWebRTCInterface=null;AdapterJS.WebRTCPlugin.isPluginInstalled=null;AdapterJS.WebRTCPlugin.pluginInjectionInterval=null;AdapterJS.WebRTCPlugin.injectPlugin=null;AdapterJS.WebRTCPlugin.PLUGIN_STATES={NONE:0,INITIALIZING:1,INJECTING:2,INJECTED:3,READY:4};AdapterJS.WebRTCPlugin.pluginState=AdapterJS.WebRTCPlugin.PLUGIN_STATES.NONE;AdapterJS.onwebrtcreadyDone=false;AdapterJS.WebRTCPlugin.PLUGIN_LOG_LEVELS={NONE:'NONE',ERROR:'ERROR',WARNING:'WARNING',INFO:'INFO',VERBOSE:'VERBOSE',SENSITIVE:'SENSITIVE'};AdapterJS.WebRTCPlugin.WaitForPluginReady=null;AdapterJS.WebRTCPlugin.callWhenPluginReady=null;__TemWebRTCReady0=function(){if(document.readyState==='complete'){AdapterJS.WebRTCPlugin.pluginState=AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY;AdapterJS.maybeThroughWebRTCReady();}else{var timer=setInterval(function(){if(document.readyState==='complete'){clearInterval(timer);AdapterJS.WebRTCPlugin.pluginState=AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY;AdapterJS.maybeThroughWebRTCReady();}},100);}};AdapterJS.maybeThroughWebRTCReady=function(){if(!AdapterJS.onwebrtcreadyDone){AdapterJS.onwebrtcreadyDone=true;if(AdapterJS._onwebrtcreadies.length){AdapterJS._onwebrtcreadies.forEach(function(callback){if(typeof(callback)==='function'){callback(AdapterJS.WebRTCPlugin.plugin!==null);}});}else if(typeof(AdapterJS.onwebrtcready)==='function'){AdapterJS.onwebrtcready(AdapterJS.WebRTCPlugin.plugin!==null);}}};AdapterJS.TEXT={PLUGIN:{REQUIRE_INSTALLATION:'This website requires you to install a WebRTC-enabling plugin '+'to work on this browser.',NOT_SUPPORTED:'Your browser does not support WebRTC.',BUTTON:'Install Now'},REFRESH:{REQUIRE_REFRESH:'Please refresh page',BUTTON:'Refresh Page'}};AdapterJS._iceConnectionStates={starting:'starting',checking:'checking',connected:'connected',completed:'connected',done:'completed',disconnected:'disconnected',failed:'failed',closed:'closed'};AdapterJS._iceConnectionFiredStates=[];AdapterJS.isDefined=null;AdapterJS.parseWebrtcDetectedBrowser=function(){var hasMatch=null;if((!!window.opr&&!!opr.addons)||!!window.opera||navigator.userAgent.indexOf(' OPR/')>=0){webrtcDetectedBrowser='opera';webrtcDetectedType='webkit';webrtcMinimumVersion=26;hasMatch=/OPR\/(\d+)/i.exec(navigator.userAgent)||[];webrtcDetectedVersion=parseInt(hasMatch[1],10);}else if(typeof InstallTrigger!=='undefined'){webrtcDetectedType='moz';}else if(Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor')>0){webrtcDetectedBrowser='safari';webrtcDetectedType='plugin';webrtcMinimumVersion=7;hasMatch=/version\/(\d+)/i.exec(navigator.userAgent)||[];webrtcDetectedVersion=parseInt(hasMatch[1],10);}else if(false||!!document.documentMode){webrtcDetectedBrowser='IE';webrtcDetectedType='plugin';webrtcMinimumVersion=9;hasMatch=/\brv[ :]+(\d+)/g.exec(navigator.userAgent)||[];webrtcDetectedVersion=parseInt(hasMatch[1]||'0',10);if(!webrtcDetectedVersion){hasMatch=/\bMSIE[ :]+(\d+)/g.exec(navigator.userAgent)||[];webrtcDetectedVersion=parseInt(hasMatch[1]||'0',10);}}else if(!!window.StyleMedia){webrtcDetectedType='';}else if(!!window.chrome&&!!window.chrome.webstore){webrtcDetectedType='webkit';}else if((webrtcDetectedBrowser==='chrome'||webrtcDetectedBrowser==='opera')&&!!window.CSS){webrtcDetectedBrowser='blink';} +window.webrtcDetectedBrowser=webrtcDetectedBrowser;window.webrtcDetectedVersion=webrtcDetectedVersion;window.webrtcMinimumVersion=webrtcMinimumVersion;};AdapterJS.addEvent=function(elem,evnt,func){if(elem.addEventListener){elem.addEventListener(evnt,func,false);}else if(elem.attachEvent){elem.attachEvent('on'+evnt,func);}else{elem[evnt]=func;}};AdapterJS.renderNotificationBar=function(text,buttonText,buttonLink,openNewTab,displayRefreshBar){if(document.readyState!=='complete'){return;} +var w=window;var i=document.createElement('iframe');i.name='adapterjs-alert';i.style.position='fixed';i.style.top='-41px';i.style.left=0;i.style.right=0;i.style.width='100%';i.style.height='40px';i.style.backgroundColor='#ffffe1';i.style.border='none';i.style.borderBottom='1px solid #888888';i.style.zIndex='9999999';if(typeof i.style.webkitTransition==='string'){i.style.webkitTransition='all .5s ease-out';}else if(typeof i.style.transition==='string'){i.style.transition='all .5s ease-out';} +document.body.appendChild(i);var c=(i.contentWindow)?i.contentWindow:(i.contentDocument.document)?i.contentDocument.document:i.contentDocument;c.document.open();c.document.write(''+text+'');if(buttonText&&buttonLink){c.document.write('');c.document.close();AdapterJS.addEvent(c.document.getElementById('okay'),'click',function(e){if(!!displayRefreshBar){AdapterJS.renderNotificationBar(AdapterJS.TEXT.EXTENSION?AdapterJS.TEXT.EXTENSION.REQUIRE_REFRESH:AdapterJS.TEXT.REFRESH.REQUIRE_REFRESH,AdapterJS.TEXT.REFRESH.BUTTON,'javascript:location.reload()');} +window.open(buttonLink,!!openNewTab?'_blank':'_top');e.preventDefault();try{e.cancelBubble=true;}catch(error){} +var pluginInstallInterval=setInterval(function(){if(!isIE){navigator.plugins.refresh(false);} +AdapterJS.WebRTCPlugin.isPluginInstalled(AdapterJS.WebRTCPlugin.pluginInfo.prefix,AdapterJS.WebRTCPlugin.pluginInfo.plugName,function(){clearInterval(pluginInstallInterval);AdapterJS.WebRTCPlugin.defineWebRTCInterface();},function(){});},500);});AdapterJS.addEvent(c.document.getElementById('cancel'),'click',function(e){w.document.body.removeChild(i);});}else{c.document.close();} +setTimeout(function(){if(typeof i.style.webkitTransform==='string'){i.style.webkitTransform='translateY(40px)';}else if(typeof i.style.transform==='string'){i.style.transform='translateY(40px)';}else{i.style.top='0px';}},300);};webrtcDetectedType=null;checkMediaDataChannelSettings=function(peerBrowserAgent,peerBrowserVersion,callback,constraints){if(typeof callback!=='function'){return;} +var beOfferer=true;var isLocalFirefox=webrtcDetectedBrowser==='firefox';var isLocalFirefoxInterop=webrtcDetectedType==='moz'&&webrtcDetectedVersion>30;var isPeerFirefox=peerBrowserAgent==='firefox';var isPeerFirefoxInterop=peerBrowserAgent==='firefox'&&((peerBrowserVersion)?(peerBrowserVersion>30):false);if((isLocalFirefox&&isPeerFirefox)||(isLocalFirefoxInterop)){try{delete constraints.mandatory.MozDontOfferDataChannel;}catch(error){console.error('Failed deleting MozDontOfferDataChannel');console.error(error);}}else if((isLocalFirefox&&!isPeerFirefox)){constraints.mandatory.MozDontOfferDataChannel=true;} +if(!isLocalFirefox){for(var prop in constraints.mandatory){if(constraints.mandatory.hasOwnProperty(prop)){if(prop.indexOf('Moz')!==-1){delete constraints.mandatory[prop];}}}} +if(isLocalFirefox&&!isPeerFirefox&&!isLocalFirefoxInterop){beOfferer=false;} +callback(beOfferer,constraints);};checkIceConnectionState=function(peerId,iceConnectionState,callback){if(typeof callback!=='function'){console.warn('No callback specified in checkIceConnectionState. Aborted.');return;} +peerId=(peerId)?peerId:'peer';if(!AdapterJS._iceConnectionFiredStates[peerId]||iceConnectionState===AdapterJS._iceConnectionStates.disconnected||iceConnectionState===AdapterJS._iceConnectionStates.failed||iceConnectionState===AdapterJS._iceConnectionStates.closed){AdapterJS._iceConnectionFiredStates[peerId]=[];} +iceConnectionState=AdapterJS._iceConnectionStates[iceConnectionState];if(AdapterJS._iceConnectionFiredStates[peerId].indexOf(iceConnectionState)<0){AdapterJS._iceConnectionFiredStates[peerId].push(iceConnectionState);if(iceConnectionState===AdapterJS._iceConnectionStates.connected){setTimeout(function(){AdapterJS._iceConnectionFiredStates[peerId].push(AdapterJS._iceConnectionStates.done);callback(AdapterJS._iceConnectionStates.done);},1000);} +callback(iceConnectionState);} +return;};createIceServer=null;createIceServers=null;RTCPeerConnection=null;RTCSessionDescription=(typeof RTCSessionDescription==='function')?RTCSessionDescription:null;RTCIceCandidate=(typeof RTCIceCandidate==='function')?RTCIceCandidate:null;getUserMedia=null;attachMediaStream=null;reattachMediaStream=null;webrtcDetectedBrowser=null;webrtcDetectedVersion=null;webrtcMinimumVersion=null;if(navigator.mozGetUserMedia||navigator.webkitGetUserMedia||(navigator.mediaDevices&&navigator.userAgent.match(/Edge\/(\d+).(\d+)$/))){(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.adapter=f()}})(function(){var define,module,exports;return(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o0?'m='+part:part).trim()+'\r\n';});};SDPUtils.matchPrefix=function(blob,prefix){return SDPUtils.splitLines(blob).filter(function(line){return line.indexOf(prefix)===0;});};SDPUtils.parseCandidate=function(line){var parts;if(line.indexOf('a=candidate:')===0){parts=line.substring(12).split(' ');}else{parts=line.substring(10).split(' ');} +var candidate={foundation:parts[0],component:parts[1],protocol:parts[2].toLowerCase(),priority:parseInt(parts[3],10),ip:parts[4],port:parseInt(parts[5],10),type:parts[7]};for(var i=8;i-1){parts.attribute=line.substr(sp+1,colon-sp-1);parts.value=line.substr(colon+1);}else{parts.attribute=line.substr(sp+1);} +return parts;};SDPUtils.getDtlsParameters=function(mediaSection,sessionpart){var lines=SDPUtils.splitLines(mediaSection);lines=lines.concat(SDPUtils.splitLines(sessionpart));var fpLine=lines.filter(function(line){return line.indexOf('a=fingerprint:')===0;})[0].substr(14);var dtlsParameters={role:'auto',fingerprints:[{algorithm:fpLine.split(' ')[0],value:fpLine.split(' ')[1]}]};return dtlsParameters;};SDPUtils.writeDtlsParameters=function(params,setupType){var sdp='a=setup:'+setupType+'\r\n';params.fingerprints.forEach(function(fp){sdp+='a=fingerprint:'+fp.algorithm+' '+fp.value+'\r\n';});return sdp;};SDPUtils.getIceParameters=function(mediaSection,sessionpart){var lines=SDPUtils.splitLines(mediaSection);lines=lines.concat(SDPUtils.splitLines(sessionpart));var iceParameters={usernameFragment:lines.filter(function(line){return line.indexOf('a=ice-ufrag:')===0;})[0].substr(12),password:lines.filter(function(line){return line.indexOf('a=ice-pwd:')===0;})[0].substr(10)};return iceParameters;};SDPUtils.writeIceParameters=function(params){return'a=ice-ufrag:'+params.usernameFragment+'\r\n'+'a=ice-pwd:'+params.password+'\r\n';};SDPUtils.parseRtpParameters=function(mediaSection){var description={codecs:[],headerExtensions:[],fecMechanisms:[],rtcp:[]};var lines=SDPUtils.splitLines(mediaSection);var mline=lines[0].split(' ');for(var i=3;i0?'9':'0';sdp+=' UDP/TLS/RTP/SAVPF ';sdp+=caps.codecs.map(function(codec){if(codec.preferredPayloadType!==undefined){return codec.preferredPayloadType;} +return codec.payloadType;}).join(' ')+'\r\n';sdp+='c=IN IP4 0.0.0.0\r\n';sdp+='a=rtcp:9 IN IP4 0.0.0.0\r\n';caps.codecs.forEach(function(codec){sdp+=SDPUtils.writeRtpMap(codec);sdp+=SDPUtils.writeFmtp(codec);sdp+=SDPUtils.writeRtcpFb(codec);});sdp+='a=rtcp-mux\r\n';return sdp;};SDPUtils.parseRtpEncodingParameters=function(mediaSection){var encodingParameters=[];var description=SDPUtils.parseRtpParameters(mediaSection);var hasRed=description.fecMechanisms.indexOf('RED')!==-1;var hasUlpfec=description.fecMechanisms.indexOf('ULPFEC')!==-1;var ssrcs=SDPUtils.matchPrefix(mediaSection,'a=ssrc:').map(function(line){return SDPUtils.parseSsrcMedia(line);}).filter(function(parts){return parts.attribute==='cname';});var primarySsrc=ssrcs.length>0&&ssrcs[0].ssrc;var secondarySsrc;var flows=SDPUtils.matchPrefix(mediaSection,'a=ssrc-group:FID').map(function(line){var parts=line.split(' ');parts.shift();return parts.map(function(part){return parseInt(part,10);});});if(flows.length>0&&flows[0].length>1&&flows[0][0]===primarySsrc){secondarySsrc=flows[0][1];} +description.codecs.forEach(function(codec){if(codec.name.toUpperCase()==='RTX'&&codec.parameters.apt){var encParam={ssrc:primarySsrc,codecPayloadType:parseInt(codec.parameters.apt,10),rtx:{payloadType:codec.payloadType,ssrc:secondarySsrc}};encodingParameters.push(encParam);if(hasRed){encParam=JSON.parse(JSON.stringify(encParam));encParam.fec={ssrc:secondarySsrc,mechanism:hasUlpfec?'red+ulpfec':'red'};encodingParameters.push(encParam);}}});if(encodingParameters.length===0&&primarySsrc){encodingParameters.push({ssrc:primarySsrc});} +var bandwidth=SDPUtils.matchPrefix(mediaSection,'b=');if(bandwidth.length){if(bandwidth[0].indexOf('b=TIAS:')===0){bandwidth=parseInt(bandwidth[0].substr(7),10);}else if(bandwidth[0].indexOf('b=AS:')===0){bandwidth=parseInt(bandwidth[0].substr(5),10);} +encodingParameters.forEach(function(params){params.maxBitrate=bandwidth;});} +return encodingParameters;};SDPUtils.writeSessionBoilerplate=function(){return'v=0\r\n'+'o=thisisadapterortc 8169639915646943137 2 IN IP4 127.0.0.1\r\n'+'s=-\r\n'+'t=0 0\r\n';};SDPUtils.writeMediaSection=function(transceiver,caps,type,stream){var sdp=SDPUtils.writeRtpDescription(transceiver.kind,caps);sdp+=SDPUtils.writeIceParameters(transceiver.iceGatherer.getLocalParameters());sdp+=SDPUtils.writeDtlsParameters(transceiver.dtlsTransport.getLocalParameters(),type==='offer'?'actpass':'active');sdp+='a=mid:'+transceiver.mid+'\r\n';if(transceiver.rtpSender&&transceiver.rtpReceiver){sdp+='a=sendrecv\r\n';}else if(transceiver.rtpSender){sdp+='a=sendonly\r\n';}else if(transceiver.rtpReceiver){sdp+='a=recvonly\r\n';}else{sdp+='a=inactive\r\n';} +if(transceiver.rtpSender){var msid='msid:'+stream.id+' '+ +transceiver.rtpSender.track.id+'\r\n';sdp+='a='+msid;sdp+='a=ssrc:'+transceiver.sendEncodingParameters[0].ssrc+' '+msid;} +sdp+='a=ssrc:'+transceiver.sendEncodingParameters[0].ssrc+' cname:'+SDPUtils.localCName+'\r\n';return sdp;};SDPUtils.getDirection=function(mediaSection,sessionpart){var lines=SDPUtils.splitLines(mediaSection);for(var i=0;i0&&typeof selector==='function'){return origGetStats(selector,successCallback);} +var fixChromeStats_=function(response){var standardReport={};var reports=response.result();reports.forEach(function(report){var standardStats={id:report.id,timestamp:report.timestamp,type:report.type};report.names().forEach(function(name){standardStats[name]=report.stat(name);});standardReport[standardStats.id]=standardStats;});return standardReport;};var makeMapStats=function(stats,legacyStats){var map=new Map(Object.keys(stats).map(function(key){return[key,stats[key]];}));legacyStats=legacyStats||stats;Object.keys(legacyStats).forEach(function(key){map[key]=legacyStats[key];});return map;};if(arguments.length>=2){var successCallbackWrapper_=function(response){args[1](makeMapStats(fixChromeStats_(response)));};return origGetStats.apply(this,[successCallbackWrapper_,arguments[0]]);} +return new Promise(function(resolve,reject){if(args.length===1&&typeof selector==='object'){origGetStats.apply(self,[function(response){resolve(makeMapStats(fixChromeStats_(response)));},reject]);}else{origGetStats.apply(self,[function(response){resolve(makeMapStats(fixChromeStats_(response),response.result()));},reject]);}}).then(successCallback,errorCallback);};return pc;};window.RTCPeerConnection.prototype=webkitRTCPeerConnection.prototype;if(webkitRTCPeerConnection.generateCertificate){Object.defineProperty(window.RTCPeerConnection,'generateCertificate',{get:function(){return webkitRTCPeerConnection.generateCertificate;}});} +['createOffer','createAnswer'].forEach(function(method){var nativeMethod=webkitRTCPeerConnection.prototype[method];webkitRTCPeerConnection.prototype[method]=function(){var self=this;if(arguments.length<1||(arguments.length===1&&typeof arguments[0]==='object')){var opts=arguments.length===1?arguments[0]:undefined;return new Promise(function(resolve,reject){nativeMethod.apply(self,[resolve,reject,opts]);});} +return nativeMethod.apply(this,arguments);};});if(browserDetails.version<51){['setLocalDescription','setRemoteDescription','addIceCandidate'].forEach(function(method){var nativeMethod=webkitRTCPeerConnection.prototype[method];webkitRTCPeerConnection.prototype[method]=function(){var args=arguments;var self=this;var promise=new Promise(function(resolve,reject){nativeMethod.apply(self,[args[0],resolve,reject]);});if(args.length<2){return promise;} +return promise.then(function(){args[1].apply(null,[]);},function(err){if(args.length>=3){args[2].apply(null,[err]);}});};});} +['setLocalDescription','setRemoteDescription','addIceCandidate'].forEach(function(method){var nativeMethod=webkitRTCPeerConnection.prototype[method];webkitRTCPeerConnection.prototype[method]=function(){arguments[0]=new((method==='addIceCandidate')?RTCIceCandidate:RTCSessionDescription)(arguments[0]);return nativeMethod.apply(this,arguments);};});var nativeAddIceCandidate=RTCPeerConnection.prototype.addIceCandidate;RTCPeerConnection.prototype.addIceCandidate=function(){return arguments[0]===null?Promise.resolve():nativeAddIceCandidate.apply(this,arguments);};}};module.exports={shimMediaStream:chromeShim.shimMediaStream,shimOnTrack:chromeShim.shimOnTrack,shimSourceObject:chromeShim.shimSourceObject,shimPeerConnection:chromeShim.shimPeerConnection,shimGetUserMedia:require('./getusermedia')};},{"../utils.js":10,"./getusermedia":4}],4:[function(require,module,exports){'use strict';var logging=require('../utils.js').log;module.exports=function(){var constraintsToChrome_=function(c){if(typeof c!=='object'||c.mandatory||c.optional){return c;} +var cc={};Object.keys(c).forEach(function(key){if(key==='require'||key==='advanced'||key==='mediaSource'){return;} +var r=(typeof c[key]==='object')?c[key]:{ideal:c[key]};if(r.exact!==undefined&&typeof r.exact==='number'){r.min=r.max=r.exact;} +var oldname_=function(prefix,name){if(prefix){return prefix+name.charAt(0).toUpperCase()+name.slice(1);} +return(name==='deviceId')?'sourceId':name;};if(r.ideal!==undefined){cc.optional=cc.optional||[];var oc={};if(typeof r.ideal==='number'){oc[oldname_('min',key)]=r.ideal;cc.optional.push(oc);oc={};oc[oldname_('max',key)]=r.ideal;cc.optional.push(oc);}else{oc[oldname_('',key)]=r.ideal;cc.optional.push(oc);}} +if(r.exact!==undefined&&typeof r.exact!=='number'){cc.mandatory=cc.mandatory||{};cc.mandatory[oldname_('',key)]=r.exact;}else{['min','max'].forEach(function(mix){if(r[mix]!==undefined){cc.mandatory=cc.mandatory||{};cc.mandatory[oldname_(mix,key)]=r[mix];}});}});if(c.advanced){cc.optional=(cc.optional||[]).concat(c.advanced);} +return cc;};var shimConstraints_=function(constraints,func){constraints=JSON.parse(JSON.stringify(constraints));if(constraints&&constraints.audio){constraints.audio=constraintsToChrome_(constraints.audio);} +if(constraints&&typeof constraints.video==='object'){var face=constraints.video.facingMode;face=face&&((typeof face==='object')?face:{ideal:face});if((face&&(face.exact==='user'||face.exact==='environment'||face.ideal==='user'||face.ideal==='environment'))&&!(navigator.mediaDevices.getSupportedConstraints&&navigator.mediaDevices.getSupportedConstraints().facingMode)){delete constraints.video.facingMode;if(face.exact==='environment'||face.ideal==='environment'){return navigator.mediaDevices.enumerateDevices().then(function(devices){devices=devices.filter(function(d){return d.kind==='videoinput';});var back=devices.find(function(d){return d.label.toLowerCase().indexOf('back')!==-1;})||(devices.length&&devices[devices.length-1]);if(back){constraints.video.deviceId=face.exact?{exact:back.deviceId}:{ideal:back.deviceId};} +constraints.video=constraintsToChrome_(constraints.video);logging('chrome: '+JSON.stringify(constraints));return func(constraints);});}} +constraints.video=constraintsToChrome_(constraints.video);} +logging('chrome: '+JSON.stringify(constraints));return func(constraints);};var shimError_=function(e){return{name:{PermissionDeniedError:'NotAllowedError',ConstraintNotSatisfiedError:'OverconstrainedError'}[e.name]||e.name,message:e.message,constraint:e.constraintName,toString:function(){return this.name+(this.message&&': ')+this.message;}};};var getUserMedia_=function(constraints,onSuccess,onError){shimConstraints_(constraints,function(c){navigator.webkitGetUserMedia(c,onSuccess,function(e){onError(shimError_(e));});});};navigator.getUserMedia=getUserMedia_;var getUserMediaPromise_=function(constraints){return new Promise(function(resolve,reject){navigator.getUserMedia(constraints,resolve,reject);});};if(!navigator.mediaDevices){navigator.mediaDevices={getUserMedia:getUserMediaPromise_,enumerateDevices:function(){return new Promise(function(resolve){var kinds={audio:'audioinput',video:'videoinput'};return MediaStreamTrack.getSources(function(devices){resolve(devices.map(function(device){return{label:device.label,kind:kinds[device.kind],deviceId:device.id,groupId:''};}));});});}};} +if(!navigator.mediaDevices.getUserMedia){navigator.mediaDevices.getUserMedia=function(constraints){return getUserMediaPromise_(constraints);};}else{var origGetUserMedia=navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);navigator.mediaDevices.getUserMedia=function(cs){return shimConstraints_(cs,function(c){return origGetUserMedia(c).catch(function(e){return Promise.reject(shimError_(e));});});};} +if(typeof navigator.mediaDevices.addEventListener==='undefined'){navigator.mediaDevices.addEventListener=function(){logging('Dummy mediaDevices.addEventListener called.');};} +if(typeof navigator.mediaDevices.removeEventListener==='undefined'){navigator.mediaDevices.removeEventListener=function(){logging('Dummy mediaDevices.removeEventListener called.');};}};},{"../utils.js":10}],5:[function(require,module,exports){'use strict';var SDPUtils=require('sdp');var edgeShim={shimPeerConnection:function(){if(window.RTCIceGatherer){if(!window.RTCIceCandidate){window.RTCIceCandidate=function(args){return args;};} +if(!window.RTCSessionDescription){window.RTCSessionDescription=function(args){return args;};}} +window.RTCPeerConnection=function(config){var self=this;var _eventTarget=document.createDocumentFragment();['addEventListener','removeEventListener','dispatchEvent'].forEach(function(method){self[method]=_eventTarget[method].bind(_eventTarget);});this.onicecandidate=null;this.onaddstream=null;this.ontrack=null;this.onremovestream=null;this.onsignalingstatechange=null;this.oniceconnectionstatechange=null;this.onnegotiationneeded=null;this.ondatachannel=null;this.localStreams=[];this.remoteStreams=[];this.getLocalStreams=function(){return self.localStreams;};this.getRemoteStreams=function(){return self.remoteStreams;};this.localDescription=new RTCSessionDescription({type:'',sdp:''});this.remoteDescription=new RTCSessionDescription({type:'',sdp:''});this.signalingState='stable';this.iceConnectionState='new';this.iceGatheringState='new';this.iceOptions={gatherPolicy:'all',iceServers:[]};if(config&&config.iceTransportPolicy){switch(config.iceTransportPolicy){case'all':case'relay':this.iceOptions.gatherPolicy=config.iceTransportPolicy;break;case'none':throw new TypeError('iceTransportPolicy "none" not supported');default:break;}} +this.usingBundle=config&&config.bundlePolicy==='max-bundle';if(config&&config.iceServers){var iceServers=JSON.parse(JSON.stringify(config.iceServers));this.iceOptions.iceServers=iceServers.filter(function(server){if(server&&server.urls){var urls=server.urls;if(typeof urls==='string'){urls=[urls];} +urls=urls.filter(function(url){return url.indexOf('turn:')===0&&url.indexOf('transport=udp')!==-1&&url.indexOf('turn:[')===-1;})[0];return!!urls;} +return false;});} +this.transceivers=[];this._localIceCandidatesBuffer=[];};window.RTCPeerConnection.prototype._emitBufferedCandidates=function(){var self=this;var sections=SDPUtils.splitSections(self.localDescription.sdp);this._localIceCandidatesBuffer.forEach(function(event){var end=!event.candidate||Object.keys(event.candidate).length===0;if(end){for(var j=1;j-1){this.localStreams.splice(idx,1);this._maybeFireNegotiationNeeded();}};window.RTCPeerConnection.prototype.getSenders=function(){return this.transceivers.filter(function(transceiver){return!!transceiver.rtpSender;}).map(function(transceiver){return transceiver.rtpSender;});};window.RTCPeerConnection.prototype.getReceivers=function(){return this.transceivers.filter(function(transceiver){return!!transceiver.rtpReceiver;}).map(function(transceiver){return transceiver.rtpReceiver;});};window.RTCPeerConnection.prototype._getCommonCapabilities=function(localCapabilities,remoteCapabilities){var commonCapabilities={codecs:[],headerExtensions:[],fecMechanisms:[]};localCapabilities.codecs.forEach(function(lCodec){for(var i=0;i0;sections.forEach(function(mediaSection,sdpMLineIndex){var transceiver=self.transceivers[sdpMLineIndex];var iceGatherer=transceiver.iceGatherer;var iceTransport=transceiver.iceTransport;var dtlsTransport=transceiver.dtlsTransport;var localCapabilities=transceiver.localCapabilities;var remoteCapabilities=transceiver.remoteCapabilities;var rejected=mediaSection.split('\n',1)[0].split(' ',2)[1]==='0';if(!rejected){var remoteIceParameters=SDPUtils.getIceParameters(mediaSection,sessionpart);if(isIceLite){var cands=SDPUtils.matchPrefix(mediaSection,'a=candidate:').map(function(cand){return SDPUtils.parseCandidate(cand);}).filter(function(cand){return cand.component==='1';});if(cands.length){iceTransport.setRemoteCandidates(cands);}} +var remoteDtlsParameters=SDPUtils.getDtlsParameters(mediaSection,sessionpart);if(isIceLite){remoteDtlsParameters.role='server';} +if(!self.usingBundle||sdpMLineIndex===0){iceTransport.start(iceGatherer,remoteIceParameters,isIceLite?'controlling':'controlled');dtlsTransport.start(remoteDtlsParameters);} +var params=self._getCommonCapabilities(localCapabilities,remoteCapabilities);self._transceive(transceiver,params.codecs.length>0,false);}});} +this.localDescription={type:description.type,sdp:description.sdp};switch(description.type){case'offer':this._updateSignalingState('have-local-offer');break;case'answer':this._updateSignalingState('stable');break;default:throw new TypeError('unsupported type "'+description.type+'"');} +var hasCallback=arguments.length>1&&typeof arguments[1]==='function';if(hasCallback){var cb=arguments[1];window.setTimeout(function(){cb();if(self.iceGatheringState==='new'){self.iceGatheringState='gathering';} +self._emitBufferedCandidates();},0);} +var p=Promise.resolve();p.then(function(){if(!hasCallback){if(self.iceGatheringState==='new'){self.iceGatheringState='gathering';} +window.setTimeout(self._emitBufferedCandidates.bind(self),500);}});return p;};window.RTCPeerConnection.prototype.setRemoteDescription=function(description){var self=this;var stream=new MediaStream();var receiverList=[];var sections=SDPUtils.splitSections(description.sdp);var sessionpart=sections.shift();var isIceLite=SDPUtils.matchPrefix(sessionpart,'a=ice-lite').length>0;this.usingBundle=SDPUtils.matchPrefix(sessionpart,'a=group:BUNDLE ').length>0;sections.forEach(function(mediaSection,sdpMLineIndex){var lines=SDPUtils.splitLines(mediaSection);var mline=lines[0].substr(2).split(' ');var kind=mline[0];var rejected=mline[1]==='0';var direction=SDPUtils.getDirection(mediaSection,sessionpart);var transceiver;var iceGatherer;var iceTransport;var dtlsTransport;var rtpSender;var rtpReceiver;var sendEncodingParameters;var recvEncodingParameters;var localCapabilities;var track;var remoteCapabilities=SDPUtils.parseRtpParameters(mediaSection);var remoteIceParameters;var remoteDtlsParameters;if(!rejected){remoteIceParameters=SDPUtils.getIceParameters(mediaSection,sessionpart);remoteDtlsParameters=SDPUtils.getDtlsParameters(mediaSection,sessionpart);remoteDtlsParameters.role='client';} +recvEncodingParameters=SDPUtils.parseRtpEncodingParameters(mediaSection);var mid=SDPUtils.matchPrefix(mediaSection,'a=mid:');if(mid.length){mid=mid[0].substr(6);}else{mid=SDPUtils.generateIdentifier();} +var cname;var remoteSsrc=SDPUtils.matchPrefix(mediaSection,'a=ssrc:').map(function(line){return SDPUtils.parseSsrcMedia(line);}).filter(function(obj){return obj.attribute==='cname';})[0];if(remoteSsrc){cname=remoteSsrc.value;} +var isComplete=SDPUtils.matchPrefix(mediaSection,'a=end-of-candidates',sessionpart).length>0;var cands=SDPUtils.matchPrefix(mediaSection,'a=candidate:').map(function(cand){return SDPUtils.parseCandidate(cand);}).filter(function(cand){return cand.component==='1';});if(description.type==='offer'&&!rejected){var transports=self.usingBundle&&sdpMLineIndex>0?{iceGatherer:self.transceivers[0].iceGatherer,iceTransport:self.transceivers[0].iceTransport,dtlsTransport:self.transceivers[0].dtlsTransport}:self._createIceAndDtlsTransports(mid,sdpMLineIndex);if(isComplete){transports.iceTransport.setRemoteCandidates(cands);} +localCapabilities=RTCRtpReceiver.getCapabilities(kind);sendEncodingParameters=[{ssrc:(2*sdpMLineIndex+2)*1001}];rtpReceiver=new RTCRtpReceiver(transports.dtlsTransport,kind);track=rtpReceiver.track;receiverList.push([track,rtpReceiver]);stream.addTrack(track);if(self.localStreams.length>0&&self.localStreams[0].getTracks().length>=sdpMLineIndex){var localTrack;if(kind==='audio'){localTrack=self.localStreams[0].getAudioTracks()[0];}else if(kind==='video'){localTrack=self.localStreams[0].getVideoTracks()[0];} +if(localTrack){rtpSender=new RTCRtpSender(localTrack,transports.dtlsTransport);}} +self.transceivers[sdpMLineIndex]={iceGatherer:transports.iceGatherer,iceTransport:transports.iceTransport,dtlsTransport:transports.dtlsTransport,localCapabilities:localCapabilities,remoteCapabilities:remoteCapabilities,rtpSender:rtpSender,rtpReceiver:rtpReceiver,kind:kind,mid:mid,cname:cname,sendEncodingParameters:sendEncodingParameters,recvEncodingParameters:recvEncodingParameters};self._transceive(self.transceivers[sdpMLineIndex],false,direction==='sendrecv'||direction==='sendonly');}else if(description.type==='answer'&&!rejected){transceiver=self.transceivers[sdpMLineIndex];iceGatherer=transceiver.iceGatherer;iceTransport=transceiver.iceTransport;dtlsTransport=transceiver.dtlsTransport;rtpSender=transceiver.rtpSender;rtpReceiver=transceiver.rtpReceiver;sendEncodingParameters=transceiver.sendEncodingParameters;localCapabilities=transceiver.localCapabilities;self.transceivers[sdpMLineIndex].recvEncodingParameters=recvEncodingParameters;self.transceivers[sdpMLineIndex].remoteCapabilities=remoteCapabilities;self.transceivers[sdpMLineIndex].cname=cname;if((isIceLite||isComplete)&&cands.length){iceTransport.setRemoteCandidates(cands);} +if(!self.usingBundle||sdpMLineIndex===0){iceTransport.start(iceGatherer,remoteIceParameters,'controlling');dtlsTransport.start(remoteDtlsParameters);} +self._transceive(transceiver,direction==='sendrecv'||direction==='recvonly',direction==='sendrecv'||direction==='sendonly');if(rtpReceiver&&(direction==='sendrecv'||direction==='sendonly')){track=rtpReceiver.track;receiverList.push([track,rtpReceiver]);stream.addTrack(track);}else{delete transceiver.rtpReceiver;}}});this.remoteDescription={type:description.type,sdp:description.sdp};switch(description.type){case'offer':this._updateSignalingState('have-remote-offer');break;case'answer':this._updateSignalingState('stable');break;default:throw new TypeError('unsupported type "'+description.type+'"');} +if(stream.getTracks().length){self.remoteStreams.push(stream);window.setTimeout(function(){var event=new Event('addstream');event.stream=stream;self.dispatchEvent(event);if(self.onaddstream!==null){window.setTimeout(function(){self.onaddstream(event);},0);} +receiverList.forEach(function(item){var track=item[0];var receiver=item[1];var trackEvent=new Event('track');trackEvent.track=track;trackEvent.receiver=receiver;trackEvent.streams=[stream];self.dispatchEvent(event);if(self.ontrack!==null){window.setTimeout(function(){self.ontrack(trackEvent);},0);}});},0);} +if(arguments.length>1&&typeof arguments[1]==='function'){window.setTimeout(arguments[1],0);} +return Promise.resolve();};window.RTCPeerConnection.prototype.close=function(){this.transceivers.forEach(function(transceiver){if(transceiver.iceTransport){transceiver.iceTransport.stop();} +if(transceiver.dtlsTransport){transceiver.dtlsTransport.stop();} +if(transceiver.rtpSender){transceiver.rtpSender.stop();} +if(transceiver.rtpReceiver){transceiver.rtpReceiver.stop();}});this._updateSignalingState('closed');};window.RTCPeerConnection.prototype._updateSignalingState=function(newState){this.signalingState=newState;var event=new Event('signalingstatechange');this.dispatchEvent(event);if(this.onsignalingstatechange!==null){this.onsignalingstatechange(event);}};window.RTCPeerConnection.prototype._maybeFireNegotiationNeeded=function(){var event=new Event('negotiationneeded');this.dispatchEvent(event);if(this.onnegotiationneeded!==null){this.onnegotiationneeded(event);}};window.RTCPeerConnection.prototype._updateConnectionState=function(){var self=this;var newState;var states={'new':0,closed:0,connecting:0,checking:0,connected:0,completed:0,failed:0};this.transceivers.forEach(function(transceiver){states[transceiver.iceTransport.state]++;states[transceiver.dtlsTransport.state]++;});states.connected+=states.completed;newState='new';if(states.failed>0){newState='failed';}else if(states.connecting>0||states.checking>0){newState='connecting';}else if(states.disconnected>0){newState='disconnected';}else if(states.new>0){newState='new';}else if(states.connected>0||states.completed>0){newState='connected';} +if(newState!==self.iceConnectionState){self.iceConnectionState=newState;var event=new Event('iceconnectionstatechange');this.dispatchEvent(event);if(this.oniceconnectionstatechange!==null){this.oniceconnectionstatechange(event);}}};window.RTCPeerConnection.prototype.createOffer=function(){var self=this;if(this._pendingOffer){throw new Error('createOffer called while there is a pending offer.');} +var offerOptions;if(arguments.length===1&&typeof arguments[0]!=='function'){offerOptions=arguments[0];}else if(arguments.length===3){offerOptions=arguments[2];} +var tracks=[];var numAudioTracks=0;var numVideoTracks=0;if(this.localStreams.length){numAudioTracks=this.localStreams[0].getAudioTracks().length;numVideoTracks=this.localStreams[0].getVideoTracks().length;} +if(offerOptions){if(offerOptions.mandatory||offerOptions.optional){throw new TypeError('Legacy mandatory/optional constraints not supported.');} +if(offerOptions.offerToReceiveAudio!==undefined){numAudioTracks=offerOptions.offerToReceiveAudio;} +if(offerOptions.offerToReceiveVideo!==undefined){numVideoTracks=offerOptions.offerToReceiveVideo;}} +if(this.localStreams.length){this.localStreams[0].getTracks().forEach(function(track){tracks.push({kind:track.kind,track:track,wantReceive:track.kind==='audio'?numAudioTracks>0:numVideoTracks>0});if(track.kind==='audio'){numAudioTracks--;}else if(track.kind==='video'){numVideoTracks--;}});} +while(numAudioTracks>0||numVideoTracks>0){if(numAudioTracks>0){tracks.push({kind:'audio',wantReceive:true});numAudioTracks--;} +if(numVideoTracks>0){tracks.push({kind:'video',wantReceive:true});numVideoTracks--;}} +var sdp=SDPUtils.writeSessionBoilerplate();var transceivers=[];tracks.forEach(function(mline,sdpMLineIndex){var track=mline.track;var kind=mline.kind;var mid=SDPUtils.generateIdentifier();var transports=self.usingBundle&&sdpMLineIndex>0?{iceGatherer:transceivers[0].iceGatherer,iceTransport:transceivers[0].iceTransport,dtlsTransport:transceivers[0].dtlsTransport}:self._createIceAndDtlsTransports(mid,sdpMLineIndex);var localCapabilities=RTCRtpSender.getCapabilities(kind);var rtpSender;var rtpReceiver;var sendEncodingParameters=[{ssrc:(2*sdpMLineIndex+1)*1001}];if(track){rtpSender=new RTCRtpSender(track,transports.dtlsTransport);} +if(mline.wantReceive){rtpReceiver=new RTCRtpReceiver(transports.dtlsTransport,kind);} +transceivers[sdpMLineIndex]={iceGatherer:transports.iceGatherer,iceTransport:transports.iceTransport,dtlsTransport:transports.dtlsTransport,localCapabilities:localCapabilities,remoteCapabilities:null,rtpSender:rtpSender,rtpReceiver:rtpReceiver,kind:kind,mid:mid,sendEncodingParameters:sendEncodingParameters,recvEncodingParameters:null};});if(this.usingBundle){sdp+='a=group:BUNDLE '+transceivers.map(function(t){return t.mid;}).join(' ')+'\r\n';} +tracks.forEach(function(mline,sdpMLineIndex){var transceiver=transceivers[sdpMLineIndex];sdp+=SDPUtils.writeMediaSection(transceiver,transceiver.localCapabilities,'offer',self.localStreams[0]);});this._pendingOffer=transceivers;var desc=new RTCSessionDescription({type:'offer',sdp:sdp});if(arguments.length&&typeof arguments[0]==='function'){window.setTimeout(arguments[0],0,desc);} +return Promise.resolve(desc);};window.RTCPeerConnection.prototype.createAnswer=function(){var self=this;var sdp=SDPUtils.writeSessionBoilerplate();if(this.usingBundle){sdp+='a=group:BUNDLE '+this.transceivers.map(function(t){return t.mid;}).join(' ')+'\r\n';} +this.transceivers.forEach(function(transceiver){var commonCapabilities=self._getCommonCapabilities(transceiver.localCapabilities,transceiver.remoteCapabilities);sdp+=SDPUtils.writeMediaSection(transceiver,commonCapabilities,'answer',self.localStreams[0]);});var desc=new RTCSessionDescription({type:'answer',sdp:sdp});if(arguments.length&&typeof arguments[0]==='function'){window.setTimeout(arguments[0],0,desc);} +return Promise.resolve(desc);};window.RTCPeerConnection.prototype.addIceCandidate=function(candidate){if(candidate===null){this.transceivers.forEach(function(transceiver){transceiver.iceTransport.addRemoteCandidate({});});}else{var mLineIndex=candidate.sdpMLineIndex;if(candidate.sdpMid){for(var i=0;i0?SDPUtils.parseCandidate(candidate.candidate):{};if(cand.protocol==='tcp'&&(cand.port===0||cand.port===9)){return;} +if(cand.component!=='1'){return;} +if(cand.type==='endOfCandidates'){cand={};} +transceiver.iceTransport.addRemoteCandidate(cand);var sections=SDPUtils.splitSections(this.remoteDescription.sdp);sections[mLineIndex+1]+=(cand.type?candidate.candidate.trim():'a=end-of-candidates')+'\r\n';this.remoteDescription.sdp=sections.join('');}} +if(arguments.length>1&&typeof arguments[1]==='function'){window.setTimeout(arguments[1],0);} +return Promise.resolve();};window.RTCPeerConnection.prototype.getStats=function(){var promises=[];this.transceivers.forEach(function(transceiver){['rtpSender','rtpReceiver','iceGatherer','iceTransport','dtlsTransport'].forEach(function(method){if(transceiver[method]){promises.push(transceiver[method].getStats());}});});var cb=arguments.length>1&&typeof arguments[1]==='function'&&arguments[1];return new Promise(function(resolve){var results=new Map();Promise.all(promises).then(function(res){res.forEach(function(result){Object.keys(result).forEach(function(id){results.set(id,result[id]);results[id]=result[id];});});if(cb){window.setTimeout(cb,0,results);} +resolve(results);});});};}};module.exports={shimPeerConnection:edgeShim.shimPeerConnection,shimGetUserMedia:require('./getusermedia')};},{"./getusermedia":6,"sdp":1}],6:[function(require,module,exports){'use strict';module.exports=function(){var shimError_=function(e){return{name:{PermissionDeniedError:'NotAllowedError'}[e.name]||e.name,message:e.message,constraint:e.constraint,toString:function(){return this.name;}};};var origGetUserMedia=navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);navigator.mediaDevices.getUserMedia=function(c){return origGetUserMedia(c).catch(function(e){return Promise.reject(shimError_(e));});};};},{}],7:[function(require,module,exports){'use strict';var browserDetails=require('../utils').browserDetails;var firefoxShim={shimOnTrack:function(){if(typeof window==='object'&&window.RTCPeerConnection&&!('ontrack'in +window.RTCPeerConnection.prototype)){Object.defineProperty(window.RTCPeerConnection.prototype,'ontrack',{get:function(){return this._ontrack;},set:function(f){if(this._ontrack){this.removeEventListener('track',this._ontrack);this.removeEventListener('addstream',this._ontrackpoly);} +this.addEventListener('track',this._ontrack=f);this.addEventListener('addstream',this._ontrackpoly=function(e){e.stream.getTracks().forEach(function(track){var event=new Event('track');event.track=track;event.receiver={track:track};event.streams=[e.stream];this.dispatchEvent(event);}.bind(this));}.bind(this));}});}},shimSourceObject:function(){if(typeof window==='object'){if(window.HTMLMediaElement&&!('srcObject'in window.HTMLMediaElement.prototype)){Object.defineProperty(window.HTMLMediaElement.prototype,'srcObject',{get:function(){return this.mozSrcObject;},set:function(stream){this.mozSrcObject=stream;}});}}},shimPeerConnection:function(){if(typeof window!=='object'||!(window.RTCPeerConnection||window.mozRTCPeerConnection)){return;} +if(!window.RTCPeerConnection){window.RTCPeerConnection=function(pcConfig,pcConstraints){if(browserDetails.version<38){if(pcConfig&&pcConfig.iceServers){var newIceServers=[];for(var i=0;i=pos&&parseInt(match[pos],10);},detectBrowser:function(){var result={};result.browser=null;result.version=null;if(typeof window==='undefined'||!window.navigator){result.browser='Not a browser.';return result;} +if(navigator.mozGetUserMedia){result.browser='firefox';result.version=this.extractVersion(navigator.userAgent,/Firefox\/([0-9]+)\./,1);}else if(navigator.webkitGetUserMedia){if(window.webkitRTCPeerConnection){result.browser='chrome';result.version=this.extractVersion(navigator.userAgent,/Chrom(e|ium)\/([0-9]+)\./,2);}else{if(navigator.userAgent.match(/Version\/(\d+).(\d+)/)){result.browser='safari';result.version=this.extractVersion(navigator.userAgent,/AppleWebKit\/([0-9]+)\./,1);}else{result.browser='Unsupported webkit-based browser '+'with GUM support but no WebRTC support.';return result;}}}else if(navigator.mediaDevices&&navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)){result.browser='edge';result.version=this.extractVersion(navigator.userAgent,/Edge\/(\d+).(\d+)$/,2);}else{result.browser='Not a supported browser.';return result;} +return result;}};module.exports={log:utils.log,disableLog:utils.disableLog,browserDetails:utils.detectBrowser(),extractVersion:utils.extractVersion};},{}]},{},[2])(2)});AdapterJS.parseWebrtcDetectedBrowser();if(navigator.mozGetUserMedia){MediaStreamTrack.getSources=function(successCb){setTimeout(function(){var infos=[{kind:'audio',id:'default',label:'',facing:''},{kind:'video',id:'default',label:'',facing:''}];successCb(infos);},0);};createIceServer=function(url,username,password){console.warn('createIceServer is deprecated. It should be replaced with an application level implementation.');var iceServer=null;var urlParts=url.split(':');if(urlParts[0].indexOf('stun')===0){iceServer={urls:[url]};}else if(urlParts[0].indexOf('turn')===0){if(webrtcDetectedVersion<27){var turnUrlParts=url.split('?');if(turnUrlParts.length===1||turnUrlParts[1].indexOf('transport=udp')===0){iceServer={urls:[turnUrlParts[0]],credential:password,username:username};}}else{iceServer={urls:[url],credential:password,username:username};}} +return iceServer;};createIceServers=function(urls,username,password){console.warn('createIceServers is deprecated. It should be replaced with an application level implementation.');var iceServers=[];for(i=0;i=34){iceServers={'urls':urls,'credential':password,'username':username};}else{for(i=0;i'+' '+' '+' '+''+''+ +(AdapterJS.options.getAllCams?'':'')+'
';while(AdapterJS.WebRTCPlugin.plugin.firstChild){frag.appendChild(AdapterJS.WebRTCPlugin.plugin.firstChild);} +document.body.appendChild(frag);AdapterJS.WebRTCPlugin.plugin=document.getElementById(AdapterJS.WebRTCPlugin.pluginInfo.pluginId);}else{AdapterJS.WebRTCPlugin.plugin=document.createElement('object');AdapterJS.WebRTCPlugin.plugin.id=AdapterJS.WebRTCPlugin.pluginInfo.pluginId;if(isIE){AdapterJS.WebRTCPlugin.plugin.width='1px';AdapterJS.WebRTCPlugin.plugin.height='1px';}else{AdapterJS.WebRTCPlugin.plugin.width='0px';AdapterJS.WebRTCPlugin.plugin.height='0px';} +AdapterJS.WebRTCPlugin.plugin.type=AdapterJS.WebRTCPlugin.pluginInfo.type;AdapterJS.WebRTCPlugin.plugin.innerHTML=''+''+' '+ +(AdapterJS.options.getAllCams?'':'')+''+'';document.body.appendChild(AdapterJS.WebRTCPlugin.plugin);} +AdapterJS.WebRTCPlugin.pluginState=AdapterJS.WebRTCPlugin.PLUGIN_STATES.INJECTED;};AdapterJS.WebRTCPlugin.isPluginInstalled=function(comName,plugName,installedCb,notInstalledCb){if(!isIE){var pluginArray=navigator.plugins;for(var i=0;i=0){installedCb();return;}} +notInstalledCb();}else{try{var axo=new ActiveXObject(comName+'.'+plugName);}catch(e){notInstalledCb();return;} +installedCb();}};AdapterJS.WebRTCPlugin.defineWebRTCInterface=function(){if(AdapterJS.WebRTCPlugin.pluginState===AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY){console.error('AdapterJS - WebRTC interface has already been defined');return;} +AdapterJS.WebRTCPlugin.pluginState=AdapterJS.WebRTCPlugin.PLUGIN_STATES.INITIALIZING;AdapterJS.isDefined=function(variable){return variable!==null&&variable!==undefined;};createIceServer=function(url,username,password){var iceServer=null;var urlParts=url.split(':');if(urlParts[0].indexOf('stun')===0){iceServer={'url':url,'hasCredentials':false};}else if(urlParts[0].indexOf('turn')===0){iceServer={'url':url,'hasCredentials':true,'credential':password,'username':username};} +return iceServer;};createIceServers=function(urls,username,password){var iceServers=[];for(var i=0;i1){if(iceServers){servers.iceServers=iceServers;} +return AdapterJS.WebRTCPlugin.plugin.PeerConnection(servers);}else{var mandatory=(constraints&&constraints.mandatory)?constraints.mandatory:null;var optional=(constraints&&constraints.optional)?constraints.optional:null;return AdapterJS.WebRTCPlugin.plugin.PeerConnection(AdapterJS.WebRTCPlugin.pageId,iceServers,mandatory,optional);}};MediaStreamTrack=function(){};MediaStreamTrack.getSources=function(callback){AdapterJS.WebRTCPlugin.callWhenPluginReady(function(){AdapterJS.WebRTCPlugin.plugin.GetSources(callback);});};getUserMedia=function(constraints,successCallback,failureCallback){constraints.audio=constraints.audio||false;constraints.video=constraints.video||false;AdapterJS.WebRTCPlugin.callWhenPluginReady(function(){AdapterJS.WebRTCPlugin.plugin.getUserMedia(constraints,successCallback,failureCallback);});};window.navigator.getUserMedia=getUserMedia;if(!navigator.mediaDevices&&typeof Promise!=='undefined'){requestUserMedia=function(constraints){return new Promise(function(resolve,reject){getUserMedia(constraints,resolve,reject);});};navigator.mediaDevices={getUserMedia:requestUserMedia,enumerateDevices:function(){return new Promise(function(resolve){var kinds={audio:'audioinput',video:'videoinput'};return MediaStreamTrack.getSources(function(devices){resolve(devices.map(function(device){return{label:device.label,kind:kinds[device.kind],id:device.id,deviceId:device.id,groupId:''};}));});});}};} +attachMediaStream=function(element,stream){if(!element||!element.parentNode){return;} +var streamId;if(stream===null){streamId='';}else{if(typeof stream.enableSoundTracks!=='undefined'){stream.enableSoundTracks(true);} +streamId=stream.id;} +var elementId=element.id.length===0?Math.random().toString(36).slice(2):element.id;var nodeName=element.nodeName.toLowerCase();if(nodeName!=='object'){var tag;switch(nodeName){case'audio':tag=AdapterJS.WebRTCPlugin.TAGS.AUDIO;break;case'video':tag=AdapterJS.WebRTCPlugin.TAGS.VIDEO;break;default:tag=AdapterJS.WebRTCPlugin.TAGS.NONE;} +var frag=document.createDocumentFragment();var temp=document.createElement('div');var classHTML='';if(element.className){classHTML='class="'+element.className+'" ';}else if(element.attributes&&element.attributes['class']){classHTML='class="'+element.attributes['class'].value+'" ';} +temp.innerHTML=''+' '+' '+' '+' '+' '+'';while(temp.firstChild){frag.appendChild(temp.firstChild);} +var height='';var width='';if(element.clientWidth||element.clientHeight){width=element.clientWidth;height=element.clientHeight;} +else if(element.width||element.height){width=element.width;height=element.height;} +element.parentNode.insertBefore(frag,element);frag=document.getElementById(elementId);frag.width=width;frag.height=height;element.parentNode.removeChild(element);}else{var children=element.children;for(var i=0;i!==children.length;++i){if(children[i].name==='streamId'){children[i].value=streamId;break;}} +element.setStreamId(streamId);} +var newElement=document.getElementById(elementId);AdapterJS.forwardEventHandlers(newElement,element,Object.getPrototypeOf(element));return newElement;};reattachMediaStream=function(to,from){var stream=null;var children=from.children;for(var i=0;i!==children.length;++i){if(children[i].name==='streamId'){AdapterJS.WebRTCPlugin.WaitForPluginReady();stream=AdapterJS.WebRTCPlugin.plugin.getStreamWithId(AdapterJS.WebRTCPlugin.pageId,children[i].value);break;}} +if(stream!==null){return attachMediaStream(to,stream);}else{console.log('Could not find the stream associated with this element');}};window.attachMediaStream=attachMediaStream;window.reattachMediaStream=reattachMediaStream;window.getUserMedia=getUserMedia;AdapterJS.attachMediaStream=attachMediaStream;AdapterJS.reattachMediaStream=reattachMediaStream;AdapterJS.getUserMedia=getUserMedia;AdapterJS.forwardEventHandlers=function(destElem,srcElem,prototype){properties=Object.getOwnPropertyNames(prototype);for(var prop in properties){if(prop){propName=properties[prop];if(typeof propName.slice==='function'&&propName.slice(0,2)==='on'&&typeof srcElem[propName]==='function'){AdapterJS.addEvent(destElem,propName.slice(2),srcElem[propName]);}}} +var subPrototype=Object.getPrototypeOf(prototype);if(!!subPrototype){AdapterJS.forwardEventHandlers(destElem,srcElem,subPrototype);}};RTCIceCandidate=function(candidate){if(!candidate.sdpMid){candidate.sdpMid='';} +AdapterJS.WebRTCPlugin.WaitForPluginReady();return AdapterJS.WebRTCPlugin.plugin.ConstructIceCandidate(candidate.sdpMid,candidate.sdpMLineIndex,candidate.candidate);};AdapterJS.addEvent(document,'readystatechange',AdapterJS.WebRTCPlugin.injectPlugin);AdapterJS.WebRTCPlugin.injectPlugin();};AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCb=AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCb||function(){AdapterJS.addEvent(document,'readystatechange',AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCbPriv);AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCbPriv();};AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCbPriv=function(){if(AdapterJS.options.hidePluginInstallPrompt){return;} +var downloadLink=AdapterJS.WebRTCPlugin.pluginInfo.downloadLink;if(downloadLink){var popupString;if(AdapterJS.WebRTCPlugin.pluginInfo.portalLink){popupString='This website requires you to install the '+' '+AdapterJS.WebRTCPlugin.pluginInfo.companyName+' WebRTC Plugin'+' to work on this browser.';}else{popupString=AdapterJS.TEXT.PLUGIN.REQUIRE_INSTALLATION;} +AdapterJS.renderNotificationBar(popupString,AdapterJS.TEXT.PLUGIN.BUTTON,downloadLink);}else{AdapterJS.renderNotificationBar(AdapterJS.TEXT.PLUGIN.NOT_SUPPORTED);}};AdapterJS.WebRTCPlugin.isPluginInstalled(AdapterJS.WebRTCPlugin.pluginInfo.prefix,AdapterJS.WebRTCPlugin.pluginInfo.plugName,AdapterJS.WebRTCPlugin.defineWebRTCInterface,AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCb);} \ No newline at end of file diff --git a/html5/verto/video_demo/verto.js b/html5/verto/video_demo/verto.js index e0ee4aec19..7b6adc13b5 100644 --- a/html5/verto/video_demo/verto.js +++ b/html5/verto/video_demo/verto.js @@ -101,14 +101,14 @@ function full_screen(name) { } $("#" + video_screen).resize(function(e) { - console.log("video size changed to " + $("#" + video_screen).width() + "x" + $("#" + video_screen).height()); + //console.log("video size changed to " + $("#" + video_screen).width() + "x" + $("#" + video_screen).height()); - if ($("#" + video_screen).width() > $(window).width()) { + //if ($("#" + video_screen).width() > $(window).width()) { //resize(false); - $("#" + video_screen).width("100%"); - $("#" + video_screen).height("100%"); - } - + //$("#" + video_screen).width("100%"); + //$("#" + video_screen).height("100%"); + //} + real_size(); }); @@ -128,13 +128,41 @@ function resize(up) { } + +$( window ).resize(function() { + real_size(); +}); + function real_size() { + - $("#" + video_screen).width(""); - $("#" + video_screen).height(""); + /* temasys hack */ + setTimeout(function() { + $("#" + video_screen).width(""); + $("#" + video_screen).height(""); + + var w = $("#" + video_screen).width(); + var h = $("#" + video_screen).height(); + + var new_w; + var new_h; + var aspect = 1920 / 1080; /*temasys doesn't provide video width hack aspect to wide screen*/ + + if (w > h) { + new_w = window.innerWidth; + new_h = Math.round(window.innerWidth / aspect); + } else { + new_h = window.innerHeight; + new_w = Math.round(window.innerHeight / aspect); + } + + $("#" + video_screen).width(new_w); + $("#" + video_screen).height(new_h); + }, 500); + + console.log("video size changed to fit screen"); - console.log("video size changed to natural default"); } @@ -202,8 +230,19 @@ function check_vid() { return use_vid; } +var DISABLE_SPEED_TEST = true; + function do_speed_test(fn) { + + + if (DISABLE_SPEED_TEST) { + if (fn) { + fn(); + } + return; + } + goto_page("bwtest"); vertoHandle.rpcClient.speedTest(1024 * 256, function(e, obj) { @@ -486,6 +525,7 @@ var callbacks = { check_vid_res(); $("#ansbtn").click(function() { + console.error("WTF", cur_call, d); cur_call.answer({ useStereo: $("#use_stereo").is(':checked'), callee_id_name: $("#cidname").val(), @@ -548,7 +588,7 @@ var callbacks = { } goto_page("incall"); - + real_size(); break; case $.verto.enum.state.hangup: $("#main_info").html("Call ended with cause: " + d.cause); diff --git a/src/switch_core_media.c b/src/switch_core_media.c index 5fb25f5772..1c27253ca9 100644 --- a/src/switch_core_media.c +++ b/src/switch_core_media.c @@ -8074,7 +8074,7 @@ SWITCH_DECLARE(void) switch_core_media_gen_local_sdp(switch_core_session_t *sess if (switch_channel_test_flag(smh->session->channel, CF_ICE)) { gen_ice(session, SWITCH_MEDIA_TYPE_AUDIO, ip, port); - switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=msid-semantic: WMS %s\r\na=end-of-candidates\r\n", smh->msid); + switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=msid-semantic: WMS %s\r\n", smh->msid); } if (a_engine->codec_negotiated) { @@ -8267,6 +8267,7 @@ SWITCH_DECLARE(void) switch_core_media_gen_local_sdp(switch_core_session_t *sess } } + switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=end-of-candidates\r\n"); switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=ssrc:%u cname:%s\r\n", a_engine->ssrc, smh->cname); switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=ssrc:%u msid:%s a0\r\n", a_engine->ssrc, smh->msid); @@ -8804,6 +8805,8 @@ SWITCH_DECLARE(void) switch_core_media_gen_local_sdp(switch_core_session_t *sess #ifdef GOOGLE_ICE switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=ice-options:google-ice\r\n"); #endif + switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=end-of-candidates\r\n"); + } diff --git a/src/switch_rtp.c b/src/switch_rtp.c index 697c302d4a..944d9e9087 100644 --- a/src/switch_rtp.c +++ b/src/switch_rtp.c @@ -3192,7 +3192,7 @@ static int do_dtls(switch_rtp_t *rtp_session, switch_dtls_t *dtls) ret = SSL_get_error(dtls->ssl, ret); switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(rtp_session->session), SWITCH_LOG_ERROR, "%s DTLS packet read err %d\n", rtp_type(rtp_session), ret); } - + if (dtls_states[dtls->state]) { r = dtls_states[dtls->state](rtp_session, dtls); } @@ -3396,8 +3396,11 @@ SWITCH_DECLARE(switch_status_t) switch_rtp_add_dtls(switch_rtp_t *rtp_session, d //SSL_CTX_set_verify(dtls->ssl_ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); SSL_CTX_set_verify(dtls->ssl_ctx, SSL_VERIFY_NONE, NULL); - SSL_CTX_set_cipher_list(dtls->ssl_ctx, "ALL"); - + SSL_CTX_set_cipher_list(dtls->ssl_ctx, "ECDH:!RC4:!SSLv3:RSA_WITH_AES_128_CBC_SHA"); + //SSL_CTX_set_cipher_list(dtls->ssl_ctx, "ECDHE-RSA-AES256-GCM-SHA384"); + //SSL_CTX_set_cipher_list(dtls->ssl_ctx, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH"); + //SSL_CTX_set_cipher_list(dtls->ssl_ctx, "SUITEB128"); + SSL_CTX_set_read_ahead(dtls->ssl_ctx, 1); #ifdef HAVE_OPENSSL_DTLS_SRTP //SSL_CTX_set_tlsext_use_srtp(dtls->ssl_ctx, "SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32"); SSL_CTX_set_tlsext_use_srtp(dtls->ssl_ctx, "SRTP_AES128_CM_SHA1_80"); @@ -3456,11 +3459,11 @@ SWITCH_DECLARE(switch_status_t) switch_rtp_add_dtls(switch_rtp_t *rtp_session, d SSL_set_verify(dtls->ssl, SSL_VERIFY_NONE, NULL); SSL_set_app_data(dtls->ssl, dtls); - BIO_ctrl(dtls->read_bio, BIO_CTRL_DGRAM_SET_MTU, 1400, NULL); - BIO_ctrl(dtls->write_bio, BIO_CTRL_DGRAM_SET_MTU, 1400, NULL); - SSL_set_mtu(dtls->ssl, 1400); - BIO_ctrl(dtls->write_bio, BIO_C_SET_BUFF_SIZE, 1400, NULL); - BIO_ctrl(dtls->read_bio, BIO_C_SET_BUFF_SIZE, 1400, NULL); + //BIO_ctrl(dtls->read_bio, BIO_CTRL_DGRAM_SET_MTU, 1400, NULL); + //BIO_ctrl(dtls->write_bio, BIO_CTRL_DGRAM_SET_MTU, 1400, NULL); + //SSL_set_mtu(dtls->ssl, 1400); + //BIO_ctrl(dtls->write_bio, BIO_C_SET_BUFF_SIZE, 1400, NULL); + //BIO_ctrl(dtls->read_bio, BIO_C_SET_BUFF_SIZE, 1400, NULL); @@ -4145,7 +4148,7 @@ SWITCH_DECLARE(switch_status_t) switch_rtp_set_video_buffer_size(switch_rtp_t *r } if (!max_frames || frames >= max_frames) { - max_frames = frames + 8; + max_frames = frames * 10; } rtp_session->last_max_vb_frames = max_frames; @@ -4202,7 +4205,7 @@ SWITCH_DECLARE(switch_status_t) switch_rtp_activate_jitter_buffer(switch_rtp_t * } if (max_queue_frames < queue_frames) { - max_queue_frames = queue_frames * 6; + max_queue_frames = queue_frames * 3; } From 2511ad50e157257b2d26d57888201be019a51a29 Mon Sep 17 00:00:00 2001 From: Italo Rossi Date: Fri, 23 Sep 2016 10:58:44 -0300 Subject: [PATCH 2/7] FS-8955 [verto_communicator] Adding DTMF shortcuts and handling DTMF history on DTMF widget --- .../verto_communicator/src/partials/chat.html | 3 +- .../src/partials/dialpad_widget.html | 26 ++++++------- .../controllers/MainController.js | 39 ++++++++++++++++++- 3 files changed, 52 insertions(+), 16 deletions(-) diff --git a/html5/verto/verto_communicator/src/partials/chat.html b/html5/verto/verto_communicator/src/partials/chat.html index 3560a2f4df..06124ca08e 100644 --- a/html5/verto/verto_communicator/src/partials/chat.html +++ b/html5/verto/verto_communicator/src/partials/chat.html @@ -188,7 +188,8 @@
- +
diff --git a/html5/verto/video_demo/js/verto-min.js b/html5/verto/video_demo/js/verto-min.js index dbe0017137..95d5cf1111 100644 --- a/html5/verto/video_demo/js/verto-min.js +++ b/html5/verto/video_demo/js/verto-min.js @@ -255,8 +255,9 @@ if(!dialog.params.remote_caller_id_number){dialog.params.remote_caller_id_number RTCcallbacks.onMessage=function(rtc,msg){console.debug(msg);};RTCcallbacks.onAnswerSDP=function(rtc,sdp){console.error("answer sdp",sdp);};}else{dialog.params.remote_caller_id_name="Outbound Call";dialog.params.remote_caller_id_number=dialog.params.destination_number;} RTCcallbacks.onICESDP=function(rtc){console.log("RECV "+rtc.type+" SDP",rtc.mediaData.SDP);if(dialog.state==$.verto.enum.state.requesting||dialog.state==$.verto.enum.state.answering||dialog.state==$.verto.enum.state.active){location.reload();return;} if(rtc.type=="offer"){if(dialog.state==$.verto.enum.state.active){dialog.setState($.verto.enum.state.requesting);dialog.sendMethod("verto.attach",{sdp:rtc.mediaData.SDP});}else{dialog.setState($.verto.enum.state.requesting);dialog.sendMethod("verto.invite",{sdp:rtc.mediaData.SDP});}}else{dialog.setState($.verto.enum.state.answering);dialog.sendMethod(dialog.attach?"verto.attach":"verto.answer",{sdp:dialog.rtc.mediaData.SDP});}};RTCcallbacks.onICE=function(rtc){if(rtc.type=="offer"){console.log("offer",rtc.mediaData.candidate);return;}};RTCcallbacks.onStream=function(rtc,stream){console.log("stream started");};RTCcallbacks.onError=function(e){console.error("ERROR:",e);dialog.hangup({cause:"Device or Permission Error"});};dialog.rtc=new $.FSRTC({callbacks:RTCcallbacks,localVideo:dialog.screenShare?null:dialog.localVideo,useVideo:dialog.params.useVideo?dialog.videoStream:null,useAudio:dialog.audioStream,useStereo:dialog.params.useStereo,videoParams:dialog.params.videoParams,audioParams:verto.options.audioParams,iceServers:verto.options.iceServers,screenShare:dialog.screenShare,useCamera:dialog.useCamera,useMic:dialog.useMic,useSpeak:dialog.useSpeak});dialog.rtc.verto=dialog.verto;if(dialog.direction==$.verto.enum.direction.inbound){if(dialog.attach){dialog.answer();}else{dialog.ring();}}};$.verto.dialog.prototype.invite=function(){var dialog=this;dialog.rtc.call();};$.verto.dialog.prototype.sendMethod=function(method,obj){var dialog=this;obj.dialogParams={};for(var i in dialog.params){if(i=="sdp"&&method!="verto.invite"&&method!="verto.attach"){continue;} +if((obj.noDialogParams&&i!="callID")){continue;} obj.dialogParams[i]=dialog.params[i];} -dialog.verto.rpcClient.call(method,obj,function(e){dialog.processReply(method,true,e);},function(e){dialog.processReply(method,false,e);});};function checkStateChange(oldS,newS){if(newS==$.verto.enum.state.purge||$.verto.enum.states[oldS.name][newS.name]){return true;} +delete obj.noDialogParams;dialog.verto.rpcClient.call(method,obj,function(e){dialog.processReply(method,true,e);},function(e){dialog.processReply(method,false,e);});};function checkStateChange(oldS,newS){if(newS==$.verto.enum.state.purge||$.verto.enum.states[oldS.name][newS.name]){return true;} return false;} function find_name(id){for(var i in $.verto.audioOutDevices){var source=$.verto.audioOutDevices[i];if(source.id===id){return(source.label);}} return id;} @@ -280,7 +281,8 @@ if(success){} break;default:break;}};$.verto.dialog.prototype.hangup=function(params){var dialog=this;if(params){if(params.causeCode){dialog.causeCode=params.causeCode;} if(params.cause){dialog.cause=params.cause;}} if(dialog.state.val>=$.verto.enum.state.new.val&&dialog.state.val<$.verto.enum.state.hangup.val){dialog.setState($.verto.enum.state.hangup);}else if(dialog.state.val<$.verto.enum.state.destroy){dialog.setState($.verto.enum.state.destroy);}};$.verto.dialog.prototype.stopRinging=function(){var dialog=this;if(dialog.verto.ringer){dialog.verto.ringer.stop();}};$.verto.dialog.prototype.indicateRing=function(){var dialog=this;if(dialog.verto.ringer){dialog.verto.ringer.attr("src",dialog.verto.options.ringFile)[0].play();setTimeout(function(){dialog.stopRinging();if(dialog.state==$.verto.enum.state.ringing){dialog.indicateRing();}},dialog.verto.options.ringSleep);}};$.verto.dialog.prototype.ring=function(){var dialog=this;dialog.setState($.verto.enum.state.ringing);dialog.indicateRing();};$.verto.dialog.prototype.useVideo=function(on){var dialog=this;dialog.params.useVideo=on;if(on){dialog.videoStream=dialog.audioStream;}else{dialog.videoStream=null;} -dialog.rtc.useVideo(dialog.videoStream,dialog.localVideo);};$.verto.dialog.prototype.setMute=function(what){var dialog=this;return dialog.rtc.setMute(what);};$.verto.dialog.prototype.getMute=function(){var dialog=this;return dialog.rtc.getMute();};$.verto.dialog.prototype.setVideoMute=function(what){var dialog=this;return dialog.rtc.setVideoMute(what);};$.verto.dialog.prototype.getVideoMute=function(){var dialog=this;return dialog.rtc.getVideoMute();};$.verto.dialog.prototype.useStereo=function(on){var dialog=this;dialog.params.useStereo=on;dialog.rtc.useStereo(on);};$.verto.dialog.prototype.dtmf=function(digits){var dialog=this;if(digits){dialog.sendMethod("verto.info",{dtmf:digits});}};$.verto.dialog.prototype.transfer=function(dest,params){var dialog=this;if(dest){dialog.sendMethod("verto.modify",{action:"transfer",destination:dest,params:params});}};$.verto.dialog.prototype.hold=function(params){var dialog=this;dialog.sendMethod("verto.modify",{action:"hold",params:params});};$.verto.dialog.prototype.unhold=function(params){var dialog=this;dialog.sendMethod("verto.modify",{action:"unhold",params:params});};$.verto.dialog.prototype.toggleHold=function(params){var dialog=this;dialog.sendMethod("verto.modify",{action:"toggleHold",params:params});};$.verto.dialog.prototype.message=function(msg){var dialog=this;var err=0;msg.from=dialog.params.login;if(!msg.to){console.error("Missing To");err++;} +dialog.rtc.useVideo(dialog.videoStream,dialog.localVideo);};$.verto.dialog.prototype.setMute=function(what){var dialog=this;return dialog.rtc.setMute(what);};$.verto.dialog.prototype.getMute=function(){var dialog=this;return dialog.rtc.getMute();};$.verto.dialog.prototype.setVideoMute=function(what){var dialog=this;return dialog.rtc.setVideoMute(what);};$.verto.dialog.prototype.getVideoMute=function(){var dialog=this;return dialog.rtc.getVideoMute();};$.verto.dialog.prototype.useStereo=function(on){var dialog=this;dialog.params.useStereo=on;dialog.rtc.useStereo(on);};$.verto.dialog.prototype.dtmf=function(digits){var dialog=this;if(digits){dialog.sendMethod("verto.info",{dtmf:digits});}};$.verto.dialog.prototype.rtt=function(obj){var dialog=this;var pobj={};if(!obj){return false;} +pobj.code=obj.code;pobj.chars=obj.chars;if(pobj.chars||pobj.code){dialog.sendMethod("verto.info",{txt:obj,noDialogParams:true});}};$.verto.dialog.prototype.transfer=function(dest,params){var dialog=this;if(dest){dialog.sendMethod("verto.modify",{action:"transfer",destination:dest,params:params});}};$.verto.dialog.prototype.hold=function(params){var dialog=this;dialog.sendMethod("verto.modify",{action:"hold",params:params});};$.verto.dialog.prototype.unhold=function(params){var dialog=this;dialog.sendMethod("verto.modify",{action:"unhold",params:params});};$.verto.dialog.prototype.toggleHold=function(params){var dialog=this;dialog.sendMethod("verto.modify",{action:"toggleHold",params:params});};$.verto.dialog.prototype.message=function(msg){var dialog=this;var err=0;msg.from=dialog.params.login;if(!msg.to){console.error("Missing To");err++;} if(!msg.body){console.error("Missing Body");err++;} if(err){return false;} dialog.sendMethod("verto.info",{msg:msg});return true;};$.verto.dialog.prototype.answer=function(params){var dialog=this;if(!dialog.answered){if(!params){params={};} @@ -289,7 +291,7 @@ dialog.params.callee_id_name=params.callee_id_name;dialog.params.callee_id_numbe if(params.useMic){dialog.useMic=params.useMic;} if(params.useSpeak){dialog.useSpeak=params.useSpeak;}} dialog.rtc.createAnswer(params);dialog.answered=true;}};$.verto.dialog.prototype.handleAnswer=function(params){var dialog=this;dialog.gotAnswer=true;if(dialog.state.val>=$.verto.enum.state.active.val){return;} -if(dialog.state.val>=$.verto.enum.state.early.val){dialog.setState($.verto.enum.state.active);}else{if(dialog.gotEarly){console.log("Dialog "+dialog.callID+" Got answer while still establishing early media, delaying...");}else{console.log("Dialog "+dialog.callID+" Answering Channel");dialog.rtc.answer(params.sdp,function(){dialog.setState($.verto.enum.state.active);},function(e){console.error(e);dialog.hangup();});console.log("Dialog "+dialog.callID+"ANSWER SDP",params.sdp);}}};$.verto.dialog.prototype.cidString=function(enc){var dialog=this;var party=dialog.params.remote_caller_id_name+(enc?" <":" <")+dialog.params.remote_caller_id_number+(enc?">":">");return party;};$.verto.dialog.prototype.sendMessage=function(msg,params){var dialog=this;if(dialog.callbacks.onMessage){dialog.callbacks.onMessage(dialog.verto,dialog,msg,params);}};$.verto.dialog.prototype.handleInfo=function(params){var dialog=this;dialog.sendMessage($.verto.enum.message.info,params.msg);};$.verto.dialog.prototype.handleDisplay=function(params){var dialog=this;if(params.display_name){dialog.params.remote_caller_id_name=params.display_name;} +if(dialog.state.val>=$.verto.enum.state.early.val){dialog.setState($.verto.enum.state.active);}else{if(dialog.gotEarly){console.log("Dialog "+dialog.callID+" Got answer while still establishing early media, delaying...");}else{console.log("Dialog "+dialog.callID+" Answering Channel");dialog.rtc.answer(params.sdp,function(){dialog.setState($.verto.enum.state.active);},function(e){console.error(e);dialog.hangup();});console.log("Dialog "+dialog.callID+"ANSWER SDP",params.sdp);}}};$.verto.dialog.prototype.cidString=function(enc){var dialog=this;var party=dialog.params.remote_caller_id_name+(enc?" <":" <")+dialog.params.remote_caller_id_number+(enc?">":">");return party;};$.verto.dialog.prototype.sendMessage=function(msg,params){var dialog=this;if(dialog.callbacks.onMessage){dialog.callbacks.onMessage(dialog.verto,dialog,msg,params);}};$.verto.dialog.prototype.handleInfo=function(params){var dialog=this;dialog.sendMessage($.verto.enum.message.info,params);};$.verto.dialog.prototype.handleDisplay=function(params){var dialog=this;if(params.display_name){dialog.params.remote_caller_id_name=params.display_name;} if(params.display_number){dialog.params.remote_caller_id_number=params.display_number;} dialog.sendMessage($.verto.enum.message.display,{});};$.verto.dialog.prototype.handleMedia=function(params){var dialog=this;if(dialog.state.val>=$.verto.enum.state.early.val){return;} dialog.gotEarly=true;dialog.rtc.answer(params.sdp,function(){console.log("Dialog "+dialog.callID+"Establishing early media");dialog.setState($.verto.enum.state.early);if(dialog.gotAnswer){console.log("Dialog "+dialog.callID+"Answering Channel");dialog.setState($.verto.enum.state.active);}},function(e){console.error(e);dialog.hangup();});console.log("Dialog "+dialog.callID+"EARLY SDP",params.sdp);};$.verto.ENUM=function(s){var i=0,o={};s.split(" ").map(function(x){o[x]={name:x,val:i++};});return Object.freeze(o);};$.verto.enum={};$.verto.enum.states=Object.freeze({new:{requesting:1,recovering:1,ringing:1,destroy:1,answering:1,hangup:1},requesting:{trying:1,hangup:1,active:1},recovering:{answering:1,hangup:1},trying:{active:1,early:1,hangup:1},ringing:{answering:1,hangup:1},answering:{active:1,hangup:1},active:{answering:1,requesting:1,hangup:1,held:1},held:{hangup:1,active:1},early:{hangup:1,active:1},hangup:{destroy:1},destroy:{},purge:{destroy:1}});$.verto.enum.state=$.verto.ENUM("new requesting trying recovering ringing answering early active held hangup destroy purge");$.verto.enum.direction=$.verto.ENUM("inbound outbound");$.verto.enum.message=$.verto.ENUM("display info pvtEvent");$.verto.enum=Object.freeze($.verto.enum);$.verto.saved=[];$.verto.unloadJobs=[];$(window).bind('beforeunload',function(){for(var f in $.verto.unloadJobs){$.verto.unloadJobs[f]();} diff --git a/html5/verto/video_demo/verto.js b/html5/verto/video_demo/verto.js index e0ee4aec19..3b06234439 100644 --- a/html5/verto/video_demo/verto.js +++ b/html5/verto/video_demo/verto.js @@ -403,41 +403,67 @@ var callbacks = { } break; case $.verto.enum.message.info: - var body = data.body; - + if (data.msg) { + data = data.msg; + var body = data.body; + /* // This section has been replaced with messageTextToJQ function - if (body.match(/\.gif|\.jpg|\.jpeg|\.png/)) { + if (body.match(/\.gif|\.jpg|\.jpeg|\.png/)) { var mod = ""; if (body.match(/dropbox.com/)) { - mod = "?dl=1"; + mod = "?dl=1"; } body = body.replace(/(http[s]{0,1}:\/\/\S+)/g, "$1
<\/a>"); - } else { + } else { body = body.replace(/(http[s]{0,1}:\/\/\S+)/g, "
$1<\/a>"); - } + } - if (body.slice(-1) !== "\n") { + if (body.slice(-1) !== "\n") { body += "\n"; - } - body = body.replace(/(?:\r\n|\r|\n)/g, '
'); - - var from = data.from_msg_name || data.from; + } + body = body.replace(/(?:\r\n|\r|\n)/g, '
'); + + var from = data.from_msg_name || data.from; - $("#chatwin").append("" + from + ":
" + body); - $('#chatwin').animate({"scrollTop": $('#chatwin')[0].scrollHeight}, "fast"); + $("#chatwin").append("" + from + ":
" + body); + $('#chatwin').animate({"scrollTop": $('#chatwin')[0].scrollHeight}, "fast"); */ - var from = data.from_msg_name || data.from; - - $('#chatwin') - .append($('').text(from + ':')) - .append($('
')) - .append(messageTextToJQ(body)) - .append($('
')); - $('#chatwin').animate({"scrollTop": $('#chatwin')[0].scrollHeight}, "fast"); + var from = data.from_msg_name || data.from; + $('#chatwin') + .append($('').text(from + ':')) + .append($('
')) + .append(messageTextToJQ(body)) + .append($('
')); + $('#chatwin').animate({"scrollTop": $('#chatwin')[0].scrollHeight}, "fast"); + } + + if (data.txt) { + console.log(data.txt); + if (data.txt.chars) { + var a = [...data.txt.chars]; + //console.log(a); + for (var x in a) { + if(a[x] == "\r") { + $("#rtt_in").append("\n"); + continue; + } else if (a[x] == "\b") { + $("#rtt_in").text($("#rtt_in").text().slice(0, -1)); + continue; + } + console.log("[" + a[x] + "]"); + $("#rtt_in").append(a[x]); + } + + var psconsole = $('#rtt_in'); + if(psconsole.length) + psconsole.scrollTop(psconsole[0].scrollHeight - psconsole.height()); + } + } + break; case $.verto.enum.message.display: var party = dialog.params.remote_caller_id_name + "<" + dialog.params.remote_caller_id_number + ">"; @@ -1558,6 +1584,30 @@ function init() { setupChat(); + $("#rtt").val(""); + $("#rtt_in").text(""); + + + $("#rtt").keyup(function (event) { + console.error(event); + console.log("KEY (" + event.which + ")\n"); + + if (event.which == 8) { + cur_call.rtt({code: event.which}); + } + + if (event.which == 13) { + $("#rtt").val(""); + } + + }); + + $("#rtt").keypress(function (event) { + console.error(event); + console.log("TEXT (" + event.which + ")\n"); + cur_call.rtt({code: event.which}); + }); + $("#ext").keyup(function (event) { if (event.keyCode == 13) { $( "#callbtn" ).trigger( "click" ); diff --git a/libs/esl/src/esl_event.c b/libs/esl/src/esl_event.c index 24f7163976..d097f74569 100644 --- a/libs/esl/src/esl_event.c +++ b/libs/esl/src/esl_event.c @@ -147,6 +147,7 @@ static const char *EVENT_NAMES[] = { "CALL_SETUP_RESULT", "CALL_DETAIL", "DEVICE_STATE", + "REAL_TIME_TEXT", "ALL" }; diff --git a/libs/esl/src/include/esl_event.h b/libs/esl/src/include/esl_event.h index 47c38b6cf7..1380f68092 100644 --- a/libs/esl/src/include/esl_event.h +++ b/libs/esl/src/include/esl_event.h @@ -137,6 +137,7 @@ typedef enum { ESL_EVENT_CALL_SETUP_RESULT, ESL_EVENT_CALL_DETAIL, ESL_EVENT_DEVICE_STATE, + ESL_EVENT_REAL_TIME_TEXT, ESL_EVENT_ALL } esl_event_types_t; diff --git a/libs/sofia-sip/libsofia-sip-ua/sdp/sdp.bnf b/libs/sofia-sip/libsofia-sip-ua/sdp/sdp.bnf index 8a2b08ab7c..42281c1ec2 100644 --- a/libs/sofia-sip/libsofia-sip-ua/sdp/sdp.bnf +++ b/libs/sofia-sip/libsofia-sip-ua/sdp/sdp.bnf @@ -85,7 +85,7 @@ media = 1*(alpha-numeric) ;typically "audio", "video", "application" - ;or "data" + ;or "data" or "text" fmt = 1*(alpha-numeric) ;typically an RTP payload type for audio diff --git a/libs/sofia-sip/libsofia-sip-ua/sdp/sdp_parse.c b/libs/sofia-sip/libsofia-sip-ua/sdp/sdp_parse.c index 887e4e818a..3b1190ee5f 100644 --- a/libs/sofia-sip/libsofia-sip-ua/sdp/sdp_parse.c +++ b/libs/sofia-sip/libsofia-sip-ua/sdp/sdp_parse.c @@ -1275,7 +1275,7 @@ static void parse_media(sdp_parser_t *p, char *r, sdp_media_t **result) media = token ;typically "audio", "video", "application" - ;or "data" + ;or "data" or "text" fmt = token ;typically an RTP payload type for audio @@ -1378,6 +1378,8 @@ void sdp_media_type(sdp_media_t *m, char const *s) m->m_type = sdp_media_image, m->m_type_name = "image"; else if (su_casematch(s, "red")) m->m_type = sdp_media_red, m->m_type_name = "red"; + else if (su_casematch(s, "text")) + m->m_type = sdp_media_text, m->m_type_name = "text"; else m->m_type = sdp_media_x, m->m_type_name = s; } diff --git a/libs/sofia-sip/libsofia-sip-ua/sdp/sdp_print.c b/libs/sofia-sip/libsofia-sip-ua/sdp/sdp_print.c index 0f8e390ebf..767556d7ac 100644 --- a/libs/sofia-sip/libsofia-sip-ua/sdp/sdp_print.c +++ b/libs/sofia-sip/libsofia-sip-ua/sdp/sdp_print.c @@ -583,6 +583,7 @@ static void print_media(sdp_printer_t *p, case sdp_media_control: media = "control"; break; case sdp_media_message: media = "message"; break; case sdp_media_image : media = "image"; break; + case sdp_media_text : media = "text"; break; default: media = m->m_type_name; } diff --git a/libs/sofia-sip/libsofia-sip-ua/sdp/sofia-sip/sdp.h b/libs/sofia-sip/libsofia-sip-ua/sdp/sofia-sip/sdp.h index 6034a7a37a..bf0741e044 100644 --- a/libs/sofia-sip/libsofia-sip-ua/sdp/sofia-sip/sdp.h +++ b/libs/sofia-sip/libsofia-sip-ua/sdp/sofia-sip/sdp.h @@ -232,7 +232,8 @@ typedef enum sdp_media_message, /**< Messaging sessions*/ sdp_media_image, /**< Image browsing sessions, * e.g., JPIP or T.38. */ - sdp_media_red /**< Redundancy. @NEW_1_12_4. */ + sdp_media_red, /**< Redundancy. @NEW_1_12_4. */ + sdp_media_text, /**< Realtime Text */ } sdp_media_e; /** Media transport protocol. */ diff --git a/src/include/private/switch_core_pvt.h b/src/include/private/switch_core_pvt.h index 4204fcd0d5..bf4dd351e1 100644 --- a/src/include/private/switch_core_pvt.h +++ b/src/include/private/switch_core_pvt.h @@ -189,7 +189,13 @@ struct switch_core_session { uint32_t decoder_errors; switch_core_video_thread_callback_func_t video_read_callback; void *video_read_user_data; + switch_core_video_thread_callback_func_t text_read_callback; + void *text_read_user_data; + switch_io_routines_t *io_override; switch_slin_data_t *sdata; + + switch_buffer_t *text_buffer; + switch_mutex_t *text_mutex; }; struct switch_media_bug { @@ -228,6 +234,11 @@ struct switch_media_bug { switch_image_t *spy_img[2]; switch_vid_spy_fmt_t spy_fmt; switch_thread_t *video_bug_thread; + + switch_buffer_t *text_buffer; + char *text_framedata; + uint32_t text_framesize; + struct switch_media_bug *next; }; diff --git a/src/include/switch_buffer.h b/src/include/switch_buffer.h index a3c5032343..e5af0baf0a 100644 --- a/src/include/switch_buffer.h +++ b/src/include/switch_buffer.h @@ -162,6 +162,8 @@ SWITCH_DECLARE(void) switch_buffer_destroy(switch_buffer_t **buffer); SWITCH_DECLARE(switch_size_t) switch_buffer_zwrite(_In_ switch_buffer_t *buffer, _In_bytecount_(datalen) const void *data, _In_ switch_size_t datalen); +SWITCH_DECLARE(void *) switch_buffer_get_head_pointer(switch_buffer_t *buffer); + /** @} */ SWITCH_END_EXTERN_C diff --git a/src/include/switch_channel.h b/src/include/switch_channel.h index d3267ae122..b1f4b2d219 100644 --- a/src/include/switch_channel.h +++ b/src/include/switch_channel.h @@ -317,6 +317,11 @@ SWITCH_DECLARE(switch_status_t) switch_channel_get_variables(switch_channel_t *c SWITCH_DECLARE(switch_status_t) switch_channel_pass_callee_id(switch_channel_t *channel, switch_channel_t *other_channel); + +static inline int switch_channel_var_true(switch_channel_t *channel, const char *variable) { + return switch_true(switch_channel_get_variable_dup(channel, variable, SWITCH_FALSE, -1)); +} + /*! * \brief Start iterating over the entries in the channel variable list. * \param channel the channel to iterate the variables for diff --git a/src/include/switch_core.h b/src/include/switch_core.h index 86763f9713..3d8b2e8988 100644 --- a/src/include/switch_core.h +++ b/src/include/switch_core.h @@ -351,6 +351,8 @@ SWITCH_DECLARE(void) switch_core_media_bug_set_read_demux_frame(_In_ switch_medi */ SWITCH_DECLARE(switch_core_session_t *) switch_core_media_bug_get_session(_In_ switch_media_bug_t *bug); +SWITCH_DECLARE(const char *) switch_core_media_bug_get_text(switch_media_bug_t *bug); + /*! \brief Test for the existance of a flag on an media bug \param bug the object to test @@ -1163,6 +1165,8 @@ SWITCH_DECLARE(void *) switch_core_session_get_stream(_In_ switch_core_session_t */ SWITCH_DECLARE(int) switch_core_session_get_stream_count(_In_ switch_core_session_t *session); +SWITCH_DECLARE(const char *) switch_core_session_get_text_buffer(switch_core_session_t *session); + /*! \brief Launch a thread designed to exist within the scope of a given session \param session a session to allocate the thread from @@ -2741,6 +2745,8 @@ SWITCH_DECLARE(int) switch_stream_system(const char *cmd, switch_stream_handle_t SWITCH_DECLARE(switch_call_direction_t) switch_ice_direction(switch_core_session_t *session); SWITCH_DECLARE(void) switch_core_session_debug_pool(switch_stream_handle_t *stream); +SWITCH_DECLARE(switch_status_t) switch_core_session_override_io_routines(switch_core_session_t *session, switch_io_routines_t *ior); + SWITCH_DECLARE(const char *)switch_version_major(void); SWITCH_DECLARE(const char *)switch_version_minor(void); SWITCH_DECLARE(const char *)switch_version_micro(void); @@ -2752,6 +2758,9 @@ SWITCH_DECLARE(const char *)switch_version_full_human(void); SWITCH_DECLARE(void) switch_core_autobind_cpu(void); +SWITCH_DECLARE(switch_status_t) switch_core_session_start_text_thread(switch_core_session_t *session); + + SWITCH_END_EXTERN_C #endif /* For Emacs: diff --git a/src/include/switch_core_event_hook.h b/src/include/switch_core_event_hook.h index 26dfb0c87d..f991955bd4 100644 --- a/src/include/switch_core_event_hook.h +++ b/src/include/switch_core_event_hook.h @@ -41,6 +41,8 @@ typedef struct switch_io_event_hook_read_frame switch_io_event_hook_read_frame_t typedef struct switch_io_event_hook_video_read_frame switch_io_event_hook_video_read_frame_t; typedef struct switch_io_event_hook_write_frame switch_io_event_hook_write_frame_t; typedef struct switch_io_event_hook_video_write_frame switch_io_event_hook_video_write_frame_t; +typedef struct switch_io_event_hook_text_read_frame switch_io_event_hook_text_read_frame_t; +typedef struct switch_io_event_hook_text_write_frame switch_io_event_hook_text_write_frame_t; typedef struct switch_io_event_hook_kill_channel switch_io_event_hook_kill_channel_t; typedef struct switch_io_event_hook_send_dtmf switch_io_event_hook_send_dtmf_t; typedef struct switch_io_event_hook_recv_dtmf switch_io_event_hook_recv_dtmf_t; @@ -54,6 +56,8 @@ typedef switch_status_t (*switch_read_frame_hook_t) (switch_core_session_t *, sw typedef switch_status_t (*switch_video_read_frame_hook_t) (switch_core_session_t *, switch_frame_t **, switch_io_flag_t, int); typedef switch_status_t (*switch_write_frame_hook_t) (switch_core_session_t *, switch_frame_t *, switch_io_flag_t, int); typedef switch_status_t (*switch_video_write_frame_hook_t) (switch_core_session_t *, switch_frame_t *, switch_io_flag_t, int); +typedef switch_status_t (*switch_text_read_frame_hook_t) (switch_core_session_t *, switch_frame_t **, switch_io_flag_t, int); +typedef switch_status_t (*switch_text_write_frame_hook_t) (switch_core_session_t *, switch_frame_t *, switch_io_flag_t, int); typedef switch_status_t (*switch_kill_channel_hook_t) (switch_core_session_t *, int); typedef switch_status_t (*switch_send_dtmf_hook_t) (switch_core_session_t *, const switch_dtmf_t *, switch_dtmf_direction_t direction); typedef switch_status_t (*switch_recv_dtmf_hook_t) (switch_core_session_t *, const switch_dtmf_t *, switch_dtmf_direction_t direction); @@ -108,6 +112,20 @@ struct switch_io_event_hook_video_write_frame { struct switch_io_event_hook_video_write_frame *next; }; +/*! \brief Node in which to store custom read frame channel callback hooks */ +struct switch_io_event_hook_text_read_frame { + /*! the read frame channel callback hook */ + switch_read_frame_hook_t text_read_frame; + struct switch_io_event_hook_text_read_frame *next; +}; + +/*! \brief Node in which to store custom video_write_frame channel callback hooks */ +struct switch_io_event_hook_text_write_frame { + /*! the video_write_frame channel callback hook */ + switch_video_write_frame_hook_t text_write_frame; + struct switch_io_event_hook_text_write_frame *next; +}; + /*! \brief Node in which to store custom kill channel callback hooks */ struct switch_io_event_hook_kill_channel { /*! the kill channel callback hook */ @@ -158,8 +176,12 @@ struct switch_io_event_hooks { switch_io_event_hook_video_read_frame_t *video_read_frame; /*! a list of write frame hooks */ switch_io_event_hook_write_frame_t *write_frame; - /*! a list of video write frame hooks */ + /*! a list of text write frame hooks */ switch_io_event_hook_video_write_frame_t *video_write_frame; + /*! a list of text write frame hooks */ + switch_io_event_hook_text_write_frame_t *text_write_frame; + /*! a list of text read frame hooks */ + switch_io_event_hook_text_read_frame_t *text_read_frame; /*! a list of kill channel hooks */ switch_io_event_hook_kill_channel_t *kill_channel; /*! a list of send dtmf hooks */ @@ -225,6 +247,8 @@ NEW_HOOK_DECL_ADD_P(read_frame); NEW_HOOK_DECL_ADD_P(write_frame); NEW_HOOK_DECL_ADD_P(video_read_frame); NEW_HOOK_DECL_ADD_P(video_write_frame); +NEW_HOOK_DECL_ADD_P(text_read_frame); +NEW_HOOK_DECL_ADD_P(text_write_frame); NEW_HOOK_DECL_ADD_P(kill_channel); NEW_HOOK_DECL_ADD_P(send_dtmf); NEW_HOOK_DECL_ADD_P(recv_dtmf); @@ -238,6 +262,8 @@ NEW_HOOK_DECL_REM_P(read_frame); NEW_HOOK_DECL_REM_P(write_frame); NEW_HOOK_DECL_REM_P(video_read_frame); NEW_HOOK_DECL_REM_P(video_write_frame); +NEW_HOOK_DECL_REM_P(text_read_frame); +NEW_HOOK_DECL_REM_P(text_write_frame); NEW_HOOK_DECL_REM_P(kill_channel); NEW_HOOK_DECL_REM_P(send_dtmf); NEW_HOOK_DECL_REM_P(recv_dtmf); diff --git a/src/include/switch_core_media.h b/src/include/switch_core_media.h index 096bc815fc..5bca68d6c0 100644 --- a/src/include/switch_core_media.h +++ b/src/include/switch_core_media.h @@ -122,9 +122,11 @@ typedef struct switch_core_media_params_s { switch_rtp_bug_flag_t manual_rtp_bugs; switch_rtp_bug_flag_t manual_video_rtp_bugs; + switch_rtp_bug_flag_t manual_text_rtp_bugs; char *rtcp_audio_interval_msec; char *rtcp_video_interval_msec; + char *rtcp_text_interval_msec; char *extrtpip; @@ -331,10 +333,10 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_codec_control(switch_core_sess SWITCH_DECLARE(switch_timer_t *) switch_core_media_get_timer(switch_core_session_t *session, switch_media_type_t mtype); -SWITCH_DECLARE(void) switch_core_media_start_video_function(switch_core_session_t *session, switch_video_function_t video_function, void *user_data); -SWITCH_DECLARE(void) switch_core_media_end_video_function(switch_core_session_t *session); +SWITCH_DECLARE(void) switch_core_media_start_engine_function(switch_core_session_t *session, switch_media_type_t type, switch_engine_function_t engine_function, void *user_data); +SWITCH_DECLARE(void) switch_core_media_end_engine_function(switch_core_session_t *session, switch_media_type_t type); SWITCH_DECLARE(switch_status_t) switch_core_session_start_video_thread(switch_core_session_t *session); -SWITCH_DECLARE(int) switch_core_media_check_video_function(switch_core_session_t *session); +SWITCH_DECLARE(int) switch_core_media_check_engine_function(switch_core_session_t *session, switch_media_type_t type); SWITCH_DECLARE(void) switch_core_session_video_reinit(switch_core_session_t *session); SWITCH_DECLARE(switch_status_t) switch_core_media_read_lock_unlock(switch_core_session_t *session, switch_media_type_t type, switch_bool_t lock); @@ -353,7 +355,23 @@ SWITCH_DECLARE(switch_bool_t) switch_core_media_check_dtls(switch_core_session_t SWITCH_DECLARE(switch_status_t) switch_core_media_set_outgoing_bitrate(switch_core_session_t *session, switch_media_type_t type, uint32_t bitrate); SWITCH_DECLARE(switch_status_t) switch_core_media_reset_jb(switch_core_session_t *session, switch_media_type_t type); SWITCH_DECLARE(switch_status_t) switch_core_session_wait_for_video_input_params(switch_core_session_t *session, uint32_t timeout_ms); - + + +SWITCH_DECLARE(switch_status_t) switch_core_session_set_text_read_callback(switch_core_session_t *session, + switch_core_text_thread_callback_func_t func, void *user_data); +SWITCH_DECLARE(switch_status_t) switch_core_session_text_read_callback(switch_core_session_t *session, switch_frame_t *frame); +SWITCH_DECLARE(switch_status_t) switch_core_session_read_text_frame(switch_core_session_t *session, switch_frame_t **frame, switch_io_flag_t flags, + int stream_id); + +SWITCH_DECLARE(switch_status_t) switch_core_session_write_text_frame(switch_core_session_t *session, switch_frame_t *frame, switch_io_flag_t flags, + int stream_id); + +SWITCH_DECLARE(switch_status_t) switch_rtp_text_factory_create(switch_rtp_text_factory_t **tfP, switch_memory_pool_t *pool); +SWITCH_DECLARE(switch_status_t) switch_rtp_text_factory_destroy(switch_rtp_text_factory_t **tfP); + +SWITCH_DECLARE(switch_status_t) switch_core_session_print(switch_core_session_t *session, const char *data); +SWITCH_DECLARE(switch_status_t) switch_core_session_printf(switch_core_session_t *session, const char *fmt, ...); + SWITCH_END_EXTERN_C #endif /* For Emacs: diff --git a/src/include/switch_ivr.h b/src/include/switch_ivr.h index 06fa8c137a..712179d783 100644 --- a/src/include/switch_ivr.h +++ b/src/include/switch_ivr.h @@ -1020,6 +1020,7 @@ SWITCH_DECLARE(switch_status_t) switch_ivr_stop_video_write_overlay_session(swit SWITCH_DECLARE(switch_status_t) switch_ivr_video_write_overlay_session(switch_core_session_t *session, const char *img_path, switch_img_position_t pos, uint8_t alpha); +SWITCH_DECLARE(switch_status_t) switch_ivr_capture_text(switch_core_session_t *session, switch_bool_t on); /** @} */ diff --git a/src/include/switch_jitterbuffer.h b/src/include/switch_jitterbuffer.h index de423fd7fd..e55bcbec28 100644 --- a/src/include/switch_jitterbuffer.h +++ b/src/include/switch_jitterbuffer.h @@ -39,7 +39,8 @@ typedef enum { typedef enum { SJB_VIDEO = 0, - SJB_AUDIO + SJB_AUDIO, + SJB_TEXT } switch_jb_type_t; diff --git a/src/include/switch_module_interfaces.h b/src/include/switch_module_interfaces.h index 1802f872e3..0997bbeb05 100644 --- a/src/include/switch_module_interfaces.h +++ b/src/include/switch_module_interfaces.h @@ -119,6 +119,8 @@ typedef switch_status_t (*switch_io_state_change_t) (switch_core_session_t *); typedef switch_status_t (*switch_io_state_run_t) (switch_core_session_t *); typedef switch_status_t (*switch_io_read_video_frame_t) (switch_core_session_t *, switch_frame_t **, switch_io_flag_t, int); typedef switch_status_t (*switch_io_write_video_frame_t) (switch_core_session_t *, switch_frame_t *, switch_io_flag_t, int); +typedef switch_status_t (*switch_io_read_text_frame_t) (switch_core_session_t *, switch_frame_t **, switch_io_flag_t, int); +typedef switch_status_t (*switch_io_write_text_frame_t) (switch_core_session_t *, switch_frame_t *, switch_io_flag_t, int); typedef switch_jb_t *(*switch_io_get_jb_t) (switch_core_session_t *, switch_media_type_t); typedef enum { @@ -132,6 +134,8 @@ typedef enum { SWITCH_IO_STATE_CHANGE, SWITCH_IO_READ_VIDEO_FRAME, SWITCH_IO_WRITE_VIDEO_FRAME, + SWITCH_IO_READ_TEXT_FRAME, + SWITCH_IO_WRITE_TEXT_FRAME, SWITCH_IO_GET_JB, } switch_io_routine_name_t; @@ -157,6 +161,10 @@ struct switch_io_routines { switch_io_read_video_frame_t read_video_frame; /*! write a video frame to a session */ switch_io_write_video_frame_t write_video_frame; + /*! read a video frame from a session */ + switch_io_read_text_frame_t read_text_frame; + /*! write a video frame to a session */ + switch_io_write_text_frame_t write_text_frame; /*! change a sessions channel run state */ switch_io_state_run_t state_run; /*! get sessions jitterbuffer */ diff --git a/src/include/switch_types.h b/src/include/switch_types.h index cc8539085e..7b81a07e8f 100644 --- a/src/include/switch_types.h +++ b/src/include/switch_types.h @@ -213,6 +213,8 @@ SWITCH_BEGIN_EXTERN_C #define SWITCH_REMOTE_VIDEO_PORT_VARIABLE "remote_video_port" #define SWITCH_LOCAL_VIDEO_IP_VARIABLE "local_video_ip" #define SWITCH_LOCAL_VIDEO_PORT_VARIABLE "local_video_port" +#define SWITCH_LOCAL_TEXT_IP_VARIABLE "local_text_ip" +#define SWITCH_LOCAL_TEXT_PORT_VARIABLE "local_text_port" #define SWITCH_HANGUP_AFTER_BRIDGE_VARIABLE "hangup_after_bridge" #define SWITCH_PARK_AFTER_BRIDGE_VARIABLE "park_after_bridge" #define SWITCH_PARK_AFTER_EARLY_BRIDGE_VARIABLE "park_after_early_bridge" @@ -234,6 +236,7 @@ SWITCH_BEGIN_EXTERN_C #define SWITCH_RTCP_AUDIO_INTERVAL_MSEC "5000" #define SWITCH_RTCP_VIDEO_INTERVAL_MSEC "2000" +#define TEXT_UNICODE_LINEFEED {0xe2, 0x80, 0xa8} #define MAX_FMTP_LEN 256 /* Jitter */ @@ -496,7 +499,8 @@ typedef enum { SWITCH_ABC_TYPE_READ_VIDEO_PING, SWITCH_ABC_TYPE_WRITE_VIDEO_PING, SWITCH_ABC_TYPE_STREAM_VIDEO_PING, - SWITCH_ABC_TYPE_VIDEO_PATCH + SWITCH_ABC_TYPE_VIDEO_PATCH, + SWITCH_ABC_TYPE_READ_TEXT } switch_abc_type_t; typedef struct { @@ -769,6 +773,7 @@ typedef enum { SWITCH_RTP_FLAG_TMMBR, SWITCH_RTP_FLAG_GEN_TS_DELTA, SWITCH_RTP_FLAG_DETECT_SSRC, + SWITCH_RTP_FLAG_TEXT, SWITCH_RTP_FLAG_INVALID } switch_rtp_flag_t; @@ -1369,6 +1374,8 @@ typedef enum { CC_JITTERBUFFER, CC_FS_RTP, CC_QUEUEABLE_DTMF_DELAY, + CC_IO_OVERRIDE, + CC_RTP_RTT, /* WARNING: DO NOT ADD ANY FLAGS BELOW THIS LINE */ CC_FLAG_MAX } switch_channel_cap_t; @@ -1514,6 +1521,13 @@ typedef enum { CF_3P_NOMEDIA_REQUESTED_BLEG, CF_IMAGE_SDP, CF_VIDEO_SDP_RECVD, + CF_TEXT_SDP_RECVD, + CF_TEXT, + CF_TEXT_POSSIBLE, + CF_TEXT_PASSIVE, + CF_TEXT_ECHO, + CF_TEXT_ACTIVE, + CF_TEXT_IDLE, /* WARNING: DO NOT ADD ANY FLAGS BELOW THIS LINE */ /* IF YOU ADD NEW ONES CHECK IF THEY SHOULD PERSIST OR ZERO THEM IN switch_core_session.c switch_core_session_request_xml() */ CF_FLAG_MAX @@ -1570,7 +1584,8 @@ typedef enum { SFF_PICTURE_RESET = (1 << 14), SFF_SAME_IMAGE = (1 << 15), SFF_USE_VIDEO_TIMESTAMP = (1 << 16), - SFF_ENCODED = (1 << 17) + SFF_ENCODED = (1 << 17), + SFF_TEXT_LINE_BREAK = (1 << 18) } switch_frame_flag_enum_t; typedef uint32_t switch_frame_flag_t; @@ -1714,9 +1729,10 @@ typedef enum { typedef enum { SWITCH_MEDIA_TYPE_AUDIO, - SWITCH_MEDIA_TYPE_VIDEO + SWITCH_MEDIA_TYPE_VIDEO, + SWITCH_MEDIA_TYPE_TEXT } switch_media_type_t; -#define SWITCH_MEDIA_TYPE_TOTAL 2 +#define SWITCH_MEDIA_TYPE_TOTAL 3 /*! @@ -1775,7 +1791,8 @@ typedef enum { SMBF_VIDEO_PATCH = (1 << 21), SMBF_SPY_VIDEO_STREAM = (1 << 22), SMBF_SPY_VIDEO_STREAM_BLEG = (1 << 23), - SMBF_READ_VIDEO_PATCH = (1 << 24) + SMBF_READ_VIDEO_PATCH = (1 << 24), + SMBF_READ_TEXT_STREAM = (1 << 25) } switch_media_bug_flag_enum_t; typedef uint32_t switch_media_bug_flag_t; @@ -2021,6 +2038,7 @@ typedef enum { SWITCH_EVENT_CALL_SETUP_RESULT, SWITCH_EVENT_CALL_DETAIL, SWITCH_EVENT_DEVICE_STATE, + SWITCH_EVENT_REAL_TIME_TEXT, SWITCH_EVENT_ALL } switch_event_types_t; @@ -2236,13 +2254,15 @@ typedef struct switch_console_callback_match switch_console_callback_match_t; typedef void (*switch_media_bug_exec_cb_t)(switch_media_bug_t *bug, void *user_data); typedef switch_status_t (*switch_core_video_thread_callback_func_t) (switch_core_session_t *session, switch_frame_t *frame, void *user_data); +typedef switch_status_t (*switch_core_text_thread_callback_func_t) (switch_core_session_t *session, switch_frame_t *frame, void *user_data); typedef void (*switch_cap_callback_t) (const char *var, const char *val, void *user_data); typedef switch_status_t (*switch_console_complete_callback_t) (const char *, const char *, switch_console_callback_match_t **matches); typedef switch_bool_t (*switch_media_bug_callback_t) (switch_media_bug_t *, void *, switch_abc_type_t); typedef switch_bool_t (*switch_tone_detect_callback_t) (switch_core_session_t *, const char *, const char *); typedef struct switch_xml_binding switch_xml_binding_t; -typedef void (*switch_video_function_t) (switch_core_session_t *session, void *user_data); +typedef void (*switch_engine_function_t) (switch_core_session_t *session, void *user_data); + typedef switch_status_t (*switch_core_codec_encode_func_t) (switch_codec_t *codec, switch_codec_t *other_codec, @@ -2609,6 +2629,10 @@ typedef enum { SCFC_PAUSE_READ } switch_file_command_t; + +struct switch_rtp_text_factory_s; +typedef struct switch_rtp_text_factory_s switch_rtp_text_factory_t; + SWITCH_END_EXTERN_C #endif /* For Emacs: diff --git a/src/include/switch_utils.h b/src/include/switch_utils.h index 691395f362..1c423b202b 100644 --- a/src/include/switch_utils.h +++ b/src/include/switch_utils.h @@ -373,6 +373,33 @@ SWITCH_DECLARE(switch_status_t) switch_b64_encode(unsigned char *in, switch_size SWITCH_DECLARE(switch_size_t) switch_b64_decode(char *in, char *out, switch_size_t olen); SWITCH_DECLARE(char *) switch_amp_encode(char *s, char *buf, switch_size_t len); + + +static inline char *switch_print_bits(const unsigned char *byte, char *buf, switch_size_t buflen) +{ + + int i, j = 0, k = 0, l = 0; + + while(k < buflen) { + l = 0; + for (i = 7; i >= 0; i--) { + buf[j++] = (*byte & (1 << i)) ? '1' : '0'; + if (++l % 4 == 0) { + buf[j++] = ' '; + } + } + k++; + byte++; + } + + if (buf[j-1] == ' ') j--; + buf[j++] = '\0'; + return buf; +} + + + + static inline switch_bool_t switch_is_digit_string(const char *s) { diff --git a/src/mod/applications/mod_av/avformat.c b/src/mod/applications/mod_av/avformat.c index 883a5be51f..04d35096e1 100644 --- a/src/mod/applications/mod_av/avformat.c +++ b/src/mod/applications/mod_av/avformat.c @@ -1140,7 +1140,7 @@ SWITCH_STANDARD_APP(record_av_function) switch_core_timer_destroy(&timer); } - switch_core_media_end_video_function(session); + switch_core_media_end_engine_function(session, SWITCH_MEDIA_TYPE_VIDEO); switch_core_session_set_read_codec(session, NULL); switch_core_codec_destroy(&codec); diff --git a/src/mod/applications/mod_commands/mod_commands.c b/src/mod/applications/mod_commands/mod_commands.c index 39bffd58ab..f44b4ab55b 100644 --- a/src/mod/applications/mod_commands/mod_commands.c +++ b/src/mod/applications/mod_commands/mod_commands.c @@ -3066,6 +3066,61 @@ SWITCH_STANDARD_API(uuid_chat) return SWITCH_STATUS_SUCCESS; } +#define UUID_CAPTURE_TEXT_SYNTAX " " +SWITCH_STANDARD_API(uuid_capture_text) +{ + switch_core_session_t *tsession = NULL; + char *uuid = NULL, *onoff = NULL; + + if (!zstr(cmd) && (uuid = strdup(cmd))) { + if ((onoff = strchr(uuid, ' '))) { + *onoff++ = '\0'; + } + } + + if (zstr(uuid) || zstr(onoff)) { + stream->write_function(stream, "-USAGE: %s\n", UUID_CAPTURE_TEXT_SYNTAX); + } else { + if ((tsession = switch_core_session_locate(uuid))) { + switch_ivr_capture_text(tsession, switch_true(onoff)); + } else { + stream->write_function(stream, "-ERR No such channel %s!\n", uuid); + } + } + + switch_safe_free(uuid); + return SWITCH_STATUS_SUCCESS; +} + + +#define UUID_SEND_TEXT_SYNTAX " " +SWITCH_STANDARD_API(uuid_send_text) +{ + switch_core_session_t *tsession = NULL; + char *uuid = NULL, *text = NULL; + + if (!zstr(cmd) && (uuid = strdup(cmd))) { + if ((text = strchr(uuid, ' '))) { + *text++ = '\0'; + } + } + + if (zstr(uuid) || zstr(text)) { + stream->write_function(stream, "-USAGE: %s\n", UUID_SEND_TEXT_SYNTAX); + } else { + if ((tsession = switch_core_session_locate(uuid))) { + switch_core_session_print(tsession, text); + switch_core_session_print(tsession, "\r\n"); + switch_core_session_rwunlock(tsession); + } else { + stream->write_function(stream, "-ERR No such channel %s!\n", uuid); + } + } + + switch_safe_free(uuid); + return SWITCH_STATUS_SUCCESS; +} + #define UUID_DROP_DTMF_SYNTAX " [on | off ] [ mask_digits | mask_file ]" SWITCH_STANDARD_API(uuid_drop_dtmf) { @@ -7197,6 +7252,8 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_commands_load) SWITCH_ADD_API(commands_api_interface, "uuid_broadcast", "Execute dialplan application", uuid_broadcast_function, BROADCAST_SYNTAX); SWITCH_ADD_API(commands_api_interface, "uuid_buglist", "List media bugs on a session", uuid_buglist_function, BUGLIST_SYNTAX); SWITCH_ADD_API(commands_api_interface, "uuid_chat", "Send a chat message", uuid_chat, UUID_CHAT_SYNTAX); + SWITCH_ADD_API(commands_api_interface, "uuid_send_text", "Send text in real-time", uuid_send_text, UUID_SEND_TEXT_SYNTAX); + SWITCH_ADD_API(commands_api_interface, "uuid_capture_text", "start/stop capture_text", uuid_capture_text, UUID_CAPTURE_TEXT_SYNTAX); SWITCH_ADD_API(commands_api_interface, "uuid_codec_debug", "Send codec a debug message", uuid_codec_debug_function, CODEC_DEBUG_SYNTAX); SWITCH_ADD_API(commands_api_interface, "uuid_codec_param", "Send codec a param", uuid_codec_param_function, CODEC_PARAM_SYNTAX); SWITCH_ADD_API(commands_api_interface, "uuid_debug_media", "Debug media", uuid_debug_media_function, DEBUG_MEDIA_SYNTAX); @@ -7376,6 +7433,8 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_commands_load) switch_console_set_complete("add uuid_broadcast ::console::list_uuid"); switch_console_set_complete("add uuid_buglist ::console::list_uuid"); switch_console_set_complete("add uuid_chat ::console::list_uuid"); + switch_console_set_complete("add uuid_send_text ::console::list_uuid"); + switch_console_set_complete("add uuid_capture_text ::console::list_uuid"); switch_console_set_complete("add uuid_codec_debug ::console::list_uuid audio"); switch_console_set_complete("add uuid_codec_debug ::console::list_uuid video"); switch_console_set_complete("add uuid_codec_param ::console::list_uuid audio read"); diff --git a/src/mod/applications/mod_conference/conference_member.c b/src/mod/applications/mod_conference/conference_member.c index b90ab477c5..064d12edac 100644 --- a/src/mod/applications/mod_conference/conference_member.c +++ b/src/mod/applications/mod_conference/conference_member.c @@ -1148,6 +1148,13 @@ switch_status_t conference_member_del(conference_obj_t *conference, conference_m lock_member(member); conference_utils_member_clear_flag(member, MFLAG_INTREE); + + switch_safe_free(member->text_framedata); + member->text_framesize = 0; + if (member->text_buffer) { + switch_buffer_destroy(&member->text_buffer); + } + if (member->rec) { conference->recording_members--; } diff --git a/src/mod/applications/mod_conference/mod_conference.c b/src/mod/applications/mod_conference/mod_conference.c index dbcb80299a..75b2e3b27e 100644 --- a/src/mod/applications/mod_conference/mod_conference.c +++ b/src/mod/applications/mod_conference/mod_conference.c @@ -249,6 +249,42 @@ void *SWITCH_THREAD_FUNC conference_thread_run(switch_thread_t *thread, void *ob floor_holder = conference->floor_holder; + for (imember = conference->members; imember; imember = imember->next) { + if (!zstr(imember->text_framedata)) { + switch_frame_t frame = { 0 }; + char *framedata; + uint32_t framedatalen; + const char *caller_id_name = switch_channel_get_variable(imember->channel, "caller_id_name"); + unsigned char CR[3] = TEXT_UNICODE_LINEFEED; + + + switch_mutex_lock(imember->text_mutex); + + framedatalen = strlen(imember->text_framedata) + strlen(caller_id_name) + 6; + + switch_zmalloc(framedata, framedatalen); + + switch_snprintf(framedata, framedatalen, "%s::\n%s", caller_id_name, imember->text_framedata); + memcpy(framedata + strlen(framedata), CR, sizeof(CR)); + + + frame.data = framedata; + frame.datalen = framedatalen; + + for (omember = conference->members; omember; omember = omember->next) { + if (omember != imember) { + switch_core_session_write_text_frame(omember->session, &frame, 0, 0); + } + } + + free(framedata); + + imember->text_framedata[0] = '\0'; + + switch_mutex_unlock(imember->text_mutex); + } + } + /* Read one frame of audio from each member channel and save it for redistribution */ for (imember = conference->members; imember; imember = imember->next) { uint32_t buf_read = 0; @@ -1610,6 +1646,65 @@ SWITCH_STANDARD_APP(conference_auto_function) } +switch_status_t conference_text_thread_callback(switch_core_session_t *session, switch_frame_t *frame, void *user_data) +{ + conference_member_t *member = (conference_member_t *)user_data; + switch_channel_t *channel = switch_core_session_get_channel(session); + switch_size_t inuse = 0; + + if (!member) return SWITCH_STATUS_FALSE; + + + switch_mutex_lock(member->text_mutex); + if (!member->text_buffer) { + switch_buffer_create_dynamic(&member->text_buffer, 512, 1024, 0); + switch_zmalloc(member->text_framedata, 1024); + member->text_framesize = 1024; + } + + if (frame->data && frame->datalen && !(frame->flags & SFF_CNG)) { + switch_buffer_write(member->text_buffer, frame->data, frame->datalen); + } + + inuse = switch_buffer_inuse(member->text_buffer); + + if (zstr(member->text_framedata) && inuse && (switch_channel_test_flag(channel, CF_TEXT_IDLE) || switch_test_flag(frame, SFF_TEXT_LINE_BREAK))) { + int bytes = 0, ok = 0; + char *p; + + if (inuse + 1 > member->text_framesize) { + void *tmp = malloc(inuse + 1024); + memcpy(tmp, member->text_framedata, member->text_framesize); + + switch_assert(tmp); + + member->text_framesize = inuse + 1024; + + free(member->text_framedata); + member->text_framedata = tmp; + + } + + bytes = switch_buffer_read(member->text_buffer, member->text_framedata, inuse); + *(member->text_framedata + bytes) = '\0'; + + for(p = member->text_framedata; p && *p; p++) { + if (*p > 32 && *p < 127) { + ok++; + } + } + + if (!ok) { + member->text_framedata[0] = '\0'; + } + + } + + switch_mutex_unlock(member->text_mutex); + + return SWITCH_STATUS_SUCCESS; +} + /* Application interface function that is called from the dialplan to join the channel to a conference */ SWITCH_STANDARD_APP(conference_function) { @@ -2123,6 +2218,7 @@ SWITCH_STANDARD_APP(conference_function) switch_mutex_init(&member.fnode_mutex, SWITCH_MUTEX_NESTED, member.pool); switch_mutex_init(&member.audio_in_mutex, SWITCH_MUTEX_NESTED, member.pool); switch_mutex_init(&member.audio_out_mutex, SWITCH_MUTEX_NESTED, member.pool); + switch_mutex_init(&member.text_mutex, SWITCH_MUTEX_NESTED, member.pool); switch_thread_rwlock_create(&member.rwlock, member.pool); if (conference_member_setup_media(&member, conference)) { @@ -2197,6 +2293,7 @@ SWITCH_STANDARD_APP(conference_function) /* Chime in the core video thread */ switch_core_session_set_video_read_callback(session, conference_video_thread_callback, (void *)&member); + switch_core_session_set_text_read_callback(session, conference_text_thread_callback, (void *)&member); if (switch_channel_test_flag(channel, CF_VIDEO_ONLY)) { while(conference_utils_member_test_flag((&member), MFLAG_RUNNING) && switch_channel_ready(channel)) { @@ -2211,6 +2308,7 @@ SWITCH_STANDARD_APP(conference_function) } switch_core_session_set_video_read_callback(session, NULL, NULL); + switch_core_session_set_text_read_callback(session, NULL, NULL); switch_channel_set_private(channel, "_conference_autocall_list_", NULL); diff --git a/src/mod/applications/mod_conference/mod_conference.h b/src/mod/applications/mod_conference/mod_conference.h index 02503f6110..a4aca869fc 100644 --- a/src/mod/applications/mod_conference/mod_conference.h +++ b/src/mod/applications/mod_conference/mod_conference.h @@ -790,6 +790,13 @@ struct conference_member { int reset_media; int flip; int flip_count; + + switch_mutex_t *text_mutex; + switch_buffer_t *text_buffer; + char *text_framedata; + uint32_t text_framesize; + + }; typedef enum { @@ -971,6 +978,7 @@ void conference_video_fnode_check(conference_file_node_t *fnode, int canvas_id); switch_status_t conference_video_set_canvas_bgimg(mcu_canvas_t *canvas, const char *img_path); switch_status_t conference_al_parse_position(al_handle_t *al, const char *data); switch_status_t conference_video_thread_callback(switch_core_session_t *session, switch_frame_t *frame, void *user_data); +switch_status_t conference_text_thread_callback(switch_core_session_t *session, switch_frame_t *frame, void *user_data); void *SWITCH_THREAD_FUNC conference_video_muxing_write_thread_run(switch_thread_t *thread, void *obj); void conference_member_check_agc_levels(conference_member_t *member); void conference_member_clear_avg(conference_member_t *member); diff --git a/src/mod/applications/mod_dptools/mod_dptools.c b/src/mod/applications/mod_dptools/mod_dptools.c index 0545ac07c4..d3f26e33d2 100644 --- a/src/mod/applications/mod_dptools/mod_dptools.c +++ b/src/mod/applications/mod_dptools/mod_dptools.c @@ -1012,6 +1012,10 @@ SWITCH_STANDARD_APP(set_mute_function) } +SWITCH_STANDARD_APP(capture_text_function) +{ + switch_ivr_capture_text(session, switch_true((char *)data)); +} SWITCH_STANDARD_APP(ring_ready_function) { @@ -6127,6 +6131,7 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_dptools_load) SWITCH_ADD_CHAT(chat_interface, "event", event_chat_send); SWITCH_ADD_CHAT(chat_interface, "api", api_chat_send); + SWITCH_ADD_API(api_interface, "strepoch", "Convert a date string into epoch time", strepoch_api_function, ""); SWITCH_ADD_API(api_interface, "page", "Send a file as a page", page_api_function, "(var1=val1,var2=val2)[:_:]"); SWITCH_ADD_API(api_interface, "strmicroepoch", "Convert a date string into micoepoch time", strmicroepoch_api_function, ""); @@ -6216,6 +6221,7 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_dptools_load) SWITCH_ADD_APP(app_interface, "multiunset", "Unset many channel variables", SET_LONG_DESC, multiunset_function, "[^^] ", SAF_SUPPORT_NOMEDIA | SAF_ROUTING_EXEC | SAF_ZOMBIE_EXEC); + SWITCH_ADD_APP(app_interface, "capture_text", "capture text", "capture text", capture_text_function, "", SAF_NONE); SWITCH_ADD_APP(app_interface, "ring_ready", "Indicate Ring_Ready", "Indicate Ring_Ready on a channel.", ring_ready_function, "", SAF_SUPPORT_NOMEDIA); SWITCH_ADD_APP(app_interface, "remove_bugs", "Remove media bugs", "Remove all media bugs from a channel.", remove_bugs_function, "[]", SAF_NONE); SWITCH_ADD_APP(app_interface, "break", "Break", "Set the break flag.", break_function, "", SAF_SUPPORT_NOMEDIA); diff --git a/src/mod/applications/mod_fsv/mod_fsv.c b/src/mod/applications/mod_fsv/mod_fsv.c index e2c6f5af4d..e99022cee8 100644 --- a/src/mod/applications/mod_fsv/mod_fsv.c +++ b/src/mod/applications/mod_fsv/mod_fsv.c @@ -183,7 +183,7 @@ SWITCH_STANDARD_APP(record_fsv_function) switch_mutex_init(&mutex, SWITCH_MUTEX_NESTED, switch_core_session_get_pool(session)); eh.mutex = mutex; eh.fd = fd; - switch_core_media_start_video_function(session, record_video_thread, &eh); + switch_core_media_start_engine_function(session, SWITCH_MEDIA_TYPE_VIDEO, record_video_thread, &eh); } @@ -257,7 +257,7 @@ SWITCH_STANDARD_APP(record_fsv_function) close(fd); } - switch_core_media_end_video_function(session); + switch_core_media_end_engine_function(session, SWITCH_MEDIA_TYPE_VIDEO); switch_core_session_set_read_codec(session, NULL); switch_core_codec_destroy(&codec); @@ -751,7 +751,7 @@ SWITCH_STANDARD_APP(decode_video_function) switch_channel_set_flag(channel, CF_VIDEO_DECODED_READ); - switch_core_media_start_video_function(session, decode_video_thread, &max_pictures); + switch_core_media_start_engine_function(session, SWITCH_MEDIA_TYPE_VIDEO, decode_video_thread, &max_pictures); switch_ivr_play_file(session, NULL, moh, NULL); @@ -762,7 +762,7 @@ SWITCH_STANDARD_APP(decode_video_function) switch_channel_set_variable(channel, SWITCH_CURRENT_APPLICATION_RESPONSE_VARIABLE, "OK"); - switch_core_media_end_video_function(session); + switch_core_media_end_engine_function(session, SWITCH_MEDIA_TYPE_VIDEO); switch_core_session_video_reset(session); } diff --git a/src/mod/endpoints/mod_rtc/mod_rtc.c b/src/mod/endpoints/mod_rtc/mod_rtc.c index 4f964827c8..2ef2bac87f 100644 --- a/src/mod/endpoints/mod_rtc/mod_rtc.c +++ b/src/mod/endpoints/mod_rtc/mod_rtc.c @@ -287,6 +287,8 @@ switch_io_routines_t rtc_io_routines = { /*.state_change */ NULL, /*.read_video_frame */ rtc_read_video_frame, /*.write_video_frame */ rtc_write_video_frame, + /*.read_text_frame */ NULL, + /*.write_text_frame */ NULL, /*.state_run*/ NULL, /*.get_jb*/ rtc_get_jb }; @@ -330,6 +332,7 @@ void rtc_attach_private(switch_core_session_t *session, private_object_t *tech_p switch_core_media_check_dtmf_type(session); switch_channel_set_cap(tech_pvt->channel, CC_JITTERBUFFER); switch_channel_set_cap(tech_pvt->channel, CC_FS_RTP); + switch_channel_set_cap(tech_pvt->channel, CC_IO_OVERRIDE); switch_media_handle_create(&tech_pvt->media_handle, session, &tech_pvt->mparams); switch_core_session_set_private(session, tech_pvt); diff --git a/src/mod/endpoints/mod_sofia/mod_sofia.c b/src/mod/endpoints/mod_sofia/mod_sofia.c index 36ef8b06b9..017150d6ed 100644 --- a/src/mod/endpoints/mod_sofia/mod_sofia.c +++ b/src/mod/endpoints/mod_sofia/mod_sofia.c @@ -62,6 +62,8 @@ static switch_status_t sofia_read_frame(switch_core_session_t *session, switch_f static switch_status_t sofia_write_frame(switch_core_session_t *session, switch_frame_t *frame, switch_io_flag_t flags, int stream_id); static switch_status_t sofia_read_video_frame(switch_core_session_t *session, switch_frame_t **frame, switch_io_flag_t flags, int stream_id); static switch_status_t sofia_write_video_frame(switch_core_session_t *session, switch_frame_t *frame, switch_io_flag_t flags, int stream_id); +static switch_status_t sofia_read_text_frame(switch_core_session_t *session, switch_frame_t **frame, switch_io_flag_t flags, int stream_id); +static switch_status_t sofia_write_text_frame(switch_core_session_t *session, switch_frame_t *frame, switch_io_flag_t flags, int stream_id); static switch_status_t sofia_kill_channel(switch_core_session_t *session, int sig); /* BODY OF THE MODULE */ @@ -918,6 +920,16 @@ static switch_status_t sofia_answer_channel(switch_core_session_t *session) return SWITCH_STATUS_SUCCESS; } +static switch_status_t sofia_read_text_frame(switch_core_session_t *session, switch_frame_t **frame, switch_io_flag_t flags, int stream_id) +{ + return switch_core_media_read_frame(session, frame, flags, stream_id, SWITCH_MEDIA_TYPE_TEXT); +} + +static switch_status_t sofia_write_text_frame(switch_core_session_t *session, switch_frame_t *frame, switch_io_flag_t flags, int stream_id) +{ + return switch_core_media_write_frame(session, frame, flags, stream_id, SWITCH_MEDIA_TYPE_TEXT); +} + static switch_status_t sofia_read_video_frame(switch_core_session_t *session, switch_frame_t **frame, switch_io_flag_t flags, int stream_id) { private_object_t *tech_pvt = (private_object_t *) switch_core_session_get_private(session); @@ -4279,6 +4291,8 @@ switch_io_routines_t sofia_io_routines = { /*.state_change */ NULL, /*.read_video_frame */ sofia_read_video_frame, /*.write_video_frame */ sofia_write_video_frame, + /*.read_text_frame */ sofia_read_text_frame, + /*.write_text_frame */ sofia_write_text_frame, /*.state_run*/ NULL, /*.get_jb*/ sofia_get_jb }; diff --git a/src/mod/endpoints/mod_sofia/rtp.c b/src/mod/endpoints/mod_sofia/rtp.c index ce42dc1875..eafde46716 100644 --- a/src/mod/endpoints/mod_sofia/rtp.c +++ b/src/mod/endpoints/mod_sofia/rtp.c @@ -122,6 +122,8 @@ switch_io_routines_t crtp_io_routines = { /*state_change*/ NULL, /*read_video_frame*/ NULL, /*write_video_frame*/ NULL, + /*read_text_frame*/ NULL, + /*write_text_frame*/ NULL, /*state_run*/ NULL diff --git a/src/mod/endpoints/mod_sofia/sofia_glue.c b/src/mod/endpoints/mod_sofia/sofia_glue.c index 10fe226649..9c493bf9a9 100644 --- a/src/mod/endpoints/mod_sofia/sofia_glue.c +++ b/src/mod/endpoints/mod_sofia/sofia_glue.c @@ -159,6 +159,7 @@ void sofia_glue_attach_private(switch_core_session_t *session, sofia_profile_t * switch_channel_set_cap(tech_pvt->channel, CC_PROXY_MEDIA); switch_channel_set_cap(tech_pvt->channel, CC_JITTERBUFFER); switch_channel_set_cap(tech_pvt->channel, CC_FS_RTP); + switch_channel_set_cap(tech_pvt->channel, CC_RTP_RTT); switch_channel_set_cap(tech_pvt->channel, CC_QUEUEABLE_DTMF_DELAY); diff --git a/src/mod/endpoints/mod_verto/mcast/mcast.c b/src/mod/endpoints/mod_verto/mcast/mcast.c index f15ff6efc5..984d1d4f22 100644 --- a/src/mod/endpoints/mod_verto/mcast/mcast.c +++ b/src/mod/endpoints/mod_verto/mcast/mcast.c @@ -53,7 +53,7 @@ #include #define closesocket(x) close(x) #endif -#include +#include #include "mcast.h" diff --git a/src/mod/endpoints/mod_verto/mod_verto.c b/src/mod/endpoints/mod_verto/mod_verto.c index b839660082..15a5d6825d 100644 --- a/src/mod/endpoints/mod_verto/mod_verto.c +++ b/src/mod/endpoints/mod_verto/mod_verto.c @@ -130,6 +130,10 @@ static switch_bool_t check_name(const char *name) } +static switch_status_t verto_read_text_frame(switch_core_session_t *session, switch_frame_t **frame, switch_io_flag_t flags, int stream_id); +static switch_status_t verto_write_text_frame(switch_core_session_t *session, switch_frame_t *frame, switch_io_flag_t flags, int stream_id); +static void set_text_funcs(switch_core_session_t *session); + static verto_profile_t *find_profile(const char *name); static jsock_t *get_jsock(const char *uuid); @@ -2116,6 +2120,11 @@ switch_endpoint_interface_t *verto_endpoint_interface = NULL; static switch_status_t verto_on_destroy(switch_core_session_t *session) { + verto_pvt_t *tech_pvt = switch_core_session_get_private_class(session, SWITCH_PVT_SECONDARY); + + switch_buffer_destroy(&tech_pvt->text_read_buffer); + switch_buffer_destroy(&tech_pvt->text_write_buffer); + UNPROTECT_INTERFACE(verto_endpoint_interface); return SWITCH_STATUS_SUCCESS; } @@ -2576,6 +2585,7 @@ static int verto_recover_callback(switch_core_session_t *session) } tech_pvt = switch_core_session_alloc(session, sizeof(*tech_pvt)); + tech_pvt->pool = switch_core_session_get_pool(session); tech_pvt->session = session; tech_pvt->channel = channel; tech_pvt->jsock_uuid = (char *) jsock_uuid_str; @@ -3261,7 +3271,7 @@ static void parse_user_vars(cJSON *obj, switch_core_session_t *session) static switch_bool_t verto__info_func(const char *method, cJSON *params, jsock_t *jsock, cJSON **response) { - cJSON *msg = NULL, *dialog = NULL; + cJSON *msg = NULL, *dialog = NULL, *txt = NULL; const char *call_id = NULL, *dtmf = NULL; switch_bool_t r = SWITCH_TRUE; char *proto = VERTO_CHAT_PROTO; @@ -3298,6 +3308,43 @@ static switch_bool_t verto__info_func(const char *method, cJSON *params, jsock_t } } + if ((txt = cJSON_GetObjectItem(params, "txt"))) { + switch_core_session_t *session; + + if ((session = switch_core_session_locate(call_id))) { + verto_pvt_t *tech_pvt = switch_core_session_get_private_class(session, SWITCH_PVT_SECONDARY); + char charbuf[2] = ""; + char *chardata = NULL; + cJSON *data; + + if ((data = cJSON_GetObjectItem(txt, "code"))) { + charbuf[0] = data->valueint; + chardata = charbuf; + } else if ((data = cJSON_GetObjectItem(txt, "chars"))) { + if (data->valuestring) { + chardata = data->valuestring; + } else if (data->valueint) { + charbuf[0] = data->valueint; + chardata = charbuf; + } + } + + + if (chardata) { + switch_mutex_lock(tech_pvt->text_read_mutex); + switch_buffer_write(tech_pvt->text_read_buffer, chardata, strlen(chardata)); + switch_mutex_unlock(tech_pvt->text_read_mutex); + + if ((switch_mutex_trylock(tech_pvt->text_cond_mutex) == SWITCH_STATUS_SUCCESS)) { + switch_thread_cond_signal(tech_pvt->text_cond); + switch_mutex_unlock(tech_pvt->text_cond_mutex); + } + } + switch_core_session_rwunlock(session); + } + + } + if ((msg = cJSON_GetObjectItem(params, "msg"))) { switch_event_t *event; char *to = (char *) cJSON_GetObjectCstr(msg, "to"); @@ -3380,6 +3427,8 @@ static switch_bool_t verto__info_func(const char *method, cJSON *params, jsock_t return r; } + + static switch_bool_t verto__invite_func(const char *method, cJSON *params, jsock_t *jsock, cJSON **response) { cJSON *obj = cJSON_CreateObject(), *screenShare = NULL, *dedEnc = NULL, *mirrorInput, *bandwidth = NULL, *canvas = NULL; @@ -3431,12 +3480,13 @@ static switch_bool_t verto__invite_func(const char *method, cJSON *params, jsock tech_pvt = switch_core_session_alloc(session, sizeof(*tech_pvt)); tech_pvt->session = session; + tech_pvt->pool = switch_core_session_get_pool(session); tech_pvt->channel = channel; tech_pvt->jsock_uuid = switch_core_session_strdup(session, jsock->uuid_str); tech_pvt->r_sdp = switch_core_session_strdup(session, sdp); switch_core_media_set_sdp_codec_string(session, sdp, SDP_TYPE_REQUEST); switch_core_session_set_private_class(session, tech_pvt, SWITCH_PVT_SECONDARY); - + set_text_funcs(session); tech_pvt->call_id = switch_core_session_strdup(session, call_id); if ((tech_pvt->smh = switch_core_session_get_media_handle(session))) { @@ -5042,6 +5092,115 @@ switch_io_routines_t verto_io_routines = { /*.outgoing_channel */ verto_outgoing_channel }; + +switch_io_routines_t verto_io_override = { + /*.outgoing_channel */ NULL, + /*.read_frame */ NULL, + /*.write_frame */ NULL, + /*.kill_channel */ NULL, + /*.send_dtmf */ NULL, + /*.receive_message */ NULL, + /*.receive_event */ NULL, + /*.state_change */ NULL, + /*.read_video_frame */ NULL, + /*.write_video_frame */ NULL, + /*.read_text_frame */ verto_read_text_frame, + /*.write_text_frame */ verto_write_text_frame, + /*.state_run*/ NULL, + /*.get_jb*/ NULL +}; + + +static switch_status_t verto_read_text_frame(switch_core_session_t *session, switch_frame_t **frame, switch_io_flag_t flags, int stream_id) +{ + verto_pvt_t *tech_pvt = switch_core_session_get_private_class(session, SWITCH_PVT_SECONDARY); + switch_status_t status; + + switch_mutex_lock(tech_pvt->text_cond_mutex); + + status = switch_thread_cond_timedwait(tech_pvt->text_cond, tech_pvt->text_cond_mutex, 100000); + switch_mutex_unlock(tech_pvt->text_cond_mutex); + + *frame = &tech_pvt->text_read_frame; + (*frame)->flags = 0; + + switch_mutex_lock(tech_pvt->text_read_mutex); + if (switch_buffer_inuse(tech_pvt->text_read_buffer)) { + status = SWITCH_STATUS_SUCCESS; + tech_pvt->text_read_frame.datalen = switch_buffer_read(tech_pvt->text_read_buffer, tech_pvt->text_read_frame.data, 100); + } else { + (*frame)->flags |= SFF_CNG; + tech_pvt->text_read_frame.datalen = 2; + status = SWITCH_STATUS_BREAK; + } + switch_mutex_unlock(tech_pvt->text_read_mutex); + + + + return status; +} + +static switch_status_t verto_write_text_frame(switch_core_session_t *session, switch_frame_t *frame, switch_io_flag_t flags, int stream_id) +{ + verto_pvt_t *tech_pvt = switch_core_session_get_private_class(session, SWITCH_PVT_SECONDARY); + + switch_mutex_lock(tech_pvt->text_write_mutex); + + + if (frame) { + switch_buffer_write(tech_pvt->text_write_buffer, frame->data, frame->datalen); + } + + if (switch_buffer_inuse(tech_pvt->text_write_buffer)) { + uint32_t datalen; + switch_byte_t data[SWITCH_RTP_MAX_BUF_LEN] = ""; + + if ((datalen = switch_buffer_read(tech_pvt->text_write_buffer, data, 100))) { + cJSON *obj = NULL, *txt = NULL, *params = NULL; + jsock_t *jsock; + + obj = jrpc_new_req("verto.info", tech_pvt->call_id, ¶ms); + txt = json_add_child_obj(params, "txt", NULL); + cJSON_AddItemToObject(txt, "chars", cJSON_CreateString((char *)data)); + + if ((jsock = get_jsock(tech_pvt->jsock_uuid))) { + jsock_queue_event(jsock, &obj, SWITCH_TRUE); + switch_thread_rwlock_unlock(jsock->rwlock); + } else { + cJSON_Delete(obj); + } + } + } + + + switch_mutex_unlock(tech_pvt->text_write_mutex); + + return SWITCH_STATUS_SUCCESS; +} + + + +static void set_text_funcs(switch_core_session_t *session) +{ + if ((switch_core_session_override_io_routines(session, &verto_io_override) == SWITCH_STATUS_SUCCESS)) { + verto_pvt_t *tech_pvt = switch_core_session_get_private_class(session, SWITCH_PVT_SECONDARY); + + tech_pvt->text_read_frame.data = tech_pvt->text_read_frame_data; + + switch_mutex_init(&tech_pvt->text_read_mutex, SWITCH_MUTEX_NESTED, tech_pvt->pool); + switch_mutex_init(&tech_pvt->text_write_mutex, SWITCH_MUTEX_NESTED, tech_pvt->pool); + switch_mutex_init(&tech_pvt->text_cond_mutex, SWITCH_MUTEX_NESTED, tech_pvt->pool); + switch_thread_cond_create(&tech_pvt->text_cond, tech_pvt->pool); + + switch_buffer_create_dynamic(&tech_pvt->text_read_buffer, 512, 1024, 0); + switch_buffer_create_dynamic(&tech_pvt->text_write_buffer, 512, 1024, 0); + + switch_channel_set_flag(switch_core_session_get_channel(session), CF_TEXT); + switch_core_session_start_text_thread(session); + } +} + + static char *verto_get_dial_string(const char *uid, switch_stream_handle_t *rstream) { jsock_t *jsock; @@ -5187,11 +5346,14 @@ static switch_call_cause_t verto_outgoing_channel(switch_core_session_t *session char name[512]; tech_pvt = switch_core_session_alloc(*new_session, sizeof(*tech_pvt)); + tech_pvt->pool = switch_core_session_get_pool(*new_session); tech_pvt->session = *new_session; tech_pvt->channel = channel; tech_pvt->jsock_uuid = switch_core_session_strdup(*new_session, jsock_uuid_str); + switch_core_session_set_private_class(*new_session, tech_pvt, SWITCH_PVT_SECONDARY); - + set_text_funcs(*new_session); + if (session) { switch_channel_t *ochannel = switch_core_session_get_channel(session); diff --git a/src/mod/endpoints/mod_verto/mod_verto.h b/src/mod/endpoints/mod_verto/mod_verto.h index c39c74d681..404529e0bf 100644 --- a/src/mod/endpoints/mod_verto/mod_verto.h +++ b/src/mod/endpoints/mod_verto/mod_verto.h @@ -173,6 +173,7 @@ typedef enum { } tflag_t; typedef struct verto_pvt_s { + switch_memory_pool_t *pool; char *jsock_uuid; char *call_id; char *r_sdp; @@ -184,6 +185,17 @@ typedef struct verto_pvt_s { switch_call_cause_t remote_hangup_cause; time_t detach_time; struct verto_pvt_s *next; + switch_byte_t text_read_frame_data[SWITCH_RTP_MAX_BUF_LEN]; + switch_frame_t text_read_frame; + + switch_thread_cond_t *text_cond; + switch_mutex_t *text_cond_mutex; + switch_mutex_t *text_read_mutex; + switch_mutex_t *text_write_mutex; + + switch_buffer_t *text_read_buffer; + switch_buffer_t *text_write_buffer; + } verto_pvt_t; typedef struct verto_vhost_s { diff --git a/src/switch_buffer.c b/src/switch_buffer.c index b2fd1482fe..01745cc6b1 100644 --- a/src/switch_buffer.c +++ b/src/switch_buffer.c @@ -53,6 +53,12 @@ struct switch_buffer { int32_t loops; }; + +SWITCH_DECLARE(void *) switch_buffer_get_head_pointer(switch_buffer_t *buffer) +{ + return buffer->head; +} + SWITCH_DECLARE(switch_status_t) switch_buffer_reset_partition_data(switch_buffer_t *buffer) { if (!switch_test_flag(buffer, SWITCH_BUFFER_FLAG_PARTITION)) { diff --git a/src/switch_core_media.c b/src/switch_core_media.c index 121c12bef0..06b4d312f8 100644 --- a/src/switch_core_media.c +++ b/src/switch_core_media.c @@ -47,9 +47,16 @@ static void gen_ice(switch_core_session_t *session, switch_media_type_t type, co #define RTCP_MUX #define MAX_CODEC_CHECK_FRAMES 50//x:mod_sofia.h #define MAX_MISMATCH_FRAMES 5//x:mod_sofia.h -#define type2str(type) type == SWITCH_MEDIA_TYPE_VIDEO ? "video" : "audio" +#define type2str(type) type == SWITCH_MEDIA_TYPE_VIDEO ? "video" : (type == SWITCH_MEDIA_TYPE_AUDIO ? "audio" : "text") #define VIDEO_REFRESH_FREQ 1000000 +#define TEXT_TIMER_MS 100 +#define TEXT_TIMER_SAMPLES 10 +#define TEXT_PERIOD_TIMEOUT 3000 +#define MAX_RED_FRAMES 25 +#define RED_PACKET_SIZE 100 + + typedef enum { SMF_INIT = (1 << 0), SMF_READY = (1 << 1), @@ -92,6 +99,24 @@ typedef enum { CRYPTO_MODE_FORBIDDEN } switch_rtp_crypto_mode_t; +struct switch_rtp_text_factory_s { + switch_memory_pool_t *pool; + switch_frame_t text_frame; + int red_level; + switch_byte_t *text_write_frame_data; + switch_frame_t text_write_frame; + switch_buffer_t *write_buffer; + int write_empty; + switch_byte_t *red_buf[MAX_RED_FRAMES]; + int red_bufsize; + int red_buflen[MAX_RED_FRAMES]; + uint32_t red_ts[MAX_RED_FRAMES]; + int red_pos; + int red_max; + switch_timer_t timer; +}; + + typedef struct switch_rtp_engine_s { switch_secure_settings_t ssec[CRYPTO_INVALID+1]; switch_rtp_crypto_key_type_t crypto_type; @@ -176,6 +201,14 @@ typedef struct switch_rtp_engine_s { uint8_t new_dtls; uint32_t sdp_bw; uint8_t reject_avp; + int t140_pt; + int red_pt; + switch_rtp_text_factory_t *tf; + + switch_engine_function_t engine_function; + void *engine_user_data; + int8_t engine_function_running; + } switch_rtp_engine_t; struct switch_media_handle_s { @@ -184,8 +217,8 @@ struct switch_media_handle_s { switch_core_media_flag_t media_flags[SCMF_MAX]; smh_flag_t flags; switch_rtp_engine_t engines[SWITCH_MEDIA_TYPE_TOTAL]; - switch_mutex_t *read_mutex[2]; - switch_mutex_t *write_mutex[2]; + switch_mutex_t *read_mutex[SWITCH_MEDIA_TYPE_TOTAL]; + switch_mutex_t *write_mutex[SWITCH_MEDIA_TYPE_TOTAL]; char *codec_order[SWITCH_MAX_CODECS]; int codec_order_last; const switch_codec_implementation_t *codecs[SWITCH_MAX_CODECS]; @@ -223,9 +256,8 @@ struct switch_media_handle_s { switch_time_t last_codec_refresh; switch_time_t last_video_refresh_req; switch_timer_t video_timer; - switch_video_function_t video_function; - void *video_user_data; - int8_t video_function_running; + + switch_vid_params_t vid_params; switch_file_handle_t *video_read_fh; switch_file_handle_t *video_write_fh; @@ -236,6 +268,9 @@ struct switch_media_handle_s { switch_thread_t *video_write_thread; int video_write_thread_running; + + switch_time_t last_text_frame; + }; static switch_srtp_crypto_suite_t SUITES[CRYPTO_INVALID] = { @@ -381,6 +416,7 @@ SWITCH_DECLARE(void) switch_core_media_pass_zrtp_hash2(switch_core_session_t *al { _switch_core_media_pass_zrtp_hash2(aleg_session, bleg_session, SWITCH_MEDIA_TYPE_AUDIO); _switch_core_media_pass_zrtp_hash2(aleg_session, bleg_session, SWITCH_MEDIA_TYPE_VIDEO); + _switch_core_media_pass_zrtp_hash2(aleg_session, bleg_session, SWITCH_MEDIA_TYPE_TEXT); } @@ -424,19 +460,21 @@ static void switch_core_media_find_zrtp_hash(switch_core_session_t *session, sdp switch_channel_t *channel = switch_core_session_get_channel(session); switch_rtp_engine_t *audio_engine; switch_rtp_engine_t *video_engine; + switch_rtp_engine_t *text_engine; sdp_media_t *m; sdp_attribute_t *attr; - int got_audio = 0, got_video = 0; + int got_audio = 0, got_video = 0, got_text = 0; if (!session->media_handle) return; audio_engine = &session->media_handle->engines[SWITCH_MEDIA_TYPE_AUDIO]; video_engine = &session->media_handle->engines[SWITCH_MEDIA_TYPE_VIDEO]; + text_engine = &session->media_handle->engines[SWITCH_MEDIA_TYPE_VIDEO]; switch_log_printf(SWITCH_CHANNEL_CHANNEL_LOG(channel), SWITCH_LOG_DEBUG1, "Looking for zrtp-hash\n"); for (m = sdp->sdp_media; m; m = m->m_next) { - if (got_audio && got_video) break; + if (got_audio && got_video && got_text) break; if (m->m_port && ((m->m_type == sdp_media_audio && !got_audio) || (m->m_type == sdp_media_video && !got_video))) { for (attr = m->m_attributes; attr; attr = attr->a_next) { @@ -454,6 +492,12 @@ static void switch_core_media_find_zrtp_hash(switch_core_session_t *session, sdp switch_channel_set_variable(channel, "r_sdp_video_zrtp_hash", attr->a_value); video_engine->remote_sdp_zrtp_hash = switch_core_session_strdup(session, attr->a_value); got_video++; + } else if (m->m_type == sdp_media_text) { + switch_log_printf(SWITCH_CHANNEL_CHANNEL_LOG(channel), SWITCH_LOG_DEBUG, + "Found text zrtp-hash; setting r_sdp_video_zrtp_hash=%s\n", attr->a_value); + switch_channel_set_variable(channel, "r_sdp_text_zrtp_hash", attr->a_value); + text_engine->remote_sdp_zrtp_hash = switch_core_session_strdup(session, attr->a_value); + got_text++; } switch_channel_set_flag(channel, CF_ZRTP_HASH); break; @@ -534,6 +578,7 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_check_autoadj(switch_core_sess { switch_rtp_engine_t *a_engine; switch_rtp_engine_t *v_engine; + switch_rtp_engine_t *t_engine; switch_media_handle_t *smh; const char *val; int x = 0; @@ -546,6 +591,7 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_check_autoadj(switch_core_sess a_engine = &smh->engines[SWITCH_MEDIA_TYPE_AUDIO]; v_engine = &smh->engines[SWITCH_MEDIA_TYPE_VIDEO]; + t_engine = &smh->engines[SWITCH_MEDIA_TYPE_TEXT]; if (!switch_media_handle_test_media_flag(smh, SCMF_DISABLE_RTP_AUTOADJ) && !((val = switch_channel_get_variable(session->channel, "disable_rtp_auto_adjust")) && switch_true(val)) && @@ -561,6 +607,11 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_check_autoadj(switch_core_sess switch_rtp_set_flag(v_engine->rtp_session, SWITCH_RTP_FLAG_AUTOADJ); x++; } + + if (t_engine->rtp_session) { + switch_rtp_set_flag(t_engine->rtp_session, SWITCH_RTP_FLAG_AUTOADJ); + x++; + } } return x ? SWITCH_STATUS_SUCCESS : SWITCH_STATUS_FALSE; @@ -758,11 +809,15 @@ SWITCH_DECLARE(payload_map_t *) switch_core_media_add_payload_map(switch_core_se switch_mutex_lock(smh->sdp_mutex); for (pmap = engine->payload_map; pmap && pmap->allocated; pmap = pmap->next) { - exists = (!strcasecmp(name, pmap->iananame) && pmap->pt == pt && (!pmap->rate || rate == pmap->rate) && (!pmap->ptime || pmap->ptime == ptime)); + + if (type == SWITCH_MEDIA_TYPE_TEXT) { + exists = (type == pmap->type && !strcasecmp(name, pmap->iananame) && pmap->pt == pt); + } else { + exists = (type == pmap->type && !strcasecmp(name, pmap->iananame) && pmap->pt == pt && (!pmap->rate || rate == pmap->rate) && (!pmap->ptime || pmap->ptime == ptime)); + } if (exists) { - - if (!zstr(fmtp) && !zstr(pmap->rm_fmtp)) { + if (type != SWITCH_MEDIA_TYPE_TEXT && !zstr(fmtp) && !zstr(pmap->rm_fmtp)) { if (strcmp(pmap->rm_fmtp, fmtp)) { exists = 0; local_pt = pmap->pt; @@ -774,7 +829,6 @@ SWITCH_DECLARE(payload_map_t *) switch_core_media_add_payload_map(switch_core_se } } - if (!exists) { switch_ssize_t hlen = -1; @@ -888,6 +942,9 @@ SWITCH_DECLARE(void) switch_core_session_clear_crypto(switch_core_session_t *ses "srtp_remote_video_crypto_key", "srtp_remote_video_crypto_tag", "srtp_remote_video_crypto_type", + "srtp_remote_text_crypto_key", + "srtp_remote_text_crypto_tag", + "srtp_remote_text_crypto_type", "rtp_secure_media", "rtp_secure_media_inbound", "rtp_secure_media_outbound", @@ -903,6 +960,7 @@ SWITCH_DECLARE(void) switch_core_session_clear_crypto(switch_core_session_t *ses for (i = 0; i < CRYPTO_INVALID; i++) { memset(&smh->engines[SWITCH_MEDIA_TYPE_AUDIO].ssec[i], 0, sizeof(smh->engines[SWITCH_MEDIA_TYPE_AUDIO].ssec[i])); memset(&smh->engines[SWITCH_MEDIA_TYPE_VIDEO].ssec[i], 0, sizeof(smh->engines[SWITCH_MEDIA_TYPE_VIDEO].ssec[i])); + memset(&smh->engines[SWITCH_MEDIA_TYPE_TEXT].ssec[i], 0, sizeof(smh->engines[SWITCH_MEDIA_TYPE_TEXT].ssec[i])); } } @@ -1177,11 +1235,15 @@ static void switch_core_session_get_recovery_crypto_key(switch_core_session_t *s keyvar = "srtp_remote_audio_crypto_key"; tagvar = "srtp_remote_audio_crypto_tag"; ctypevar = "srtp_remote_audio_crypto_type"; - } else { + } else if (type == SWITCH_MEDIA_TYPE_VIDEO) { keyvar = "srtp_remote_video_crypto_key"; tagvar = "srtp_remote_video_crypto_tag"; ctypevar = "srtp_remote_video_crypto_type"; - } + } else if (type == SWITCH_MEDIA_TYPE_TEXT) { + keyvar = "srtp_remote_text_crypto_key"; + tagvar = "srtp_remote_text_crypto_tag"; + ctypevar = "srtp_remote_text_crypto_type"; + } else return; if ((tmp = switch_channel_get_variable(session->channel, keyvar))) { if ((tmp = switch_channel_get_variable(session->channel, ctypevar))) { @@ -1209,8 +1271,12 @@ static void switch_core_session_apply_crypto(switch_core_session_t *session, swi if (type == SWITCH_MEDIA_TYPE_AUDIO) { varname = "rtp_secure_audio_confirmed"; - } else { + } else if (type == SWITCH_MEDIA_TYPE_VIDEO) { varname = "rtp_secure_video_confirmed"; + } else if (type == SWITCH_MEDIA_TYPE_TEXT) { + varname = "rtp_secure_text_confirmed"; + } else { + return; } if (!session->media_handle) return; @@ -1402,6 +1468,10 @@ SWITCH_DECLARE(int) switch_core_session_check_incoming_crypto(switch_core_sessio switch_channel_set_variable(session->channel, "srtp_remote_video_crypto_key", crypto); switch_channel_set_variable_printf(session->channel, "srtp_remote_video_crypto_tag", "%d", crypto_tag); switch_channel_set_variable_printf(session->channel, "srtp_remote_video_crypto_type", "%s", switch_core_media_crypto_type2str(ctype)); + } else if (engine->type == SWITCH_MEDIA_TYPE_TEXT) { + switch_channel_set_variable(session->channel, "srtp_remote_text_crypto_key", crypto); + switch_channel_set_variable_printf(session->channel, "srtp_remote_text_crypto_tag", "%d", crypto_tag); + switch_channel_set_variable_printf(session->channel, "srtp_remote_text_crypto_type", "%s", switch_core_media_crypto_type2str(ctype)); } engine->ssec[engine->crypto_type].crypto_tag = crypto_tag; @@ -1434,6 +1504,9 @@ SWITCH_DECLARE(int) switch_core_session_check_incoming_crypto(switch_core_sessio } else if (engine->type == SWITCH_MEDIA_TYPE_VIDEO) { switch_channel_set_variable(session->channel, "srtp_remote_video_crypto_key", crypto); switch_channel_set_variable_printf(session->channel, "srtp_remote_video_crypto_type", "%s", switch_core_media_crypto_type2str(ctype)); + } else if (engine->type == SWITCH_MEDIA_TYPE_TEXT) { + switch_channel_set_variable(session->channel, "srtp_remote_text_crypto_key", crypto); + switch_channel_set_variable_printf(session->channel, "srtp_remote_text_crypto_type", "%s", switch_core_media_crypto_type2str(ctype)); } engine->ssec[engine->crypto_type].crypto_tag = crypto_tag; @@ -1483,6 +1556,9 @@ SWITCH_DECLARE(void) switch_core_session_check_outgoing_crypto(switch_core_sessi switch_core_media_build_crypto(session->media_handle, SWITCH_MEDIA_TYPE_VIDEO, SWITCH_NO_CRYPTO_TAG, smh->crypto_suite_order[i], SWITCH_RTP_CRYPTO_SEND, 0); + + switch_core_media_build_crypto(session->media_handle, + SWITCH_MEDIA_TYPE_TEXT, SWITCH_NO_CRYPTO_TAG, smh->crypto_suite_order[i], SWITCH_RTP_CRYPTO_SEND, 0); } } @@ -1544,7 +1620,7 @@ static void set_stats(switch_core_session_t *session, switch_media_type_t type, SWITCH_DECLARE(void) switch_core_media_sync_stats(switch_core_session_t *session) { switch_media_handle_t *smh; - switch_rtp_engine_t *a_engine, *v_engine; + switch_rtp_engine_t *a_engine, *v_engine, *t_engine; switch_assert(session); @@ -1554,6 +1630,7 @@ SWITCH_DECLARE(void) switch_core_media_sync_stats(switch_core_session_t *session a_engine = &smh->engines[SWITCH_MEDIA_TYPE_AUDIO]; v_engine = &smh->engines[SWITCH_MEDIA_TYPE_VIDEO]; + t_engine = &smh->engines[SWITCH_MEDIA_TYPE_TEXT]; if (a_engine->rtp_session) { switch_rtp_sync_stats(a_engine->rtp_session); @@ -1563,6 +1640,10 @@ SWITCH_DECLARE(void) switch_core_media_sync_stats(switch_core_session_t *session switch_rtp_sync_stats(v_engine->rtp_session); } + if (t_engine->rtp_session) { + switch_rtp_sync_stats(t_engine->rtp_session); + } + } SWITCH_DECLARE(void) switch_core_media_set_stats(switch_core_session_t *session) @@ -1576,6 +1657,7 @@ SWITCH_DECLARE(void) switch_core_media_set_stats(switch_core_session_t *session) set_stats(session, SWITCH_MEDIA_TYPE_AUDIO, "audio"); set_stats(session, SWITCH_MEDIA_TYPE_VIDEO, "video"); + set_stats(session, SWITCH_MEDIA_TYPE_TEXT, "text"); } @@ -1583,7 +1665,7 @@ SWITCH_DECLARE(void) switch_core_media_set_stats(switch_core_session_t *session) SWITCH_DECLARE(void) switch_media_handle_destroy(switch_core_session_t *session) { switch_media_handle_t *smh; - switch_rtp_engine_t *a_engine, *v_engine; + switch_rtp_engine_t *a_engine, *v_engine;//, *t_engine; switch_assert(session); @@ -1593,6 +1675,7 @@ SWITCH_DECLARE(void) switch_media_handle_destroy(switch_core_session_t *session) a_engine = &smh->engines[SWITCH_MEDIA_TYPE_AUDIO]; v_engine = &smh->engines[SWITCH_MEDIA_TYPE_VIDEO]; + //t_engine = &smh->engines[SWITCH_MEDIA_TYPE_TEXT]; if (smh->video_timer.timer_interface) { @@ -1644,6 +1727,7 @@ SWITCH_DECLARE(switch_status_t) switch_media_handle_create(switch_media_handle_t *smhp = session->media_handle; switch_set_flag(session->media_handle, SMF_INIT); session->media_handle->media_flags[SCMF_RUNNING] = 1; + session->media_handle->engines[SWITCH_MEDIA_TYPE_AUDIO].read_frame.buflen = SWITCH_RTP_MAX_BUF_LEN; session->media_handle->engines[SWITCH_MEDIA_TYPE_AUDIO].type = SWITCH_MEDIA_TYPE_AUDIO; session->media_handle->engines[SWITCH_MEDIA_TYPE_AUDIO].crypto_type = CRYPTO_INVALID; @@ -1652,16 +1736,30 @@ SWITCH_DECLARE(switch_status_t) switch_media_handle_create(switch_media_handle_t session->media_handle->engines[SWITCH_MEDIA_TYPE_AUDIO].ssec[i].crypto_type = i; } + + + session->media_handle->engines[SWITCH_MEDIA_TYPE_TEXT].read_frame.buflen = SWITCH_RTP_MAX_BUF_LEN; + session->media_handle->engines[SWITCH_MEDIA_TYPE_TEXT].type = SWITCH_MEDIA_TYPE_AUDIO; + session->media_handle->engines[SWITCH_MEDIA_TYPE_TEXT].crypto_type = CRYPTO_INVALID; + + for (i = 0; i < CRYPTO_INVALID; i++) { + session->media_handle->engines[SWITCH_MEDIA_TYPE_TEXT].ssec[i].crypto_type = i; + } + + + session->media_handle->engines[SWITCH_MEDIA_TYPE_VIDEO].read_frame.buflen = SWITCH_RTP_MAX_BUF_LEN; session->media_handle->engines[SWITCH_MEDIA_TYPE_VIDEO].type = SWITCH_MEDIA_TYPE_VIDEO; session->media_handle->engines[SWITCH_MEDIA_TYPE_VIDEO].crypto_type = CRYPTO_INVALID; - + switch_channel_set_variable(session->channel, "video_media_flow", "sendrecv"); switch_channel_set_variable(session->channel, "audio_media_flow", "sendrecv"); + switch_channel_set_variable(session->channel, "text_media_flow", "sendrecv"); session->media_handle->engines[SWITCH_MEDIA_TYPE_AUDIO].smode = SWITCH_MEDIA_FLOW_SENDRECV; session->media_handle->engines[SWITCH_MEDIA_TYPE_VIDEO].smode = SWITCH_MEDIA_FLOW_SENDRECV; + session->media_handle->engines[SWITCH_MEDIA_TYPE_TEXT].smode = SWITCH_MEDIA_FLOW_SENDRECV; for (i = 0; i < CRYPTO_INVALID; i++) { session->media_handle->engines[SWITCH_MEDIA_TYPE_VIDEO].ssec[i].crypto_type = i; @@ -1692,14 +1790,25 @@ SWITCH_DECLARE(switch_status_t) switch_media_handle_create(switch_media_handle_t session->media_handle->engines[SWITCH_MEDIA_TYPE_VIDEO].ssrc = (uint32_t) ((intptr_t) &session->media_handle->engines[SWITCH_MEDIA_TYPE_VIDEO] + (uint32_t) time(NULL) / 2); + session->media_handle->engines[SWITCH_MEDIA_TYPE_TEXT].ssrc = + (uint32_t) ((intptr_t) &session->media_handle->engines[SWITCH_MEDIA_TYPE_TEXT] + (uint32_t) time(NULL) / 2); + + + session->media_handle->engines[SWITCH_MEDIA_TYPE_AUDIO].payload_map = switch_core_alloc(session->pool, sizeof(payload_map_t)); session->media_handle->engines[SWITCH_MEDIA_TYPE_AUDIO].cur_payload_map = session->media_handle->engines[SWITCH_MEDIA_TYPE_AUDIO].payload_map; session->media_handle->engines[SWITCH_MEDIA_TYPE_AUDIO].cur_payload_map->current = 1; + session->media_handle->engines[SWITCH_MEDIA_TYPE_VIDEO].payload_map = switch_core_alloc(session->pool, sizeof(payload_map_t)); session->media_handle->engines[SWITCH_MEDIA_TYPE_VIDEO].cur_payload_map = session->media_handle->engines[SWITCH_MEDIA_TYPE_VIDEO].payload_map; session->media_handle->engines[SWITCH_MEDIA_TYPE_VIDEO].cur_payload_map->current = 1; session->media_handle->engines[SWITCH_MEDIA_TYPE_VIDEO].codec_settings.video.try_hardware_encoder = 1; + + session->media_handle->engines[SWITCH_MEDIA_TYPE_TEXT].payload_map = switch_core_alloc(session->pool, sizeof(payload_map_t)); + session->media_handle->engines[SWITCH_MEDIA_TYPE_TEXT].cur_payload_map = session->media_handle->engines[SWITCH_MEDIA_TYPE_AUDIO].payload_map; + session->media_handle->engines[SWITCH_MEDIA_TYPE_TEXT].cur_payload_map->current = 1; + switch_channel_set_flag(session->channel, CF_DTLS_OK); status = SWITCH_STATUS_SUCCESS; @@ -1874,7 +1983,7 @@ static void check_jb(switch_core_session_t *session, const char *input, int32_t { const char *val; switch_media_handle_t *smh; - switch_rtp_engine_t *a_engine = NULL, *v_engine = NULL; + switch_rtp_engine_t *a_engine = NULL, *v_engine = NULL, *t_engine = NULL; switch_assert(session); @@ -1884,6 +1993,7 @@ static void check_jb(switch_core_session_t *session, const char *input, int32_t a_engine = &smh->engines[SWITCH_MEDIA_TYPE_AUDIO]; v_engine = &smh->engines[SWITCH_MEDIA_TYPE_VIDEO]; + t_engine = &smh->engines[SWITCH_MEDIA_TYPE_TEXT]; if (!zstr(input)) { @@ -1935,6 +2045,32 @@ static void check_jb(switch_core_session_t *session, const char *input, int32_t return; } } + + if (t_engine->rtp_session) { + if (!strncasecmp(input, "tbsize:", 7)) { + int frames = 0, max_frames = 0; + s = input + 7; + + frames = atoi(s); + + if ((s = strchr(s, ':')) && *(s+1) != '\0') { + max_frames = atoi(s+1); + } + + if (frames > 0) { + switch_rtp_set_video_buffer_size(t_engine->rtp_session, frames, max_frames); + } + return; + } else if (!strncasecmp(input, "tdebug:", 7)) { + s = input + 7; + + if (s && !strcmp(s, "off")) { + s = NULL; + } + switch_rtp_debug_jitter_buffer(t_engine->rtp_session, s); + return; + } + } } @@ -2134,6 +2270,133 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_read_lock_unlock(switch_core_s } + + +//? +SWITCH_DECLARE(switch_status_t) switch_rtp_text_factory_create(switch_rtp_text_factory_t **tfP, switch_memory_pool_t *pool) +{ + int x; + + *tfP = switch_core_alloc(pool, sizeof(**tfP)); + + switch_buffer_create_dynamic(&(*tfP)->write_buffer, 512, 1024, 0); + (*tfP)->pool = pool; + (*tfP)->text_write_frame_data = switch_core_alloc(pool, SWITCH_RTP_MAX_BUF_LEN); + (*tfP)->text_write_frame.packet = (*tfP)->text_write_frame_data; + (*tfP)->text_write_frame.data = (switch_byte_t *)(*tfP)->text_write_frame.packet + 12; + (*tfP)->text_write_frame.buflen = SWITCH_RTP_MAX_BUF_LEN - 12; + + (*tfP)->red_max = 5; + (*tfP)->red_bufsize = SWITCH_RTP_MAX_BUF_LEN; + + switch_core_timer_init(&(*tfP)->timer, "soft", TEXT_TIMER_MS, TEXT_TIMER_SAMPLES, pool); + + for(x = 0; x < (*tfP)->red_max; x++) { + (*tfP)->red_buf[x] = switch_core_alloc(pool, SWITCH_RTP_MAX_BUF_LEN); + } + + return SWITCH_STATUS_SUCCESS; +} + +SWITCH_DECLARE(switch_status_t) switch_rtp_text_factory_destroy(switch_rtp_text_factory_t **tfP) +{ + switch_core_timer_destroy(&(*tfP)->timer); + switch_buffer_destroy(&(*tfP)->write_buffer); + + return SWITCH_STATUS_SUCCESS;; +} + +#include + +static int get_rtt_red_seq(int want_seq, void *data, switch_size_t datalen, int seq, switch_payload_t *new_payload, void *new_data, uint32_t *new_datalen) +{ + unsigned char *buf = data; + int count = 0; + unsigned char *e = (buf + datalen); + + int len[MAX_RED_FRAMES] = { 0 }; + int pt[MAX_RED_FRAMES] = { 0 }; + int idx = 0, x = 0; + + *new_datalen = datalen; + + *(buf + datalen) = '\0'; + + while (*buf & 0x80) { + if (buf + 3 > e) { + *new_datalen = 0; + return 0; + } + + pt[count] = *buf & 0x7F; + len[count] = (ntohs(*(uint16_t *)(buf + 2)) & 0x03ff); + buf += 4; + count++; + } + + buf++; + + idx = count - (seq - want_seq); + + if (idx < 0) { + *new_datalen = 0; + return 0; + } + + if (!len[idx]) { + *new_datalen = len[idx]; + return 0; + } + + for(x = 0; x < idx; x++) { + buf += len[x]; + } + + *new_datalen = len[idx]; + *new_payload = pt[idx]; + + memcpy(new_data, buf, len[idx]); + + *(((char *)new_data) + len[idx]) = '\0'; + + return 1; + +} + +static void *get_rtt_payload(void *data, switch_size_t datalen, switch_payload_t *new_payload, uint32_t *new_datalen, int *red_level) +{ + unsigned char *buf = data; + int bytes = 0, count = 0, pt = 0, len = 0;//, ts = 0; + unsigned char *e = (buf + datalen); + + *new_datalen = datalen; + *red_level = 1; + + while (*buf & 0x80) { + if (buf + 3 > e) { + *new_datalen = 0; + return NULL; + } + count++; + pt = *buf & 0x7F; + //ts = ntohs(*(uint16_t *)(buf + 1)) >> 2; + len = (ntohs(*(uint16_t *)(buf + 2)) & 0x03ff); + buf += 4; + bytes += len; + } + + *new_datalen = datalen - bytes - 1 - (count *4); + *new_payload = pt; + buf += bytes + 1; + + if (buf > e) { + *new_datalen = 0; + return NULL; + } + + return buf; +} + //? SWITCH_DECLARE(switch_status_t) switch_core_media_read_frame(switch_core_session_t *session, switch_frame_t **frame, switch_io_flag_t flags, int stream_id, switch_media_type_t type) @@ -2156,7 +2419,7 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_read_frame(switch_core_session engine = &smh->engines[type]; - if (!engine->read_codec.implementation || !switch_core_codec_ready(&engine->read_codec)) { + if (type != SWITCH_MEDIA_TYPE_TEXT && (!engine->read_codec.implementation || !switch_core_codec_ready(&engine->read_codec))) { return SWITCH_STATUS_FALSE; } @@ -2176,10 +2439,12 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_read_frame(switch_core_session engine->read_frame.flags = SFF_NONE; engine->read_frame.m = SWITCH_FALSE; engine->read_frame.img = NULL; - + engine->read_frame.payload = 0; + while (smh->media_flags[SCMF_RUNNING] && engine->read_frame.datalen == 0) { engine->read_frame.flags = SFF_NONE; status = switch_rtp_zerocopy_read_frame(engine->rtp_session, &engine->read_frame, flags); + if (status != SWITCH_STATUS_SUCCESS && status != SWITCH_STATUS_BREAK) { if (status == SWITCH_STATUS_TIMEOUT) { @@ -2225,7 +2490,7 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_read_frame(switch_core_session /* re-set codec if necessary */ - if (engine->reset_codec > 0) { + if (type != SWITCH_MEDIA_TYPE_TEXT && engine->reset_codec > 0) { const char *val; int rtp_timeout_sec = 0; int rtp_hold_timeout_sec = 0; @@ -2283,6 +2548,7 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_read_frame(switch_core_session do_cng = 1; } + if (do_cng) { /* return CNG for now */ *frame = &engine->read_frame; @@ -2379,7 +2645,7 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_read_frame(switch_core_session switch_channel_queue_dtmf(session->channel, &dtmf); } - if (engine->read_frame.datalen > 0) { + if (type != SWITCH_MEDIA_TYPE_TEXT && engine->read_frame.datalen > 0) { uint32_t bytes = 0; int frames = 1; @@ -2466,8 +2732,7 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_read_frame(switch_core_session engine->last_seq = engine->read_frame.seq; } else if (smh->media_flags[SCMF_AUTOFIX_TIMING] && is_vbr && switch_rtp_get_jitter_buffer(engine->rtp_session) - && engine->read_frame.timestamp && engine->read_frame.seq) { - + && engine->read_frame.timestamp && engine->read_frame.seq && engine->read_impl.samples_per_second) { uint32_t codec_ms = (int) (engine->read_frame.timestamp - engine->last_ts) / (engine->read_impl.samples_per_second / 1000); @@ -2582,12 +2847,72 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_read_frame(switch_core_session break; } } - + if (engine->read_frame.datalen == 0) { *frame = NULL; } - *frame = &engine->read_frame; + + if (type == SWITCH_MEDIA_TYPE_TEXT && !switch_test_flag((&engine->read_frame), SFF_CNG)) { + if (engine->red_pt) { + unsigned char *p = engine->read_frame.data; + + *(p + engine->read_frame.datalen) = '\0'; + engine->tf->text_frame = engine->read_frame; + + if (switch_test_flag((&engine->read_frame), SFF_PLC)) { + switch_jb_t *jb = switch_core_session_get_jb(session, SWITCH_MEDIA_TYPE_TEXT); + int i = 0; + + engine->tf->text_frame.datalen = 0; + + for (i = 1; i < 3; i++) { + switch_frame_t frame = { 0 }; + uint8_t buf[SWITCH_RTP_MAX_BUF_LEN]; + frame.data = buf; + frame.buflen = sizeof(buf); + + if (switch_jb_peek_frame(jb, 0, engine->read_frame.seq, i, &frame) == SWITCH_STATUS_SUCCESS) { + if (get_rtt_red_seq(engine->read_frame.seq, + frame.data, + frame.datalen, + frame.seq, + &engine->tf->text_frame.payload, + engine->tf->text_frame.data, + &engine->tf->text_frame.datalen)) { + break; + + } + } + + } + + if (engine->tf->text_frame.datalen == 0) { + engine->tf->text_frame.data = "� "; + engine->tf->text_frame.datalen = strlen(engine->tf->text_frame.data); + } + + } else { + if (!(engine->tf->text_frame.data = get_rtt_payload(engine->read_frame.data, + engine->tf->text_frame.datalen, + &engine->tf->text_frame.payload, + &engine->tf->text_frame.datalen, + &engine->tf->red_level))) { + engine->tf->text_frame.datalen = 0; + } + } + + *frame = &engine->tf->text_frame; + + if ((*frame)->datalen == 0) { + (*frame)->flags |= SFF_CNG; + (*frame)->data = ""; + } + } + + } else { + *frame = &engine->read_frame; + } status = SWITCH_STATUS_SUCCESS; @@ -2626,26 +2951,29 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_write_frame(switch_core_sessio return SWITCH_STATUS_SUCCESS; } - while (!(engine->read_codec.implementation && switch_rtp_ready(engine->rtp_session))) { - if (switch_channel_ready(session->channel)) { - switch_yield(10000); - } else { + if (type != SWITCH_MEDIA_TYPE_TEXT) { + + while (!(engine->read_codec.implementation && switch_rtp_ready(engine->rtp_session))) { + if (switch_channel_ready(session->channel)) { + switch_yield(10000); + } else { + return SWITCH_STATUS_GENERR; + } + } + + if (!engine->read_codec.implementation || !switch_core_codec_ready(&engine->read_codec)) { return SWITCH_STATUS_GENERR; } - } - if (!engine->read_codec.implementation || !switch_core_codec_ready(&engine->read_codec)) { - return SWITCH_STATUS_GENERR; - } + if (!switch_test_flag(frame, SFF_CNG) && !switch_test_flag(frame, SFF_PROXY_PACKET)) { + if (engine->read_impl.encoded_bytes_per_packet) { + bytes = engine->read_impl.encoded_bytes_per_packet; + frames = ((int) frame->datalen / bytes); + } else + frames = 1; - if (!switch_test_flag(frame, SFF_CNG) && !switch_test_flag(frame, SFF_PROXY_PACKET)) { - if (engine->read_impl.encoded_bytes_per_packet) { - bytes = engine->read_impl.encoded_bytes_per_packet; - frames = ((int) frame->datalen / bytes); - } else - frames = 1; - - samples = frames * engine->read_impl.samples_per_packet; + samples = frames * engine->read_impl.samples_per_packet; + } } engine->timestamp_send += samples; @@ -3083,7 +3411,7 @@ SWITCH_DECLARE(void) switch_core_media_clear_ice(switch_core_session_t *session) SWITCH_DECLARE(void) switch_core_media_pause(switch_core_session_t *session) { - switch_rtp_engine_t *a_engine, *v_engine; + switch_rtp_engine_t *a_engine, *v_engine, *t_engine; switch_media_handle_t *smh; switch_assert(session); @@ -3094,6 +3422,7 @@ SWITCH_DECLARE(void) switch_core_media_pause(switch_core_session_t *session) a_engine = &smh->engines[SWITCH_MEDIA_TYPE_AUDIO]; v_engine = &smh->engines[SWITCH_MEDIA_TYPE_VIDEO]; + t_engine = &smh->engines[SWITCH_MEDIA_TYPE_TEXT]; if (a_engine->rtp_session) { switch_rtp_set_flag(a_engine->rtp_session, SWITCH_RTP_FLAG_PAUSE); @@ -3102,11 +3431,15 @@ SWITCH_DECLARE(void) switch_core_media_pause(switch_core_session_t *session) if (v_engine->rtp_session) { switch_rtp_set_flag(v_engine->rtp_session, SWITCH_RTP_FLAG_PAUSE); } + + if (t_engine->rtp_session) { + switch_rtp_set_flag(t_engine->rtp_session, SWITCH_RTP_FLAG_PAUSE); + } } SWITCH_DECLARE(void) switch_core_media_resume(switch_core_session_t *session) { - switch_rtp_engine_t *a_engine, *v_engine; + switch_rtp_engine_t *a_engine, *v_engine, *t_engine; switch_media_handle_t *smh; switch_assert(session); @@ -3117,6 +3450,7 @@ SWITCH_DECLARE(void) switch_core_media_resume(switch_core_session_t *session) a_engine = &smh->engines[SWITCH_MEDIA_TYPE_AUDIO]; v_engine = &smh->engines[SWITCH_MEDIA_TYPE_VIDEO]; + t_engine = &smh->engines[SWITCH_MEDIA_TYPE_TEXT]; if (a_engine->rtp_session) { switch_rtp_clear_flag(a_engine->rtp_session, SWITCH_RTP_FLAG_PAUSE); @@ -3125,6 +3459,10 @@ SWITCH_DECLARE(void) switch_core_media_resume(switch_core_session_t *session) if (v_engine->rtp_session) { switch_rtp_clear_flag(v_engine->rtp_session, SWITCH_RTP_FLAG_PAUSE); } + + if (t_engine->rtp_session) { + switch_rtp_clear_flag(t_engine->rtp_session, SWITCH_RTP_FLAG_PAUSE); + } } @@ -3713,13 +4051,13 @@ SWITCH_DECLARE(uint8_t) switch_core_media_negotiate_sdp(switch_core_session_t *s switch_channel_t *channel = switch_core_session_get_channel(session); const char *val; const char *crypto = NULL; - int got_crypto = 0, got_video_crypto = 0, got_audio = 0, saw_audio = 0, got_avp = 0, got_video_avp = 0, got_video_savp = 0, got_savp = 0, got_udptl = 0, got_webrtc = 0; + int got_crypto = 0, got_video_crypto = 0, got_audio = 0, saw_audio = 0, got_avp = 0, got_video_avp = 0, got_video_savp = 0, got_savp = 0, got_udptl = 0, got_webrtc = 0, got_text = 0, got_text_crypto = 0; int scrooge = 0; sdp_parser_t *parser = NULL; sdp_session_t *sdp; const switch_codec_implementation_t **codec_array; int total_codecs; - switch_rtp_engine_t *a_engine, *v_engine; + switch_rtp_engine_t *a_engine, *v_engine, *t_engine; switch_media_handle_t *smh; uint32_t near_rate = 0; const switch_codec_implementation_t *mimp = NULL, *near_match = NULL; @@ -3749,6 +4087,7 @@ SWITCH_DECLARE(uint8_t) switch_core_media_negotiate_sdp(switch_core_session_t *s a_engine = &smh->engines[SWITCH_MEDIA_TYPE_AUDIO]; v_engine = &smh->engines[SWITCH_MEDIA_TYPE_VIDEO]; + t_engine = &smh->engines[SWITCH_MEDIA_TYPE_TEXT]; codec_array = smh->codecs; total_codecs = smh->mparams->num_codecs; @@ -3843,6 +4182,7 @@ SWITCH_DECLARE(uint8_t) switch_core_media_negotiate_sdp(switch_core_session_t *s check_ice(smh, SWITCH_MEDIA_TYPE_AUDIO, sdp, NULL); check_ice(smh, SWITCH_MEDIA_TYPE_VIDEO, sdp, NULL); + check_ice(smh, SWITCH_MEDIA_TYPE_TEXT, sdp, NULL); if ((sdp->sdp_connection && sdp->sdp_connection->c_address && !strcmp(sdp->sdp_connection->c_address, "0.0.0.0"))) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "RFC2543 from March 1999 called; They want their 0.0.0.0 hold method back.....\n"); @@ -4629,6 +4969,102 @@ SWITCH_DECLARE(uint8_t) switch_core_media_negotiate_sdp(switch_core_session_t *s } } + } else if (switch_channel_var_true(session->channel, "rtp_enable_text") && !got_text && m->m_type == sdp_media_text && m->m_port) { + sdp_rtpmap_t *map; + payload_map_t *red_pmap = NULL; + + + connection = sdp->sdp_connection; + if (m->m_connections) { + connection = m->m_connections; + } + + if (!connection) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Cannot find a c= line in the sdp at media or session level!\n"); + match = 0; + break; + } + + switch_channel_set_variable(session->channel, "text_possible", "true"); + switch_channel_set_flag(session->channel, CF_TEXT_SDP_RECVD); + switch_channel_set_flag(session->channel, CF_TEXT_POSSIBLE); + + got_text++; + + for (map = m->m_rtpmaps; map; map = map->rm_next) { + payload_map_t *pmap; + + pmap = switch_core_media_add_payload_map(session, + SWITCH_MEDIA_TYPE_TEXT, + map->rm_encoding, + NULL, + NULL, + SDP_TYPE_REQUEST, + map->rm_pt, + 1000, + 0, + 1, + SWITCH_TRUE); + + + pmap->remote_sdp_ip = switch_core_session_strdup(session, (char *) connection->c_address); + pmap->remote_sdp_port = (switch_port_t) m->m_port; + pmap->rm_fmtp = switch_core_session_strdup(session, (char *) mmap->rm_fmtp); + + pmap->agreed_pt = (switch_payload_t) map->rm_pt; + pmap->recv_pt = (switch_payload_t) map->rm_pt; + + + t_engine->cur_payload_map = pmap; + + if (!strcasecmp(map->rm_encoding, "red")) { + red_pmap = pmap; + } + } + + t_engine->cur_payload_map = red_pmap; + + for (attr = m->m_attributes; attr; attr = attr->a_next) { + if (!strcasecmp(attr->a_name, "rtcp") && attr->a_value) { + switch_channel_set_variable(session->channel, "sip_remote_text_rtcp_port", attr->a_value); + + } else if (!got_text_crypto && !strcasecmp(attr->a_name, "crypto") && !zstr(attr->a_value)) { + int crypto_tag; + + if (!(smh->mparams->ndlb & SM_NDLB_ALLOW_CRYPTO_IN_AVP) && + !switch_true(switch_channel_get_variable(session->channel, "rtp_allow_crypto_in_avp"))) { + if (m->m_proto != sdp_proto_srtp && !got_webrtc) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "a=crypto in RTP/AVP, refer to rfc3711\n"); + match = 0; + goto done; + } + } + + crypto = attr->a_value; + crypto_tag = atoi(crypto); + + got_text_crypto = switch_core_session_check_incoming_crypto(session, + "rtp_has_text_crypto", + SWITCH_MEDIA_TYPE_TEXT, crypto, crypto_tag, sdp_type); + + } + } + + + //map->rm_encoding + //map->rm_fmtp + //map->rm_pt + //t_engine->cur_payload_map = pmap; + + t_engine->codec_negotiated = 1; + + if (!t_engine->local_sdp_port) { + switch_core_media_choose_port(session, SWITCH_MEDIA_TYPE_TEXT, 1); + } + + check_ice(smh, SWITCH_MEDIA_TYPE_TEXT, sdp, m); + //parse rtt + } else if (m->m_type == sdp_media_video && m->m_port) { sdp_rtpmap_t *map; const char *rm_encoding; @@ -5433,6 +5869,138 @@ SWITCH_DECLARE(void) switch_core_autobind_cpu(void) } + + +static void *SWITCH_THREAD_FUNC text_helper_thread(switch_thread_t *thread, void *obj) +{ + struct media_helper *mh = obj; + switch_core_session_t *session = mh->session; + switch_channel_t *channel = switch_core_session_get_channel(session); + switch_status_t status; + switch_frame_t *read_frame = NULL; + switch_media_handle_t *smh; + switch_rtp_engine_t *t_engine = NULL; + unsigned char CR[] = TEXT_UNICODE_LINEFEED; + switch_frame_t cr_frame = { 0 }; + + if (!(smh = session->media_handle)) { + return NULL; + } + + cr_frame.data = CR; + cr_frame.datalen = 3; + + t_engine = &smh->engines[SWITCH_MEDIA_TYPE_TEXT]; + t_engine->thread_id = switch_thread_self(); + + switch_core_session_read_lock(session); + + mh->up = 1; + + switch_core_media_check_dtls(session, SWITCH_MEDIA_TYPE_TEXT); + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%s Text thread started.\n", switch_channel_get_name(session->channel)); + + switch_core_session_write_text_frame(session, &cr_frame, 0, 0); + + while (switch_channel_up_nosig(channel)) { + + if (t_engine->engine_function) { + int run = 0; + + switch_mutex_lock(smh->control_mutex); + if (t_engine->engine_function_running == 0) { + t_engine->engine_function_running = 1; + run = 1; + } + switch_mutex_unlock(smh->control_mutex); + + if (run) { + t_engine->engine_function(session, t_engine->engine_user_data); + switch_mutex_lock(smh->control_mutex); + t_engine->engine_function = NULL; + t_engine->engine_user_data = NULL; + t_engine->engine_function_running = 0; + switch_mutex_unlock(smh->control_mutex); + } + } + + if (!switch_channel_test_flag(session->channel, CF_TEXT_PASSIVE)) { + + status = switch_core_session_read_text_frame(session, &read_frame, SWITCH_IO_FLAG_NONE, 0); + + if (!SWITCH_READ_ACCEPTABLE(status)) { + switch_cond_next(); + continue; + } + + if (!switch_test_flag(read_frame, SFF_CNG)) { + if (switch_channel_test_flag(session->channel, CF_TEXT_ECHO)) { + switch_core_session_write_text_frame(session, read_frame, 0, 0); + } + } + } + + switch_core_session_write_text_frame(session, NULL, 0, 0); + + + } + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%s Text thread ended\n", switch_channel_get_name(session->channel)); + + switch_core_session_rwunlock(session); + + mh->up = 0; + return NULL; +} + + +SWITCH_DECLARE(switch_status_t) switch_core_session_start_text_thread(switch_core_session_t *session) +{ + switch_threadattr_t *thd_attr = NULL; + switch_memory_pool_t *pool = switch_core_session_get_pool(session); + switch_rtp_engine_t *t_engine = NULL; + switch_media_handle_t *smh; + + if (!switch_channel_test_flag(session->channel, CF_TEXT)) { + return SWITCH_STATUS_NOTIMPL; + } + + if (!(smh = session->media_handle)) { + return SWITCH_STATUS_FALSE; + } + + t_engine = &smh->engines[SWITCH_MEDIA_TYPE_TEXT]; + + switch_mutex_lock(smh->control_mutex); + + if (t_engine->media_thread) { + switch_mutex_unlock(smh->control_mutex); + return SWITCH_STATUS_FALSE; + } + + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "%s Starting Text thread\n", switch_core_session_get_name(session)); + + if (t_engine->rtp_session) { + switch_rtp_set_default_payload(t_engine->rtp_session, t_engine->cur_payload_map->agreed_pt); + } + + t_engine->mh.session = session; + switch_threadattr_create(&thd_attr, pool); + switch_threadattr_stacksize_set(thd_attr, SWITCH_THREAD_STACKSIZE); + + switch_thread_cond_create(&t_engine->mh.cond, pool); + switch_mutex_init(&t_engine->mh.cond_mutex, SWITCH_MUTEX_NESTED, pool); + //switch_mutex_init(&t_engine->mh.file_read_mutex, SWITCH_MUTEX_NESTED, pool); + //switch_mutex_init(&t_engine->mh.file_write_mutex, SWITCH_MUTEX_NESTED, pool); + //switch_mutex_init(&smh->read_mutex[SWITCH_MEDIA_TYPE_TEXT], SWITCH_MUTEX_NESTED, pool); + //switch_mutex_init(&smh->write_mutex[SWITCH_MEDIA_TYPE_TEXT], SWITCH_MUTEX_NESTED, pool); + switch_thread_create(&t_engine->media_thread, thd_attr, text_helper_thread, &t_engine->mh, switch_core_session_get_pool(session)); + + switch_mutex_unlock(smh->control_mutex); + return SWITCH_STATUS_SUCCESS; +} + static void *SWITCH_THREAD_FUNC video_helper_thread(switch_thread_t *thread, void *obj) { struct media_helper *mh = obj; @@ -5522,23 +6090,23 @@ static void *SWITCH_THREAD_FUNC video_helper_thread(switch_thread_t *thread, voi switch_yield(10000); continue; } - - if (smh->video_function) { + + if (v_engine->engine_function) { int run = 0; switch_mutex_lock(smh->control_mutex); - if (smh->video_function_running == 0) { - smh->video_function_running = 1; + if (v_engine->engine_function_running == 0) { + v_engine->engine_function_running = 1; run = 1; } switch_mutex_unlock(smh->control_mutex); if (run) { - smh->video_function(session, smh->video_user_data); + v_engine->engine_function(session, v_engine->engine_user_data); switch_mutex_lock(smh->control_mutex); - smh->video_function = NULL; - smh->video_user_data = NULL; - smh->video_function_running = 0; + v_engine->engine_function = NULL; + v_engine->engine_user_data = NULL; + v_engine->engine_function_running = 0; switch_mutex_unlock(smh->control_mutex); } } @@ -5635,56 +6203,71 @@ SWITCH_DECLARE(switch_status_t) switch_core_session_start_video_thread(switch_co return SWITCH_STATUS_SUCCESS; } -SWITCH_DECLARE(void) switch_core_media_start_video_function(switch_core_session_t *session, switch_video_function_t video_function, void *user_data) +SWITCH_DECLARE(void) switch_core_media_start_engine_function(switch_core_session_t *session, switch_media_type_t type, switch_engine_function_t engine_function, void *user_data) { switch_media_handle_t *smh; + switch_rtp_engine_t *engine; if (!(smh = session->media_handle)) { return; } - switch_core_session_start_video_thread(session); + engine = &smh->engines[type]; + + if (type == SWITCH_MEDIA_TYPE_VIDEO) { + switch_core_session_start_video_thread(session); + } + + if (type == SWITCH_MEDIA_TYPE_TEXT) { + switch_core_session_start_text_thread(session); + } switch_mutex_lock(smh->control_mutex); - if (!smh->video_function_running) { - smh->video_function = video_function; - smh->video_user_data = user_data; + if (!engine->engine_function_running) { + engine->engine_function = engine_function; + engine->engine_user_data = user_data; switch_core_session_video_reset(session); } switch_mutex_unlock(smh->control_mutex); } -SWITCH_DECLARE(int) switch_core_media_check_video_function(switch_core_session_t *session) +SWITCH_DECLARE(int) switch_core_media_check_engine_function(switch_core_session_t *session, switch_media_type_t type) { switch_media_handle_t *smh; int r; + switch_rtp_engine_t *engine; if (!(smh = session->media_handle)) { return 0; } + + engine = &smh->engines[type]; switch_mutex_lock(smh->control_mutex); - r = (smh->video_function_running > 0); + r = (engine->engine_function_running > 0); switch_mutex_unlock(smh->control_mutex); return r; } -SWITCH_DECLARE(void) switch_core_media_end_video_function(switch_core_session_t *session) +SWITCH_DECLARE(void) switch_core_media_end_engine_function(switch_core_session_t *session, switch_media_type_t type) { switch_media_handle_t *smh; + switch_rtp_engine_t *engine; if (!(smh = session->media_handle)) { return; } + + engine = &smh->engines[type]; switch_mutex_lock(smh->control_mutex); - if (smh->video_function_running > 0) { - smh->video_function_running = -1; + if (engine->engine_function_running > 0) { + engine->engine_function_running = -1; } switch_mutex_unlock(smh->control_mutex); - while(smh->video_function_running != 0) { + while(engine->engine_function_running != 0) { switch_yield(10000); } } @@ -5732,11 +6315,12 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_proxy_remote_addr(switch_core_ char rip[RA_PTR_LEN] = ""; char rp[RA_PTR_LEN] = ""; char rvp[RA_PTR_LEN] = ""; - char *p, *ip_ptr = NULL, *port_ptr = NULL, *vid_port_ptr = NULL, *pe; + char rtp[RA_PTR_LEN] = ""; + char *p, *ip_ptr = NULL, *port_ptr = NULL, *vid_port_ptr = NULL, *text_port_ptr = NULL, *pe; int x; const char *val; switch_status_t status = SWITCH_STATUS_FALSE; - switch_rtp_engine_t *a_engine, *v_engine; + switch_rtp_engine_t *a_engine, *v_engine, *t_engine; switch_media_handle_t *smh; switch_assert(session); @@ -5747,6 +6331,7 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_proxy_remote_addr(switch_core_ a_engine = &smh->engines[SWITCH_MEDIA_TYPE_AUDIO]; v_engine = &smh->engines[SWITCH_MEDIA_TYPE_VIDEO]; + t_engine = &smh->engines[SWITCH_MEDIA_TYPE_TEXT]; if (zstr(sdp_str)) { sdp_str = smh->mparams->remote_sdp_str; @@ -5776,6 +6361,10 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_proxy_remote_addr(switch_core_ vid_port_ptr = p + 8; } + if ((p = (char *) switch_stristr("m=text ", sdp_str))) { + text_port_ptr = p + 7; + } + if (!(ip_ptr && port_ptr)) { goto end; } @@ -5811,6 +6400,16 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_proxy_remote_addr(switch_core_ } } + p = text_port_ptr; + x = 0; + while (x < sizeof(rtp) - 1 && p && *p && (*p >= '0' && *p <= '9')) { + rtp[x++] = *p; + p++; + if (p >= pe) { + goto end; + } + } + if (!(*rip && *rp)) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "invalid SDP\n"); goto end; @@ -5826,6 +6425,13 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_proxy_remote_addr(switch_core_ switch_channel_set_flag(session->channel, CF_VIDEO); } + if (*rtp) { + t_engine->cur_payload_map->remote_sdp_ip = switch_core_session_strdup(session, rip); + t_engine->cur_payload_map->remote_sdp_port = (switch_port_t) atoi(rtp); + switch_channel_set_flag(session->channel, CF_TEXT); + switch_channel_set_flag(session->channel, CF_TEXT_POSSIBLE); + } + if (v_engine->cur_payload_map->remote_sdp_ip && v_engine->cur_payload_map->remote_sdp_port) { if (!strcmp(v_engine->cur_payload_map->remote_sdp_ip, rip) && atoi(rvp) == v_engine->cur_payload_map->remote_sdp_port) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Remote video address:port [%s:%d] has not changed.\n", @@ -5864,6 +6470,44 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_proxy_remote_addr(switch_core_ } } + if (t_engine->cur_payload_map->remote_sdp_ip && t_engine->cur_payload_map->remote_sdp_port) { + if (!strcmp(t_engine->cur_payload_map->remote_sdp_ip, rip) && atoi(rvp) == t_engine->cur_payload_map->remote_sdp_port) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Remote text address:port [%s:%d] has not changed.\n", + t_engine->cur_payload_map->remote_sdp_ip, t_engine->cur_payload_map->remote_sdp_port); + } else { + switch_channel_set_flag(session->channel, CF_TEXT); + switch_channel_set_flag(session->channel, CF_TEXT_POSSIBLE); + if (switch_rtp_ready(t_engine->rtp_session)) { + const char *rport = NULL; + switch_port_t remote_rtcp_port = t_engine->remote_rtcp_port; + + if (!remote_rtcp_port) { + if ((rport = switch_channel_get_variable(session->channel, "rtp_remote_text_rtcp_port"))) { + remote_rtcp_port = (switch_port_t)atoi(rport); + } + } + + + if (switch_rtp_set_remote_address(t_engine->rtp_session, t_engine->cur_payload_map->remote_sdp_ip, + t_engine->cur_payload_map->remote_sdp_port, remote_rtcp_port, SWITCH_TRUE, &err) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "TEXT RTP REPORTS ERROR: [%s]\n", err); + } else { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "TEXT RTP CHANGING DEST TO: [%s:%d]\n", + t_engine->cur_payload_map->remote_sdp_ip, t_engine->cur_payload_map->remote_sdp_port); + if (!switch_media_handle_test_media_flag(smh, SCMF_DISABLE_RTP_AUTOADJ) && !switch_channel_test_flag(session->channel, CF_PROXY_MODE) && + !((val = switch_channel_get_variable(session->channel, "disable_rtp_auto_adjust")) && switch_true(val)) && + !switch_channel_test_flag(session->channel, CF_AVPF)) { + /* Reactivate the NAT buster flag. */ + switch_rtp_set_flag(t_engine->rtp_session, SWITCH_RTP_FLAG_AUTOADJ); + } + if (switch_media_handle_test_media_flag(smh, SCMF_AUTOFIX_TIMING)) { + t_engine->check_frames = 0; + } + } + } + } + } + if (switch_rtp_ready(a_engine->rtp_session)) { char *remote_host = switch_rtp_get_remote_host(a_engine->rtp_session); switch_port_t remote_port = switch_rtp_get_remote_port(a_engine->rtp_session); @@ -6113,16 +6757,19 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_choose_port(switch_core_sessio engine->adv_sdp_port = sdp_port; engine->adv_sdp_ip = smh->mparams->adv_sdp_audio_ip = smh->mparams->extrtpip = switch_core_session_strdup(session, use_ip); - if (type == SWITCH_MEDIA_TYPE_AUDIO) { switch_channel_set_variable(session->channel, SWITCH_LOCAL_MEDIA_IP_VARIABLE, engine->local_sdp_ip); switch_channel_set_variable_printf(session->channel, SWITCH_LOCAL_MEDIA_PORT_VARIABLE, "%d", sdp_port); switch_channel_set_variable(session->channel, SWITCH_ADVERTISED_MEDIA_IP_VARIABLE, engine->adv_sdp_ip); - } else { + } else if (type == SWITCH_MEDIA_TYPE_VIDEO) { switch_channel_set_variable(session->channel, SWITCH_LOCAL_VIDEO_IP_VARIABLE, engine->adv_sdp_ip); switch_channel_set_variable_printf(session->channel, SWITCH_LOCAL_VIDEO_PORT_VARIABLE, "%d", sdp_port); + } else if (type == SWITCH_MEDIA_TYPE_TEXT) { + switch_channel_set_variable(session->channel, SWITCH_LOCAL_TEXT_IP_VARIABLE, engine->adv_sdp_ip); + switch_channel_set_variable_printf(session->channel, SWITCH_LOCAL_TEXT_PORT_VARIABLE, "%d", sdp_port); } + return SWITCH_STATUS_SUCCESS; } @@ -6161,7 +6808,7 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_choose_ports(switch_core_sessi //? SWITCH_DECLARE(void) switch_core_media_deactivate_rtp(switch_core_session_t *session) { - switch_rtp_engine_t *a_engine, *v_engine; + switch_rtp_engine_t *a_engine, *v_engine, *t_engine; switch_media_handle_t *smh; switch_assert(session); @@ -6172,6 +6819,11 @@ SWITCH_DECLARE(void) switch_core_media_deactivate_rtp(switch_core_session_t *ses a_engine = &smh->engines[SWITCH_MEDIA_TYPE_AUDIO]; v_engine = &smh->engines[SWITCH_MEDIA_TYPE_VIDEO]; + t_engine = &smh->engines[SWITCH_MEDIA_TYPE_TEXT]; + + if (t_engine->tf) { + switch_rtp_text_factory_destroy(&t_engine->tf); + } if (v_engine->media_thread) { switch_status_t st; @@ -6196,6 +6848,29 @@ SWITCH_DECLARE(void) switch_core_media_deactivate_rtp(switch_core_session_t *ses } + if (t_engine->media_thread) { + switch_status_t st; + + t_engine->mh.up = 0; + switch_thread_join(&st, t_engine->media_thread); + t_engine->media_thread = NULL; + } + + + if (t_engine->rtp_session) { + switch_rtp_destroy(&t_engine->rtp_session); + } else if (t_engine->local_sdp_port) { + switch_rtp_release_port(smh->mparams->rtpip, t_engine->local_sdp_port); + } + + + if (t_engine->local_sdp_port > 0 && !zstr(smh->mparams->remote_ip) && + switch_core_media_check_nat(smh, smh->mparams->remote_ip)) { + switch_nat_del_mapping((switch_port_t) t_engine->local_sdp_port, SWITCH_NAT_UDP); + switch_nat_del_mapping((switch_port_t) t_engine->local_sdp_port + 1, SWITCH_NAT_UDP); + } + + if (a_engine->rtp_session) { switch_rtp_destroy(&a_engine->rtp_session); } else if (a_engine->local_sdp_port) { @@ -6351,7 +7026,7 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_activate_rtp(switch_core_sessi char tmp[50]; char *timer_name = NULL; const char *var; - switch_rtp_engine_t *a_engine, *v_engine; + switch_rtp_engine_t *a_engine, *v_engine, *t_engine; switch_media_handle_t *smh; switch_assert(session); @@ -6362,6 +7037,7 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_activate_rtp(switch_core_sessi a_engine = &smh->engines[SWITCH_MEDIA_TYPE_AUDIO]; v_engine = &smh->engines[SWITCH_MEDIA_TYPE_VIDEO]; + t_engine = &smh->engines[SWITCH_MEDIA_TYPE_TEXT]; if (switch_channel_down(session->channel)) { @@ -6389,6 +7065,10 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_activate_rtp(switch_core_sessi goto video; } + if (switch_channel_test_flag(session->channel, CF_TEXT_POSSIBLE) && !switch_rtp_ready(t_engine->rtp_session)) { + goto text; + } + status = SWITCH_STATUS_SUCCESS; goto end; } @@ -6826,9 +7506,313 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_activate_rtp(switch_core_sessi + text: + + + + if (switch_channel_test_flag(session->channel, CF_TEXT_POSSIBLE) && t_engine->cur_payload_map->rm_encoding && t_engine->cur_payload_map->remote_sdp_port) { + /******************************************************************************************/ + if (t_engine->rtp_session && switch_channel_test_flag(session->channel, CF_REINVITE)) { + //const char *ip = switch_channel_get_variable(session->channel, SWITCH_LOCAL_MEDIA_IP_VARIABLE); + //const char *port = switch_channel_get_variable(session->channel, SWITCH_LOCAL_MEDIA_PORT_VARIABLE); + char *remote_host = switch_rtp_get_remote_host(t_engine->rtp_session); + switch_port_t remote_port = switch_rtp_get_remote_port(t_engine->rtp_session); + + + + if (remote_host && remote_port && !strcmp(remote_host, t_engine->cur_payload_map->remote_sdp_ip) && remote_port == t_engine->cur_payload_map->remote_sdp_port) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Text params are unchanged for %s.\n", + switch_channel_get_name(session->channel)); + t_engine->cur_payload_map->negotiated = 1; + goto text_up; + } else { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Text params changed for %s from %s:%d to %s:%d\n", + switch_channel_get_name(session->channel), + remote_host, remote_port, t_engine->cur_payload_map->remote_sdp_ip, t_engine->cur_payload_map->remote_sdp_port); + } + } + + if (!switch_channel_test_flag(session->channel, CF_PROXY_MEDIA)) { + if (switch_rtp_ready(t_engine->rtp_session)) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, + "TEXT RTP [%s] %s port %d -> %s port %d codec: %u\n", switch_channel_get_name(session->channel), + t_engine->local_sdp_ip, t_engine->local_sdp_port, t_engine->cur_payload_map->remote_sdp_ip, + t_engine->cur_payload_map->remote_sdp_port, t_engine->cur_payload_map->agreed_pt); + + switch_rtp_set_default_payload(t_engine->rtp_session, t_engine->cur_payload_map->agreed_pt); + } + } + + switch_snprintf(tmp, sizeof(tmp), "%d", t_engine->local_sdp_port); + switch_channel_set_variable(session->channel, SWITCH_LOCAL_TEXT_IP_VARIABLE, a_engine->adv_sdp_ip); + switch_channel_set_variable(session->channel, SWITCH_LOCAL_TEXT_PORT_VARIABLE, tmp); + + + if (t_engine->rtp_session && switch_channel_test_flag(session->channel, CF_REINVITE)) { + const char *rport = NULL; + switch_port_t remote_rtcp_port = t_engine->remote_rtcp_port; + + //switch_channel_clear_flag(session->channel, CF_REINVITE); + + if (!remote_rtcp_port) { + if ((rport = switch_channel_get_variable(session->channel, "rtp_remote_text_rtcp_port"))) { + remote_rtcp_port = (switch_port_t)atoi(rport); + } + } + + if (switch_rtp_set_remote_address + (t_engine->rtp_session, t_engine->cur_payload_map->remote_sdp_ip, t_engine->cur_payload_map->remote_sdp_port, remote_rtcp_port, SWITCH_TRUE, + &err) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "TEXT RTP REPORTS ERROR: [%s]\n", err); + } else { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "TEXT RTP CHANGING DEST TO: [%s:%d]\n", + t_engine->cur_payload_map->remote_sdp_ip, t_engine->cur_payload_map->remote_sdp_port); + if (!switch_media_handle_test_media_flag(smh, SCMF_DISABLE_RTP_AUTOADJ) && !switch_channel_test_flag(session->channel, CF_AVPF) && + !((val = switch_channel_get_variable(session->channel, "disable_rtp_auto_adjust")) && switch_true(val))) { + /* Reactivate the NAT buster flag. */ + switch_rtp_set_flag(t_engine->rtp_session, SWITCH_RTP_FLAG_AUTOADJ); + } + + } + goto text_up; + } + + if (switch_channel_test_flag(session->channel, CF_PROXY_MEDIA)) { + switch_core_media_proxy_remote_addr(session, NULL); + + memset(flags, 0, sizeof(flags)); + flags[SWITCH_RTP_FLAG_PROXY_MEDIA]++; + flags[SWITCH_RTP_FLAG_DATAWAIT]++; + + if (!switch_media_handle_test_media_flag(smh, SCMF_DISABLE_RTP_AUTOADJ) && !switch_channel_test_flag(session->channel, CF_AVPF) && + !((val = switch_channel_get_variable(session->channel, "disable_rtp_auto_adjust")) && switch_true(val))) { + flags[SWITCH_RTP_FLAG_AUTOADJ]++; + } + timer_name = NULL; + + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, + "PROXY TEXT RTP [%s] %s:%d->%s:%d codec: %u ms: %d\n", + switch_channel_get_name(session->channel), + a_engine->cur_payload_map->remote_sdp_ip, + t_engine->local_sdp_port, + t_engine->cur_payload_map->remote_sdp_ip, + t_engine->cur_payload_map->remote_sdp_port, t_engine->cur_payload_map->agreed_pt, t_engine->read_impl.microseconds_per_packet / 1000); + + if (switch_rtp_ready(t_engine->rtp_session)) { + switch_rtp_set_default_payload(t_engine->rtp_session, t_engine->cur_payload_map->agreed_pt); + } + } else { + timer_name = smh->mparams->timer_name; + + if ((var = switch_channel_get_variable(session->channel, "rtp_timer_name"))) { + timer_name = (char *) var; + } + } + + /******************************************************************************************/ + + if (t_engine->rtp_session) { + goto text_up; + } + + + if (!t_engine->local_sdp_port) { + switch_core_media_choose_port(session, SWITCH_MEDIA_TYPE_TEXT, 1); + } + + memset(flags, 0, sizeof(flags)); + flags[SWITCH_RTP_FLAG_DATAWAIT]++; + flags[SWITCH_RTP_FLAG_RAW_WRITE]++; + + if (!switch_media_handle_test_media_flag(smh, SCMF_DISABLE_RTP_AUTOADJ) && !switch_channel_test_flag(session->channel, CF_PROXY_MODE) && + !((val = switch_channel_get_variable(session->channel, "disable_rtp_auto_adjust")) && switch_true(val)) && + !switch_channel_test_flag(session->channel, CF_AVPF)) { + flags[SWITCH_RTP_FLAG_AUTOADJ]++; + } + + if (switch_channel_test_flag(session->channel, CF_PROXY_MEDIA)) { + flags[SWITCH_RTP_FLAG_PROXY_MEDIA]++; + } + //TEXT switch_core_media_set_text_codec(session, 0); + + flags[SWITCH_RTP_FLAG_USE_TIMER] = 1; + flags[SWITCH_RTP_FLAG_NOBLOCK] = 0; + flags[SWITCH_RTP_FLAG_TEXT]++; + //flags[SWITCH_RTP_FLAG_VIDEO]++; + + t_engine->rtp_session = switch_rtp_new(a_engine->local_sdp_ip, + t_engine->local_sdp_port, + t_engine->cur_payload_map->remote_sdp_ip, + t_engine->cur_payload_map->remote_sdp_port, + t_engine->cur_payload_map->agreed_pt, + TEXT_TIMER_SAMPLES, TEXT_TIMER_MS * 1000, flags, NULL, &err, switch_core_session_get_pool(session)); + + + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "%sTEXT RTP [%s] %s:%d->%s:%d codec: %u ms: %d [%s]\n", + switch_channel_test_flag(session->channel, CF_PROXY_MEDIA) ? "PROXY " : "", + switch_channel_get_name(session->channel), + a_engine->local_sdp_ip, + t_engine->local_sdp_port, + t_engine->cur_payload_map->remote_sdp_ip, + t_engine->cur_payload_map->remote_sdp_port, t_engine->cur_payload_map->agreed_pt, + 0, switch_rtp_ready(t_engine->rtp_session) ? "SUCCESS" : err); + + + if (switch_rtp_ready(t_engine->rtp_session)) { + const char *ssrc; + + + if (!t_engine->tf) { + switch_rtp_text_factory_create(&t_engine->tf, switch_core_session_get_pool(session)); + } + + switch_rtp_set_video_buffer_size(t_engine->rtp_session, 2, 2048); + + switch_rtp_set_payload_map(t_engine->rtp_session, &t_engine->payload_map); + switch_channel_set_flag(session->channel, CF_TEXT); + switch_core_session_start_text_thread(session); + + if ((ssrc = switch_channel_get_variable(session->channel, "rtp_use_text_ssrc"))) { + uint32_t ssrc_ul = (uint32_t) strtoul(ssrc, NULL, 10); + switch_rtp_set_ssrc(t_engine->rtp_session, ssrc_ul); + t_engine->ssrc = ssrc_ul; + } else { + switch_rtp_set_ssrc(t_engine->rtp_session, t_engine->ssrc); + } + + if (t_engine->remote_ssrc) { + switch_rtp_set_remote_ssrc(t_engine->rtp_session, t_engine->remote_ssrc); + } + + if (t_engine->ice_in.cands[t_engine->ice_in.chosen[0]][0].ready) { + + gen_ice(session, SWITCH_MEDIA_TYPE_TEXT, NULL, 0); + + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Activating Text ICE\n"); + + switch_rtp_activate_ice(t_engine->rtp_session, + t_engine->ice_in.ufrag, + t_engine->ice_out.ufrag, + t_engine->ice_out.pwd, + t_engine->ice_in.pwd, + IPR_RTP, +#ifdef GOOGLE_ICE + ICE_GOOGLE_JINGLE, + NULL +#else + switch_ice_direction(session) == + SWITCH_CALL_DIRECTION_OUTBOUND ? ICE_VANILLA : (ICE_VANILLA | ICE_CONTROLLED), + &t_engine->ice_in +#endif + ); + + + } + + if ((val = switch_channel_get_variable(session->channel, "rtcp_text_interval_msec")) || (val = smh->mparams->rtcp_text_interval_msec)) { + const char *rport = switch_channel_get_variable(session->channel, "rtp_remote_text_rtcp_port"); + switch_port_t remote_port = t_engine->remote_rtcp_port; + + if (rport) { + remote_port = (switch_port_t)atoi(rport); + } + if (!strcasecmp(val, "passthru")) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Activating TEXT RTCP PASSTHRU PORT %d\n", remote_port); + switch_rtp_activate_rtcp(t_engine->rtp_session, -1, remote_port, t_engine->rtcp_mux > 0); + } else { + int interval = atoi(val); + if (interval < 100 || interval > 500000) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, + "Invalid rtcp interval spec [%d] must be between 100 and 500000\n", interval); + interval = 5000; + } + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, + "Activating TEXT RTCP PORT %d interval %d mux %d\n", remote_port, interval, t_engine->rtcp_mux); + switch_rtp_activate_rtcp(t_engine->rtp_session, interval, remote_port, t_engine->rtcp_mux > 0); + + } + + + if (t_engine->ice_in.cands[t_engine->ice_in.chosen[1]][1].ready) { + if (t_engine->rtcp_mux > 0 && !strcmp(t_engine->ice_in.cands[t_engine->ice_in.chosen[1]][1].con_addr, + t_engine->ice_in.cands[t_engine->ice_in.chosen[0]][0].con_addr) && + t_engine->ice_in.cands[t_engine->ice_in.chosen[1]][1].con_port == t_engine->ice_in.cands[t_engine->ice_in.chosen[0]][0].con_port) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Skipping TEXT RTCP ICE (Same as TEXT RTP)\n"); + } else { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Activating TEXT RTCP ICE\n"); + switch_rtp_activate_ice(t_engine->rtp_session, + t_engine->ice_in.ufrag, + t_engine->ice_out.ufrag, + t_engine->ice_out.pwd, + t_engine->ice_in.pwd, + IPR_RTCP, +#ifdef GOOGLE_ICE + ICE_GOOGLE_JINGLE, + NULL +#else + switch_ice_direction(session) == + SWITCH_CALL_DIRECTION_OUTBOUND ? ICE_VANILLA : (ICE_VANILLA | ICE_CONTROLLED), + &t_engine->ice_in +#endif + ); + + + + } + + } + } + + if (!zstr(t_engine->local_dtls_fingerprint.str) && switch_rtp_has_dtls() && dtls_ok(smh->session)) { + dtls_type_t xtype, + dtype = t_engine->dtls_controller ? DTLS_TYPE_CLIENT : DTLS_TYPE_SERVER; + xtype = DTLS_TYPE_RTP; + if (t_engine->rtcp_mux > 0 && smh->mparams->rtcp_text_interval_msec) xtype |= DTLS_TYPE_RTCP; + + switch_rtp_add_dtls(t_engine->rtp_session, &t_engine->local_dtls_fingerprint, &t_engine->remote_dtls_fingerprint, dtype | xtype); + + if (t_engine->rtcp_mux < 1 && smh->mparams->rtcp_text_interval_msec) { + xtype = DTLS_TYPE_RTCP; + switch_rtp_add_dtls(t_engine->rtp_session, &t_engine->local_dtls_fingerprint, &t_engine->remote_dtls_fingerprint, dtype | xtype); + } + } + + + if ((val = switch_channel_get_variable(session->channel, "rtp_manual_text_rtp_bugs"))) { + switch_core_media_parse_rtp_bugs(&t_engine->rtp_bugs, val); + } + + + //if (switch_channel_test_flag(session->channel, CF_AVPF)) { + //smh->mparams->manual_video_rtp_bugs = RTP_BUG_SEND_LINEAR_TIMESTAMPS; + //} + + switch_rtp_intentional_bugs(t_engine->rtp_session, t_engine->rtp_bugs | smh->mparams->manual_text_rtp_bugs); + + //XX + + + switch_channel_set_variable_printf(session->channel, "rtp_use_text_pt", "%d", t_engine->cur_payload_map->agreed_pt); + t_engine->ssrc = switch_rtp_get_ssrc(t_engine->rtp_session); + switch_channel_set_variable_printf(session->channel, "rtp_use_text_ssrc", "%u", t_engine->ssrc); + + switch_core_session_apply_crypto(session, SWITCH_MEDIA_TYPE_TEXT); + + + if (switch_channel_test_flag(session->channel, CF_ZRTP_PASSTHRU)) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Activating text UDPTL mode\n"); + switch_rtp_udptl_mode(t_engine->rtp_session); + } + + } else { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "TEXT RTP REPORTS ERROR: [%s]\n", switch_str_nil(err)); + switch_channel_hangup(session->channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER); + goto end; + } + } - + text_up: video: if (switch_channel_direction(session->channel) == SWITCH_CALL_DIRECTION_OUTBOUND) { @@ -7076,10 +8060,8 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_activate_rtp(switch_core_sessi if (v_engine->ice_in.cands[v_engine->ice_in.chosen[1]][1].ready) { - if (v_engine->rtcp_mux > 0 && v_engine->ice_in.cands[v_engine->ice_in.chosen[0]][0].ready && - !strcmp(v_engine->ice_in.cands[v_engine->ice_in.chosen[1]][1].con_addr, - v_engine->ice_in.cands[v_engine->ice_in.chosen[0]][0].con_addr) && - v_engine->ice_in.cands[v_engine->ice_in.chosen[1]][1].con_port == v_engine->ice_in.cands[v_engine->ice_in.chosen[0]][0].con_port) { + if (v_engine->rtcp_mux > 0 && !strcmp(v_engine->ice_in.cands[v_engine->ice_in.chosen[1]][1].con_addr, v_engine->ice_in.cands[v_engine->ice_in.chosen[0]][0].con_addr) + && v_engine->ice_in.cands[v_engine->ice_in.chosen[1]][1].con_port == v_engine->ice_in.cands[v_engine->ice_in.chosen[0]][0].con_port) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Skipping VIDEO RTCP ICE (Same as VIDEO RTP)\n"); } else { @@ -7126,9 +8108,9 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_activate_rtp(switch_core_sessi switch_core_media_parse_rtp_bugs(&v_engine->rtp_bugs, val); } - //if (switch_channel_test_flag(session->channel, CF_AVPF)) { - //smh->mparams->manual_video_rtp_bugs = RTP_BUG_SEND_LINEAR_TIMESTAMPS; - //} + if (switch_channel_test_flag(session->channel, CF_AVPF)) { + smh->mparams->manual_video_rtp_bugs = RTP_BUG_SEND_LINEAR_TIMESTAMPS; + } switch_rtp_intentional_bugs(v_engine->rtp_session, v_engine->rtp_bugs | smh->mparams->manual_video_rtp_bugs); @@ -7655,7 +8637,7 @@ SWITCH_DECLARE(void) switch_core_media_gen_local_sdp(switch_core_session_t *sess char *buf; int ptime = 0; uint32_t rate = 0; - uint32_t v_port; + uint32_t v_port, t_port; int use_cng = 1; const char *val; const char *family; @@ -7673,8 +8655,9 @@ SWITCH_DECLARE(void) switch_core_media_gen_local_sdp(switch_core_session_t *sess //const char *local_audio_crypto_key = switch_core_session_local_crypto_key(session, SWITCH_MEDIA_TYPE_AUDIO); const char *local_sdp_audio_zrtp_hash = switch_core_media_get_zrtp_hash(session, SWITCH_MEDIA_TYPE_AUDIO, SWITCH_TRUE); const char *local_sdp_video_zrtp_hash = switch_core_media_get_zrtp_hash(session, SWITCH_MEDIA_TYPE_VIDEO, SWITCH_TRUE); + const char *local_sdp_text_zrtp_hash = switch_core_media_get_zrtp_hash(session, SWITCH_MEDIA_TYPE_TEXT, SWITCH_TRUE); const char *tmp; - switch_rtp_engine_t *a_engine, *v_engine; + switch_rtp_engine_t *a_engine, *v_engine, *t_engine; switch_media_handle_t *smh; ice_t *ice_out; //int vp8 = 0; @@ -7693,6 +8676,7 @@ SWITCH_DECLARE(void) switch_core_media_gen_local_sdp(switch_core_session_t *sess a_engine = &smh->engines[SWITCH_MEDIA_TYPE_AUDIO]; v_engine = &smh->engines[SWITCH_MEDIA_TYPE_VIDEO]; + t_engine = &smh->engines[SWITCH_MEDIA_TYPE_TEXT]; if ((!a_engine->rtcp_mux && !v_engine->rtcp_mux) && (sdp_type == SDP_TYPE_REQUEST || switch_true(switch_channel_get_variable(session->channel, "rtcp_mux")))) { @@ -8745,6 +9729,273 @@ SWITCH_DECLARE(void) switch_core_media_gen_local_sdp(switch_core_session_t *sess } + ///TEXT + + if (sdp_type == SDP_TYPE_RESPONSE && !switch_channel_test_flag(session->channel, CF_TEXT_POSSIBLE)) { + if (switch_channel_test_flag(session->channel, CF_TEXT_SDP_RECVD)) { + switch_channel_clear_flag(session->channel, CF_TEXT_SDP_RECVD); + switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "m=text 0 %s 19\r\n", + get_media_profile_name(session, + (switch_channel_test_flag(session->channel, CF_SECURE) + && switch_channel_direction(session->channel) == SWITCH_CALL_DIRECTION_OUTBOUND) || + a_engine->crypto_type != CRYPTO_INVALID || switch_channel_test_flag(session->channel, CF_DTLS))); + } + } else if ((switch_channel_test_flag(session->channel, CF_TEXT_POSSIBLE) || switch_channel_var_true(session->channel, "rtp_enable_text")) && + switch_channel_test_cap(session->channel, CC_RTP_RTT)) { + t_engine->t140_pt = 0; + t_engine->red_pt = 0; + + + if (sdp_type == SDP_TYPE_REQUEST) { + t_engine->t140_pt = 96; + t_engine->red_pt = 97; + + switch_core_media_add_payload_map(session, + SWITCH_MEDIA_TYPE_TEXT, + "red", + NULL, + NULL, + SDP_TYPE_REQUEST, + t_engine->red_pt, + 1000, + 0, + 1, + SWITCH_TRUE); + + switch_core_media_add_payload_map(session, + SWITCH_MEDIA_TYPE_TEXT, + "t140", + NULL, + NULL, + SDP_TYPE_REQUEST, + t_engine->t140_pt, + 1000, + 0, + 1, + SWITCH_TRUE); + + t_engine->codec_negotiated = 1; + } + + if (switch_channel_direction(session->channel) == SWITCH_CALL_DIRECTION_INBOUND) { + if (switch_channel_test_flag(smh->session->channel, CF_DTLS)) { + t_engine->no_crypto = 1; + } + } + + + if (!t_engine->local_sdp_port) { + switch_core_media_choose_port(session, SWITCH_MEDIA_TYPE_TEXT, 0); + } + + if ((t_port = t_engine->adv_sdp_port)) { + int loops; + + for (loops = 0; loops < 2; loops++) { + + if (switch_channel_test_flag(smh->session->channel, CF_ICE)) { + gen_ice(session, SWITCH_MEDIA_TYPE_TEXT, ip, (switch_port_t)t_port); + } + + + switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "m=text %d %s", + t_port, + get_media_profile_name(session, + (loops == 0 && switch_channel_test_flag(session->channel, CF_SECURE) + && switch_channel_direction(session->channel) == SWITCH_CALL_DIRECTION_OUTBOUND) || + a_engine->crypto_type != CRYPTO_INVALID || switch_channel_test_flag(session->channel, CF_DTLS))); + + + /*****************************/ + if (t_engine->codec_negotiated) { + + switch_mutex_lock(smh->sdp_mutex); + for (pmap = t_engine->payload_map; pmap && pmap->allocated; pmap = pmap->next) { + + if (pmap->type != SWITCH_MEDIA_TYPE_TEXT || !pmap->negotiated) { + continue; + } + + switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), " %d", pmap->pt); + + } + switch_mutex_unlock(smh->sdp_mutex); + + + //TEXT switch_core_media_set_text_codec(session, 0); + //switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), " %d", t_engine->cur_payload_map->agreed_pt); + } + + switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "\r\n"); + + if (t_engine->codec_negotiated) { + switch_mutex_lock(smh->sdp_mutex); + for (pmap = t_engine->payload_map; pmap && pmap->allocated; pmap = pmap->next) { + + if (pmap->type != SWITCH_MEDIA_TYPE_TEXT || !pmap->negotiated) { + continue; + } + + if (!strcasecmp(pmap->iananame, "t140")) { + t_engine->t140_pt = pmap->pt; + } + + if (!strcasecmp(pmap->iananame, "red")) { + t_engine->red_pt = pmap->pt; + } + + switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=rtpmap:%d %s/%ld\r\n", + pmap->pt, pmap->iananame, pmap->rate); + + } + switch_mutex_unlock(smh->sdp_mutex); + + + if (t_engine->t140_pt && t_engine->red_pt) { + switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=fmtp:%d %d/%d/%d\r\n", t_engine->red_pt, t_engine->t140_pt, t_engine->t140_pt, t_engine->t140_pt); + } + + + if (t_engine->smode == SWITCH_MEDIA_FLOW_SENDONLY) { + switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "%s", "a=sendonly\r\n"); + } else if (t_engine->smode == SWITCH_MEDIA_FLOW_RECVONLY) { + switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "%s", "a=recvonly\r\n"); + } + + } + + if ((is_outbound || switch_channel_test_flag(session->channel, CF_RECOVERING)) + && switch_channel_test_flag(smh->session->channel, CF_DTLS)) { + generate_local_fingerprint(smh, SWITCH_MEDIA_TYPE_TEXT); + } + + + if (!zstr(t_engine->local_dtls_fingerprint.type)) { + switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=fingerprint:%s %s\na=setup:%s\r\n", t_engine->local_dtls_fingerprint.type, + t_engine->local_dtls_fingerprint.str, get_setup(t_engine, session, sdp_type)); + } + + + if (smh->mparams->rtcp_text_interval_msec) { + if (t_engine->rtcp_mux > 0) { + switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=rtcp-mux\r\n"); + switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=rtcp:%d IN %s %s\r\n", t_port, family, ip); + } else { + switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=rtcp:%d IN %s %s\r\n", t_port + 1, family, ip); + } + } + + //switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=ssrc:%u\r\n", t_engine->ssrc); + + if (t_engine->ice_out.cands[0][0].ready) { + char tmp1[11] = ""; + char tmp2[11] = ""; + uint32_t c1 = (2^24)*126 + (2^8)*65535 + (2^0)*(256 - 1); + //uint32_t c2 = (2^24)*126 + (2^8)*65535 + (2^0)*(256 - 2); + //uint32_t c3 = (2^24)*126 + (2^8)*65534 + (2^0)*(256 - 1); + //uint32_t c4 = (2^24)*126 + (2^8)*65534 + (2^0)*(256 - 2); + + uint32_t c2 = c1 - 1; + uint32_t c3 = c1 - 2; + uint32_t c4 = c1 - 3; + + tmp1[10] = '\0'; + tmp2[10] = '\0'; + switch_stun_random_string(tmp1, 10, "0123456789"); + switch_stun_random_string(tmp2, 10, "0123456789"); + + ice_out = &t_engine->ice_out; + + + switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=ssrc:%u cname:%s\r\n", t_engine->ssrc, smh->cname); + switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=ssrc:%u msid:%s v0\r\n", t_engine->ssrc, smh->msid); + switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=ssrc:%u mslabel:%s\r\n", t_engine->ssrc, smh->msid); + switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=ssrc:%u label:%sv0\r\n", t_engine->ssrc, smh->msid); + + + + switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=ice-ufrag:%s\r\n", ice_out->ufrag); + switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=ice-pwd:%s\r\n", ice_out->pwd); + + + switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=candidate:%s 1 %s %u %s %d typ host generation 0\r\n", + tmp1, ice_out->cands[0][0].transport, c1, + ice_out->cands[0][0].con_addr, ice_out->cands[0][0].con_port + ); + + if (!zstr(t_engine->local_sdp_ip) && !zstr(ice_out->cands[0][0].con_addr) && + strcmp(t_engine->local_sdp_ip, ice_out->cands[0][0].con_addr) + && t_engine->local_sdp_port != ice_out->cands[0][0].con_port) { + + switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=candidate:%s 1 %s %u %s %d typ srflx raddr %s rport %d generation 0\r\n", + tmp2, ice_out->cands[0][0].transport, c3, + ice_out->cands[0][0].con_addr, ice_out->cands[0][0].con_port, + t_engine->local_sdp_ip, t_engine->local_sdp_port + ); + } + + + if (t_engine->rtcp_mux < 1 || is_outbound || switch_channel_test_flag(session->channel, CF_RECOVERING)) { + + switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=candidate:%s 2 %s %u %s %d typ host generation 0\r\n", + tmp1, ice_out->cands[0][0].transport, c2, + ice_out->cands[0][0].con_addr, ice_out->cands[0][0].con_port + (t_engine->rtcp_mux > 0 ? 0 : 1) + ); + + + if (!zstr(t_engine->local_sdp_ip) && !zstr(ice_out->cands[0][1].con_addr) && + strcmp(t_engine->local_sdp_ip, ice_out->cands[0][1].con_addr) + && t_engine->local_sdp_port != ice_out->cands[0][1].con_port) { + + switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=candidate:%s 2 %s %u %s %d typ srflx generation 0\r\n", + tmp2, ice_out->cands[0][0].transport, c4, + ice_out->cands[0][0].con_addr, ice_out->cands[0][0].con_port + (t_engine->rtcp_mux > 0 ? 0 : 1), + t_engine->local_sdp_ip, t_engine->local_sdp_port + (t_engine->rtcp_mux > 0 ? 0 : 1) + ); + } + } + + + +#ifdef GOOGLE_ICE + switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=ice-options:google-ice\r\n"); +#endif + } + + + + if (loops == 0 && switch_channel_test_flag(session->channel, CF_SECURE) && !switch_channel_test_flag(session->channel, CF_DTLS)) { + int i; + + for (i = 0; smh->crypto_suite_order[i] != CRYPTO_INVALID; i++) { + switch_rtp_crypto_key_type_t j = SUITES[smh->crypto_suite_order[i]].type; + + if ((t_engine->crypto_type == j || t_engine->crypto_type == CRYPTO_INVALID) && !zstr(t_engine->ssec[j].local_crypto_key)) { + switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=crypto:%s\r\n", t_engine->ssec[j].local_crypto_key); + } + } + //switch_snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "a=encryption:optional\r\n"); + } + + + if (local_sdp_text_zrtp_hash) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Adding text a=zrtp-hash:%s\n", local_sdp_text_zrtp_hash); + switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=zrtp-hash:%s\r\n", local_sdp_text_zrtp_hash); + } + + + if (switch_channel_test_flag(session->channel, CF_DTLS) || + !switch_channel_test_flag(session->channel, CF_SECURE) || + smh->crypto_mode == CRYPTO_MODE_MANDATORY || smh->crypto_mode == CRYPTO_MODE_FORBIDDEN) { + break; + } + } + } + + } + + + if (map) { switch_event_destroy(&map); @@ -8965,13 +10216,14 @@ SWITCH_DECLARE(void) switch_core_media_patch_sdp(switch_core_session_t *session) { switch_size_t len; char *p, *q, *pe, *qe; - int has_video = 0, has_audio = 0, has_ip = 0; + int has_video = 0, has_audio = 0, has_text = 0, has_ip = 0; char port_buf[25] = ""; char vport_buf[25] = ""; + char tport_buf[25] = ""; char *new_sdp; int bad = 0; switch_media_handle_t *smh; - switch_rtp_engine_t *a_engine, *v_engine; + switch_rtp_engine_t *a_engine, *v_engine, *t_engine; payload_map_t *pmap; switch_assert(session); @@ -8982,6 +10234,7 @@ SWITCH_DECLARE(void) switch_core_media_patch_sdp(switch_core_session_t *session) a_engine = &smh->engines[SWITCH_MEDIA_TYPE_AUDIO]; v_engine = &smh->engines[SWITCH_MEDIA_TYPE_VIDEO]; + t_engine = &smh->engines[SWITCH_MEDIA_TYPE_TEXT]; if (zstr(smh->mparams->local_sdp_str)) { return; @@ -9222,8 +10475,72 @@ SWITCH_DECLARE(void) switch_core_media_patch_sdp(switch_core_session_t *session) } has_video++; + } else if (!strncmp("m=text ", p, 8) && *(p + 8) != '0') { + if (!has_text) { + switch_core_media_choose_port(session, SWITCH_MEDIA_TYPE_TEXT, 1); + clear_pmaps(t_engine); + pmap = switch_core_media_add_payload_map(session, + SWITCH_MEDIA_TYPE_TEXT, + "PROXY-VID", + NULL, + NULL, + SDP_TYPE_RESPONSE, + 0, + 90000, + 90000, + 1, + SWITCH_TRUE); + t_engine->cur_payload_map = pmap; + + switch_snprintf(tport_buf, sizeof(tport_buf), "%u", t_engine->adv_sdp_port); + + if (switch_channel_media_ready(session->channel) && !switch_rtp_ready(t_engine->rtp_session)) { + switch_channel_set_flag(session->channel, CF_TEXT_POSSIBLE); + switch_channel_set_flag(session->channel, CF_REINVITE); + switch_core_media_activate_rtp(session); + } + + t_engine->codec_negotiated = 1; + //TEXT switch_core_media_set_text_codec(session, SWITCH_FALSE); + } + + strncpy(q, p, 8); + p += 8; + + if (p >= pe) { + bad = 8; + goto end; + } + + q += 8; + + if (q >= qe) { + bad = 9; + goto end; + } + + strncpy(q, tport_buf, strlen(tport_buf)); + q += strlen(tport_buf); + + if (q >= qe) { + bad = 10; + goto end; + } + + while (p && *p && (*p >= '0' && *p <= '9')) { + + if (p >= pe) { + bad = 11; + goto end; + } + + p++; + } + + has_text++; } + while (p && *p && *p != '\n') { if (p >= pe) { @@ -9450,7 +10767,7 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_reset_jb(switch_core_session_t SWITCH_DECLARE(switch_status_t) switch_core_media_receive_message(switch_core_session_t *session, switch_core_session_message_t *msg) { switch_media_handle_t *smh; - switch_rtp_engine_t *a_engine, *v_engine; + switch_rtp_engine_t *a_engine, *v_engine;//, *t_engine; switch_status_t status = SWITCH_STATUS_SUCCESS; switch_assert(session); @@ -9465,6 +10782,7 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_receive_message(switch_core_se a_engine = &smh->engines[SWITCH_MEDIA_TYPE_AUDIO]; v_engine = &smh->engines[SWITCH_MEDIA_TYPE_VIDEO]; + //t_engine = &smh->engines[SWITCH_MEDIA_TYPE_TEXT]; switch (msg->message_id) { @@ -10515,7 +11833,7 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_codec_chosen(switch_core_sessi SWITCH_DECLARE(void) switch_core_session_stop_media(switch_core_session_t *session) { - switch_rtp_engine_t *a_engine, *v_engine; + switch_rtp_engine_t *a_engine, *v_engine, *t_engine; switch_media_handle_t *smh; int type; @@ -10527,6 +11845,7 @@ SWITCH_DECLARE(void) switch_core_session_stop_media(switch_core_session_t *sessi a_engine = &smh->engines[SWITCH_MEDIA_TYPE_AUDIO]; v_engine = &smh->engines[SWITCH_MEDIA_TYPE_VIDEO]; + t_engine = &smh->engines[SWITCH_MEDIA_TYPE_TEXT]; if (switch_core_codec_ready(&v_engine->read_codec)) { type = 1; @@ -10546,6 +11865,10 @@ SWITCH_DECLARE(void) switch_core_session_stop_media(switch_core_session_t *sessi switch_rtp_reset(v_engine->rtp_session); } + if (t_engine->rtp_session) { + switch_rtp_reset(t_engine->rtp_session); + } + smh->msid = NULL; smh->cname = NULL; @@ -10554,6 +11877,11 @@ SWITCH_DECLARE(void) switch_core_session_stop_media(switch_core_session_t *sessi v_engine->ice_out.cands[0][0].foundation = NULL; v_engine->ice_out.cands[0][0].component_id = 0; + t_engine->ice_out.ufrag = NULL; + t_engine->ice_out.pwd = NULL; + t_engine->ice_out.cands[0][0].foundation = NULL; + t_engine->ice_out.cands[0][0].component_id = 0; + a_engine->ice_out.ufrag = NULL; a_engine->ice_out.pwd = NULL; @@ -10564,6 +11892,10 @@ SWITCH_DECLARE(void) switch_core_session_stop_media(switch_core_session_t *sessi gen_ice(smh->session, SWITCH_MEDIA_TYPE_VIDEO, NULL, 0); } + if (t_engine->ice_in.cands[t_engine->ice_in.chosen[0]][0].ready) { + gen_ice(smh->session, SWITCH_MEDIA_TYPE_TEXT, NULL, 0); + } + if (a_engine->ice_in.cands[a_engine->ice_in.chosen[0]][0].ready) { gen_ice(smh->session, SWITCH_MEDIA_TYPE_AUDIO, NULL, 0); } @@ -10573,9 +11905,11 @@ SWITCH_DECLARE(void) switch_core_session_stop_media(switch_core_session_t *sessi a_engine->local_dtls_fingerprint.len = 0; v_engine->local_dtls_fingerprint.len = 0; + t_engine->local_dtls_fingerprint.len = 0; a_engine->remote_ssrc = 0; v_engine->remote_ssrc = 0; + t_engine->remote_ssrc = 0; switch_channel_clear_flag(smh->session->channel, CF_VIDEO_READY); switch_core_session_wake_video_thread(smh->session); @@ -11725,7 +13059,7 @@ SWITCH_DECLARE(switch_status_t) switch_core_session_read_video_frame(switch_core } } - if ((*frame) && (*frame)->codec) { + if ((*frame)->codec) { if (patchers) { switch_set_flag((*frame)->codec, SWITCH_CODEC_FLAG_VIDEO_PATCHING); } else { @@ -11740,6 +13074,7 @@ SWITCH_DECLARE(switch_status_t) switch_core_session_read_video_frame(switch_core return status; } + SWITCH_DECLARE(switch_status_t) switch_core_session_set_video_read_callback(switch_core_session_t *session, switch_core_video_thread_callback_func_t func, void *user_data) { @@ -11788,6 +13123,500 @@ SWITCH_DECLARE(switch_status_t) switch_core_session_video_read_callback(switch_c } + +SWITCH_DECLARE(switch_status_t) switch_core_session_set_text_read_callback(switch_core_session_t *session, + switch_core_text_thread_callback_func_t func, void *user_data) +{ + switch_status_t status = SWITCH_STATUS_SUCCESS; + switch_media_handle_t *smh; + + if (!(smh = session->media_handle)) { + return SWITCH_STATUS_FALSE; + } + + switch_mutex_lock(smh->control_mutex); + if (!func) { + session->text_read_callback = NULL; + session->text_read_user_data = NULL; + } else if (session->text_read_callback) { + status = SWITCH_STATUS_FALSE; + } else { + session->text_read_callback = func; + session->text_read_user_data = user_data; + } + + switch_core_session_start_text_thread(session); + switch_mutex_unlock(smh->control_mutex); + + return status; +} + + + +SWITCH_DECLARE(switch_status_t) switch_core_session_text_read_callback(switch_core_session_t *session, switch_frame_t *frame) +{ + switch_media_handle_t *smh; + switch_status_t status = SWITCH_STATUS_CONTINUE; + + if (!(smh = session->media_handle)) { + return SWITCH_STATUS_FALSE; + } + + switch_mutex_lock(smh->control_mutex); + + if (session->text_read_callback) { + status = session->text_read_callback(session, frame, session->text_read_user_data); + } + + switch_mutex_unlock(smh->control_mutex); + + return status; +} + + + +SWITCH_DECLARE(switch_status_t) switch_core_session_read_text_frame(switch_core_session_t *session, switch_frame_t **frame, switch_io_flag_t flags, + int stream_id) +{ + switch_status_t status = SWITCH_STATUS_FALSE; + switch_io_event_hook_text_read_frame_t *ptr; + switch_media_handle_t *smh; + switch_io_read_text_frame_t read_text_frame = NULL; + switch_time_t now; + + switch_assert(session != NULL); + + if (!(smh = session->media_handle)) { + return SWITCH_STATUS_FALSE; + } + + if (switch_channel_down_nosig(session->channel)) { + return SWITCH_STATUS_FALSE; + } + + if (!(read_text_frame = session->endpoint_interface->io_routines->read_text_frame)) { + if (session->io_override) { + read_text_frame = session->io_override->read_text_frame; + } + } + + if (read_text_frame) { + if ((status = read_text_frame(session, frame, flags, stream_id)) == SWITCH_STATUS_SUCCESS) { + for (ptr = session->event_hooks.text_read_frame; ptr; ptr = ptr->next) { + if ((status = ptr->text_read_frame(session, frame, flags, stream_id)) != SWITCH_STATUS_SUCCESS) { + break; + } + } + } + } + + if (status == SWITCH_STATUS_INUSE) { + *frame = &runtime.dummy_cng_frame; + switch_cond_next(); + return SWITCH_STATUS_SUCCESS; + } + + if (status != SWITCH_STATUS_SUCCESS && status != SWITCH_STATUS_BREAK) { + goto done; + } + + if (!(*frame)) { + goto done; + } + + now = switch_micro_time_now(); + + if (switch_test_flag((*frame), SFF_CNG)) { + if (smh->last_text_frame && now - smh->last_text_frame > TEXT_PERIOD_TIMEOUT * 1000) { + switch_channel_set_flag(session->channel, CF_TEXT_IDLE); + switch_channel_clear_flag(session->channel, CF_TEXT_ACTIVE); + smh->last_text_frame = 0; + } + } else { + unsigned char *p = (*frame)->data; + + smh->last_text_frame = now; + switch_channel_set_flag(session->channel, CF_TEXT_ACTIVE); + switch_channel_clear_flag(session->channel, CF_TEXT_IDLE); + + while(p && *p) { + if (*p == '\r' || *p == '\n') { + switch_set_flag((*frame), SFF_TEXT_LINE_BREAK); + break; + } + + if (*p == 0xE2 && *(p+1) == 0x80 && *(p+2) == 0xA8) { + switch_set_flag((*frame), SFF_TEXT_LINE_BREAK); + break; + } + + p++; + } + } + + if ((*frame)->data && (*frame)->datalen && !((*frame)->flags & SFF_CNG)) { + if (!session->text_buffer) { + switch_mutex_init(&session->text_mutex, SWITCH_MUTEX_NESTED, switch_core_session_get_pool(session)); + switch_buffer_create_dynamic(&session->text_buffer, 512, 1024, 0); + } + switch_buffer_write(session->text_buffer, (*frame)->data, (*frame)->datalen); + } + + if (session->bugs) { + switch_media_bug_t *bp; + int prune = 0; + + switch_thread_rwlock_rdlock(session->bug_rwlock); + for (bp = session->bugs; bp; bp = bp->next) { + switch_bool_t ok = SWITCH_TRUE; + + if (switch_channel_test_flag(session->channel, CF_PAUSE_BUGS) && !switch_core_media_bug_test_flag(bp, SMBF_NO_PAUSE)) { + continue; + } + + if (!switch_channel_test_flag(session->channel, CF_ANSWERED) && switch_core_media_bug_test_flag(bp, SMBF_ANSWER_REQ)) { + continue; + } + + if (switch_test_flag(bp, SMBF_PRUNE)) { + prune++; + continue; + } + + if (bp->ready && switch_test_flag(bp, SMBF_READ_TEXT_STREAM)) { + int bytes = 0; + + if ((*frame)) { + switch_size_t inuse = 0; + + if ((*frame)->data && (*frame)->datalen && !((*frame)->flags & SFF_CNG)) { + switch_mutex_lock(session->text_mutex); + switch_buffer_write(bp->text_buffer, (char *)(*frame)->data, (*frame)->datalen); + switch_mutex_unlock(session->text_mutex); + } + + inuse = switch_buffer_inuse(bp->text_buffer); + + if (zstr(bp->text_framedata) && inuse && + (switch_channel_test_flag(session->channel, CF_TEXT_IDLE) || switch_test_flag((*frame), SFF_TEXT_LINE_BREAK))) { + + if (inuse + 1 > bp->text_framesize) { + void *tmp = malloc(inuse + 1024); + memcpy(tmp, bp->text_framedata, bp->text_framesize); + + switch_assert(tmp); + + bp->text_framesize = inuse + 1024; + + free(bp->text_framedata); + bp->text_framedata = tmp; + + } + + + bytes = switch_buffer_read(bp->text_buffer, bp->text_framedata, inuse); + *(bp->text_framedata + bytes) = '\0'; + + ok = bp->callback(bp, bp->user_data, SWITCH_ABC_TYPE_READ_TEXT); + bp->text_framedata[0] = '\0'; + } else ok = SWITCH_TRUE; + } + } + + if (ok == SWITCH_FALSE) { + switch_set_flag(bp, SMBF_PRUNE); + prune++; + } + } + + switch_thread_rwlock_unlock(session->bug_rwlock); + + if (prune) { + switch_core_media_bug_prune(session); + } + } + + if (status == SWITCH_STATUS_SUCCESS || status == SWITCH_STATUS_BREAK) { + switch_core_session_text_read_callback(session, *frame); + } + + done: + + return status; +} + + +static void build_red_packet(switch_rtp_engine_t *t_engine) +{ + int pos; + switch_frame_t *frame = &t_engine->tf->text_write_frame; + switch_byte_t *buf = (switch_byte_t *) frame->data; + uint32_t plen = 0, loops = 0; + uint16_t *u16; + + pos = t_engine->tf->red_pos + 1; + + if (pos == t_engine->tf->red_max) pos = 0; + + for (;;) { + uint16_t ts = frame->timestamp - t_engine->tf->red_ts[pos]; + uint16_t len = t_engine->tf->red_buflen[pos]; + + loops++; + + //1 + *buf = t_engine->t140_pt & 0x7f; + + if (pos != t_engine->tf->red_pos) { + *buf |= 0x80; + + buf++; //2 + u16 = (uint16_t *) buf; + *u16 = htons(ts << 2); + buf++;//3 + *buf += (len & 0x300) >> 8; + buf++;//4 + *buf = len & 0xff; + } + + buf++; + + if (pos == t_engine->tf->red_pos) break; + + + pos++; + + if (pos == t_engine->tf->red_max) pos = 0; + } + + + plen = ((loops - 1) * 4) + 1; + + + pos = t_engine->tf->red_pos + 1; + + if (pos == t_engine->tf->red_max) pos = 0; + + for (;;) { + if (t_engine->tf->red_buflen[pos]) { + memcpy(buf, t_engine->tf->red_buf[pos], t_engine->tf->red_buflen[pos]); + plen += t_engine->tf->red_buflen[pos]; + buf += t_engine->tf->red_buflen[pos]; + } + + if (pos == t_engine->tf->red_pos) break; + + pos++; + + if (pos == t_engine->tf->red_max) pos = 0; + } + + + buf = frame->data; + *(buf+plen) = '\0'; + + frame->datalen = plen; + frame->payload = t_engine->red_pt; +} + +SWITCH_DECLARE(switch_status_t) switch_core_session_write_text_frame(switch_core_session_t *session, switch_frame_t *frame, switch_io_flag_t flags, + int stream_id) +{ + switch_status_t status = SWITCH_STATUS_FALSE; + switch_media_handle_t *smh; + switch_io_event_hook_text_write_frame_t *ptr; + switch_rtp_engine_t *t_engine; + switch_io_write_text_frame_t write_text_frame = NULL; + + switch_assert(session); + + if (!(smh = session->media_handle)) { + return SWITCH_STATUS_FALSE; + } + + if (switch_channel_down(session->channel)) { + return SWITCH_STATUS_FALSE; + } + + if (switch_core_session_media_flow(session, SWITCH_MEDIA_TYPE_TEXT) == SWITCH_MEDIA_FLOW_RECVONLY || switch_core_session_media_flow(session, SWITCH_MEDIA_TYPE_TEXT) == SWITCH_MEDIA_FLOW_INACTIVE) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG3, "Writing text to RECVONLY/INACTIVE session\n"); + return SWITCH_STATUS_SUCCESS; + } + + //if (switch_channel_test_flag(session->channel, CF_TEXT_PAUSE_WRITE)) { + // return SWITCH_STATUS_SUCCESS; + //} + + if (smh->write_mutex[SWITCH_MEDIA_TYPE_TEXT] && switch_mutex_trylock(smh->write_mutex[SWITCH_MEDIA_TYPE_TEXT]) != SWITCH_STATUS_SUCCESS) { + /* return CNG, another thread is already writing */ + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG1, "%s is already being written to for %s\n", + switch_channel_get_name(session->channel), type2str(SWITCH_MEDIA_TYPE_TEXT)); + goto done; + } + + t_engine = &smh->engines[SWITCH_MEDIA_TYPE_TEXT]; + + if (switch_channel_test_cap(session->channel, CC_RTP_RTT)) { + + if (frame) { + char *str = (char *) frame->data; + switch_buffer_write(t_engine->tf->write_buffer, str, frame->datalen); + } + + if (!switch_buffer_inuse(t_engine->tf->write_buffer)) { + t_engine->tf->write_empty++; + return SWITCH_STATUS_BREAK; + } + + frame = &t_engine->tf->text_write_frame; + switch_core_timer_sync(&t_engine->tf->timer); + frame->timestamp = t_engine->tf->timer.samplecount; + + if (t_engine->red_pt) { + t_engine->tf->red_ts[t_engine->tf->red_pos] = frame->timestamp; + + if (t_engine->tf->write_empty > TEXT_PERIOD_TIMEOUT / TEXT_TIMER_MS) { + int pos; + + for(pos = 0; pos < t_engine->tf->red_max; pos++) { + t_engine->tf->red_ts[pos] = 0; + t_engine->tf->red_buf[pos][0] = '\0'; + t_engine->tf->red_buflen[pos] = 0; + } + + frame->m = 1; + t_engine->tf->write_empty = 0; + + } else { + frame->m = 0; + } + + t_engine->tf->red_buflen[t_engine->tf->red_pos] = + switch_buffer_read(t_engine->tf->write_buffer, t_engine->tf->red_buf[t_engine->tf->red_pos], RED_PACKET_SIZE); + + *(t_engine->tf->red_buf[t_engine->tf->red_pos] + t_engine->tf->red_buflen[t_engine->tf->red_pos]) = '\0'; + + build_red_packet(t_engine); + + + } else { + frame->datalen = switch_buffer_read(t_engine->tf->write_buffer, t_engine->tf->text_write_frame.data, RED_PACKET_SIZE); + frame->payload = t_engine->t140_pt; + } + } + + if (!(write_text_frame = session->endpoint_interface->io_routines->write_text_frame)) { + if (session->io_override) { + write_text_frame = session->io_override->write_text_frame; + } + } + + if (write_text_frame) { + if ((status = write_text_frame(session, frame, flags, stream_id)) == SWITCH_STATUS_SUCCESS) { + for (ptr = session->event_hooks.text_write_frame; ptr; ptr = ptr->next) { + if ((status = ptr->text_write_frame(session, frame, flags, stream_id)) != SWITCH_STATUS_SUCCESS) { + break; + } + } + } + } + + + if (switch_channel_test_cap(session->channel, CC_RTP_RTT)) { + if (t_engine->red_pt) { + t_engine->tf->red_pos++; + if (t_engine->tf->red_pos == t_engine->tf->red_max) { + t_engine->tf->red_pos = 0; + } + } + } + + done: + + if (smh->write_mutex[SWITCH_MEDIA_TYPE_TEXT]) { + switch_mutex_unlock(smh->write_mutex[SWITCH_MEDIA_TYPE_TEXT]); + } + + return status; + } + +SWITCH_DECLARE(switch_status_t) switch_core_session_printf(switch_core_session_t *session, const char *fmt, ...) +{ + char *data = NULL; + int ret = 0; + va_list ap; + switch_frame_t frame = { 0 }; + switch_rtp_engine_t *t_engine; + switch_media_handle_t *smh; + unsigned char CR[] = TEXT_UNICODE_LINEFEED; + + if (!(smh = session->media_handle)) { + return SWITCH_STATUS_FALSE; + } + + t_engine = &smh->engines[SWITCH_MEDIA_TYPE_TEXT]; + + if (!switch_rtp_ready(t_engine->rtp_session)) { + return SWITCH_STATUS_NOTIMPL; + } + + + va_start(ap, fmt); + ret = switch_vasprintf(&data, fmt, ap); + va_end(ap); + + if (ret == -1) { + abort(); + } + + + frame.data = data; + frame.datalen = strlen(data); + + switch_core_session_write_text_frame(session, &frame, 0, 0); + + frame.data = CR; + frame.datalen = 3; + + switch_core_session_write_text_frame(session, &frame, 0, 0); + + switch_safe_free(data); + + return SWITCH_STATUS_SUCCESS; +} + + +SWITCH_DECLARE(switch_status_t) switch_core_session_print(switch_core_session_t *session, const char *data) +{ + switch_frame_t frame = { 0 }; + //switch_rtp_engine_t *t_engine; + //switch_media_handle_t *smh; + + //if (!(smh = session->media_handle)) { + // return SWITCH_STATUS_FALSE; + //} + + //t_engine = &smh->engines[SWITCH_MEDIA_TYPE_TEXT]; + + //if (!switch_rtp_ready(t_engine->rtp_session)) { + // return SWITCH_STATUS_NOTIMPL; + //} + + + if (!switch_channel_test_flag(session->channel, CF_TEXT)) { + return SWITCH_STATUS_NOTIMPL; + } + + frame.data = (char *) data; + frame.datalen = strlen(data); + + switch_core_session_write_text_frame(session, &frame, 0, 0); + + return SWITCH_STATUS_SUCCESS; +} + + + /* For Emacs: * Local Variables: * mode:c diff --git a/src/switch_core_media_bug.c b/src/switch_core_media_bug.c index e50eb3df57..b5f6a86549 100644 --- a/src/switch_core_media_bug.c +++ b/src/switch_core_media_bug.c @@ -88,6 +88,11 @@ SWITCH_DECLARE(switch_core_session_t *) switch_core_media_bug_get_session(switch return bug->session; } +SWITCH_DECLARE(const char *) switch_core_media_bug_get_text(switch_media_bug_t *bug) +{ + return bug->text_framedata; +} + SWITCH_DECLARE(switch_frame_t *) switch_core_media_bug_get_video_ping_frame(switch_media_bug_t *bug) { return bug->video_ping_frame; @@ -791,6 +796,14 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_bug_add(switch_core_session_t switch_queue_create(&bug->spy_video_queue[1], SWITCH_CORE_QUEUE_LEN, switch_core_session_get_pool(session)); } + if ((switch_test_flag(bug, SMBF_READ_TEXT_STREAM))) { + + switch_buffer_create_dynamic(&bug->text_buffer, 512, 1024, 0); + switch_zmalloc(bug->text_framedata, 1024); + bug->text_framesize = 1024; + + } + if ((switch_test_flag(bug, SMBF_READ_VIDEO_STREAM) || switch_test_flag(bug, SMBF_WRITE_VIDEO_STREAM))) { switch_memory_pool_t *pool = switch_core_session_get_pool(session); @@ -1150,6 +1163,11 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_bug_close(switch_media_bug_t * bp->callback(bp, bp->user_data, SWITCH_ABC_TYPE_CLOSE); } + if (bp->text_buffer) { + switch_buffer_destroy(&bp->text_buffer); + switch_safe_free(bp->text_framedata); + } + if (switch_test_flag(bp, SMBF_READ_VIDEO_STREAM) || switch_test_flag(bp, SMBF_WRITE_VIDEO_STREAM) || switch_test_flag(bp, SMBF_READ_VIDEO_PING) || switch_test_flag(bp, SMBF_WRITE_VIDEO_PING)) { switch_channel_clear_flag_recursive(bp->session->channel, CF_VIDEO_DECODED_READ); } diff --git a/src/switch_core_session.c b/src/switch_core_session.c index aa0f92f222..8433308609 100644 --- a/src/switch_core_session.c +++ b/src/switch_core_session.c @@ -1469,6 +1469,10 @@ SWITCH_DECLARE(void) switch_core_session_perform_destroy(switch_core_session_t * switch_channel_get_name((*session)->channel), switch_channel_state_name(switch_channel_get_state((*session)->channel))); + if ((*session)->text_buffer) { + switch_buffer_destroy(&(*session)->text_buffer); + } + switch_core_session_reset(*session, SWITCH_TRUE, SWITCH_TRUE); switch_core_media_bug_remove_all(*session); @@ -1880,6 +1884,19 @@ SWITCH_DECLARE(switch_status_t) switch_core_session_thread_launch(switch_core_se return status; } +SWITCH_DECLARE(const char *) switch_core_session_get_text_buffer(switch_core_session_t *session) +{ + const char *buf = NULL; + + if (session->text_buffer) { + switch_mutex_lock(session->text_mutex); + buf = (const char *)switch_core_session_strdup(session, (const char *) switch_buffer_get_head_pointer(session->text_buffer)); + switch_mutex_unlock(session->text_mutex); + } + + return buf; +} + SWITCH_DECLARE(void) switch_core_session_launch_thread(switch_core_session_t *session, switch_thread_start_t func, void *obj) { switch_thread_t *thread; @@ -2945,6 +2962,17 @@ SWITCH_DECLARE(void) switch_core_session_raw_read(switch_core_session_t *session switch_core_session_set_codec_slin(session, session->sdata); } +SWITCH_DECLARE(switch_status_t) switch_core_session_override_io_routines(switch_core_session_t *session, switch_io_routines_t *ior) +{ + if (session->endpoint_interface && switch_channel_test_cap(session->channel, CC_IO_OVERRIDE)) { + session->io_override = ior; + return SWITCH_STATUS_SUCCESS; + } + + return SWITCH_STATUS_FALSE; +} + + /* For Emacs: * Local Variables: * mode:c diff --git a/src/switch_event.c b/src/switch_event.c index 4ebe0f37d5..138621a758 100644 --- a/src/switch_event.c +++ b/src/switch_event.c @@ -219,6 +219,7 @@ static char *EVENT_NAMES[] = { "CALL_SETUP_RESULT", "CALL_DETAIL", "DEVICE_STATE", + "REAL_TIME_TEXT", "ALL" }; diff --git a/src/switch_ivr.c b/src/switch_ivr.c index 54ec0ff6a3..bed71c97c6 100644 --- a/src/switch_ivr.c +++ b/src/switch_ivr.c @@ -2728,7 +2728,8 @@ SWITCH_DECLARE(switch_status_t) switch_ivr_generate_xml_cdr(switch_core_session_ char tmp[512], *f; int cdr_off = 0, v_off = 0, cd_off = 0; switch_hold_record_t *hold_record = switch_channel_get_hold_record(channel), *hr; - + const char *text_buffer = NULL; + if (*xml_cdr) { cdr = *xml_cdr; } else { @@ -2750,6 +2751,12 @@ SWITCH_DECLARE(switch_status_t) switch_ivr_generate_xml_cdr(switch_core_session_ x_field = switch_xml_add_child_d(x_channel_data, "direction", cd_off++); switch_xml_set_txt_d(x_field, switch_channel_direction(channel) == SWITCH_CALL_DIRECTION_OUTBOUND ? "outbound" : "inbound"); + + if ((text_buffer = switch_core_session_get_text_buffer(session))) { + x_field = switch_xml_add_child_d(x_channel_data, "textlog", cd_off++); + switch_xml_set_txt_d(x_field, text_buffer); + } + x_field = switch_xml_add_child_d(x_channel_data, "state_number", cd_off++); switch_snprintf(tmp, sizeof(tmp), "%d", switch_channel_get_state(channel)); switch_xml_set_txt_d(x_field, tmp); diff --git a/src/switch_ivr_async.c b/src/switch_ivr_async.c index e52884fd99..8df35b5a0b 100644 --- a/src/switch_ivr_async.c +++ b/src/switch_ivr_async.c @@ -663,6 +663,7 @@ SWITCH_DECLARE(switch_status_t) switch_ivr_session_echo(switch_core_session_t *s } switch_channel_set_flag(channel, CF_VIDEO_ECHO); + switch_channel_set_flag(channel, CF_TEXT_ECHO); while (switch_channel_ready(channel)) { status = switch_core_session_read_frame(session, &read_frame, SWITCH_IO_FLAG_NONE, 0); @@ -717,6 +718,7 @@ SWITCH_DECLARE(switch_status_t) switch_ivr_session_echo(switch_core_session_t *s switch_core_session_video_reset(session); switch_core_session_reset(session, SWITCH_TRUE, SWITCH_TRUE); + switch_channel_clear_flag(channel, CF_TEXT_ECHO); return SWITCH_STATUS_SUCCESS; } @@ -1525,6 +1527,84 @@ static switch_bool_t record_callback(switch_media_bug_t *bug, void *user_data, s return SWITCH_TRUE; } + +static switch_bool_t text_callback(switch_media_bug_t *bug, void *user_data, switch_abc_type_t type) +{ + + switch (type) { + case SWITCH_ABC_TYPE_READ_TEXT: + { + const char *text = switch_core_media_bug_get_text(bug); + + + if (!zstr(text)) { + switch_event_t *event = NULL; + switch_core_session_t *session = switch_core_media_bug_get_session(bug); + //switch_channel_t *channel = switch_core_session_get_channel(session); + + if (switch_event_create(&event, SWITCH_EVENT_REAL_TIME_TEXT) == SWITCH_STATUS_SUCCESS) { + switch_event_add_body(event, text, SWITCH_VA_NONE); + + if (switch_true(switch_core_get_variable("fire_text_events"))) { + switch_event_t *clone = NULL; + + switch_event_dup(&clone, event); + switch_event_fire(&clone); + } + + switch_core_session_queue_event(session, &event); + } + } + } + break; + default: + break; + } + + return SWITCH_TRUE; +} + +SWITCH_DECLARE(switch_status_t) switch_ivr_capture_text(switch_core_session_t *session, switch_bool_t on) +{ + switch_media_bug_t *bug; + switch_channel_t *channel = switch_core_session_get_channel(session); + + bug = (switch_media_bug_t *) switch_channel_get_private(channel, "capture_text"); + + if (on) { + + if (bug) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "text bug already attached\n"); + return SWITCH_STATUS_FALSE; + } + + + if (switch_core_media_bug_add(session, "capture_text", switch_core_session_get_uuid(session), + text_callback, NULL, 0, + SMBF_READ_TEXT_STREAM, + &bug) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Cannot attach bug\n"); + return SWITCH_STATUS_FALSE; + } + + switch_channel_set_private(channel, "capture_text", bug); + return SWITCH_STATUS_SUCCESS; + + } else { + + if (bug) { + switch_channel_set_private(channel, "capture_text", NULL); + switch_core_media_bug_remove(session, &bug); + return SWITCH_STATUS_SUCCESS; + } else { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "text bug not attached\n"); + return SWITCH_STATUS_FALSE; + } + + } +} + + SWITCH_DECLARE(switch_status_t) switch_ivr_record_session_mask(switch_core_session_t *session, const char *file, switch_bool_t on) { switch_media_bug_t *bug; diff --git a/src/switch_ivr_bridge.c b/src/switch_ivr_bridge.c index 62d5daf600..735e0efc91 100644 --- a/src/switch_ivr_bridge.c +++ b/src/switch_ivr_bridge.c @@ -39,13 +39,39 @@ static void cleanup_proxy_mode_b(switch_core_session_t *session); /* Bridge Related Stuff*/ /*********************************************************************************/ -#ifdef SWITCH_VIDEO_IN_THREADS struct vid_helper { switch_core_session_t *session_a; switch_core_session_t *session_b; int up; }; + +static void text_bridge_thread(switch_core_session_t *session, void *obj) +{ + struct vid_helper *vh = obj; + switch_status_t status; + switch_frame_t *read_frame = 0; + switch_channel_t *channel = switch_core_session_get_channel(vh->session_a); + switch_channel_t *b_channel = switch_core_session_get_channel(vh->session_b); + + vh->up = 1; + + while (switch_channel_up_nosig(channel) && switch_channel_up_nosig(b_channel) && vh->up == 1) { + status = switch_core_session_read_text_frame(vh->session_a, &read_frame, SWITCH_IO_FLAG_NONE, 0); + + if (SWITCH_READ_ACCEPTABLE(status) && !switch_test_flag(read_frame, SFF_CNG)) { + switch_core_session_write_text_frame(vh->session_b, read_frame, 0, 0); + } + + switch_core_session_write_text_frame(vh->session_a, NULL, 0, 0); + } + + vh->up = 0; +} + + +#ifdef SWITCH_VIDEO_IN_THREADS + static void video_bridge_thread(switch_core_session_t *session, void *obj) { struct vid_helper *vh = obj; @@ -223,7 +249,7 @@ static void video_bridge_thread(switch_core_session_t *session, void *obj) static void launch_video(struct vid_helper *vh) { - switch_core_media_start_video_function(vh->session_a, video_bridge_thread, vh); + switch_core_media_start_engine_function(vh->session_a, SWITCH_MEDIA_TYPE_VIDEO, video_bridge_thread, vh); } #endif @@ -334,6 +360,8 @@ static void *audio_bridge_thread(switch_thread_t *thread, void *obj) const char *exec_app = NULL; const char *exec_data = NULL; switch_codec_implementation_t read_impl = { 0 }; + uint32_t txt_launch = 0; + struct vid_helper th = { 0 }; #ifdef SWITCH_VIDEO_IN_THREADS struct vid_helper vh = { 0 }; @@ -449,6 +477,7 @@ static void *audio_bridge_thread(switch_thread_t *thread, void *obj) bridge_filter_dtmf = switch_true(switch_channel_get_variable(chan_a, "bridge_filter_dtmf")); + for (;;) { switch_channel_state_t b_state; switch_status_t status; @@ -512,6 +541,16 @@ static void *audio_bridge_thread(switch_thread_t *thread, void *obj) } continue; } + + if (switch_channel_test_flag(chan_a, CF_TEXT) && switch_channel_test_flag(chan_b, CF_TEXT) && !txt_launch) { + txt_launch++; + + th.session_a = session_a; + th.session_b = session_b; + switch_core_media_start_engine_function(th.session_a, SWITCH_MEDIA_TYPE_TEXT, text_bridge_thread, &th); + + } + #ifdef SWITCH_VIDEO_IN_THREADS if (switch_channel_test_flag(chan_a, CF_VIDEO) && switch_channel_test_flag(chan_b, CF_VIDEO) && !vid_launch) { vid_launch++; @@ -716,6 +755,7 @@ static void *audio_bridge_thread(switch_thread_t *thread, void *obj) end_of_bridge_loop: + #ifdef SWITCH_VIDEO_IN_THREADS if (vh.up > 0) { vh.up = -1; @@ -760,8 +800,18 @@ static void *audio_bridge_thread(switch_thread_t *thread, void *obj) end: + + if (switch_core_media_check_engine_function(session_a, SWITCH_MEDIA_TYPE_TEXT)) { + if (th.up == 1) { + th.up = -1; + } + + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session_a), SWITCH_LOG_DEBUG, "Ending text thread.\n"); + switch_core_media_end_engine_function(session_a, SWITCH_MEDIA_TYPE_TEXT); + } + #ifdef SWITCH_VIDEO_IN_THREADS - if (switch_core_media_check_video_function(session_a)) { + if (switch_core_media_check_engine_function(session_a, SWITCH_MEDIA_TYPE_VIDEO)) { if (vh.up == 1) { vh.up = -1; } @@ -772,7 +822,7 @@ static void *audio_bridge_thread(switch_thread_t *thread, void *obj) switch_core_session_kill_channel(session_b, SWITCH_SIG_BREAK); switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session_a), SWITCH_LOG_DEBUG, "Ending video thread.\n"); - switch_core_media_end_video_function(session_a); + switch_core_media_end_engine_function(session_a, SWITCH_MEDIA_TYPE_VIDEO); switch_channel_clear_flag(chan_a, CF_NOT_READY); switch_channel_clear_flag(chan_b, CF_NOT_READY); } diff --git a/src/switch_jitterbuffer.c b/src/switch_jitterbuffer.c index b396dd4611..2d74888ce4 100644 --- a/src/switch_jitterbuffer.c +++ b/src/switch_jitterbuffer.c @@ -37,7 +37,7 @@ #define PERIOD_LEN 250 #define MAX_FRAME_PADDING 2 #define MAX_MISSING_SEQ 20 -#define jb_debug(_jb, _level, _format, ...) if (_jb->debug_level >= _level) switch_log_printf(SWITCH_CHANNEL_SESSION_LOG_CLEAN(_jb->session), SWITCH_LOG_ALERT, "JB:%p:%s lv:%d ln:%.4d sz:%.3u/%.3u/%.3u/%.3u c:%.3u %.3u/%.3u/%.3u/%.3u %.2f%% ->" _format, (void *) _jb, (jb->type == SJB_AUDIO ? "aud" : "vid"), _level, __LINE__, _jb->min_frame_len, _jb->max_frame_len, _jb->frame_len, _jb->complete_frames, _jb->period_count, _jb->consec_good_count, _jb->period_good_count, _jb->consec_miss_count, _jb->period_miss_count, _jb->period_miss_pct, __VA_ARGS__) +#define jb_debug(_jb, _level, _format, ...) if (_jb->debug_level >= _level) switch_log_printf(SWITCH_CHANNEL_SESSION_LOG_CLEAN(_jb->session), SWITCH_LOG_ALERT, "JB:%p:%s lv:%d ln:%.4d sz:%.3u/%.3u/%.3u/%.3u c:%.3u %.3u/%.3u/%.3u/%.3u %.2f%% ->" _format, (void *) _jb, (jb->type == SJB_TEXT ? "txt" : (jb->type == SJB_AUDIO ? "aud" : "vid")), _level, __LINE__, _jb->min_frame_len, _jb->max_frame_len, _jb->frame_len, _jb->complete_frames, _jb->period_count, _jb->consec_good_count, _jb->period_good_count, _jb->consec_miss_count, _jb->period_miss_count, _jb->period_miss_pct, __VA_ARGS__) //const char *TOKEN_1 = "ONE"; //const char *TOKEN_2 = "TWO"; @@ -101,6 +101,8 @@ struct switch_jb_s { switch_jb_type_t type; switch_core_session_t *session; switch_channel_t *channel; + uint32_t buffer_lag; + uint32_t flush; }; @@ -1117,7 +1119,7 @@ SWITCH_DECLARE(switch_status_t) switch_jb_put_packet(switch_jb_t *jb, switch_rtp if (!want) want = got; - if (switch_test_flag(jb, SJB_QUEUE_ONLY) || jb->type == SJB_AUDIO) { + if (switch_test_flag(jb, SJB_QUEUE_ONLY) || jb->type == SJB_AUDIO || jb->type == SJB_TEXT) { jb->next_seq = htons(got + 1); } else { @@ -1203,12 +1205,26 @@ SWITCH_DECLARE(switch_status_t) switch_jb_get_packet(switch_jb_t *jb, switch_rtp switch_mutex_lock(jb->mutex); if (jb->complete_frames == 0) { + jb->flush = 0; switch_goto_status(SWITCH_STATUS_BREAK, end); } if (jb->complete_frames < jb->frame_len) { - jb_debug(jb, 2, "BUFFERING %u/%u\n", jb->complete_frames , jb->frame_len); - switch_goto_status(SWITCH_STATUS_MORE_DATA, end); + + if (jb->type == SJB_TEXT) { + if (jb->complete_frames && !jb->buffer_lag) { + jb->buffer_lag = 10; + } + + if (jb->buffer_lag && --jb->buffer_lag == 0) { + jb->flush = 1; + } + } + + if (!jb->flush) { + jb_debug(jb, 2, "BUFFERING %u/%u\n", jb->complete_frames , jb->frame_len); + switch_goto_status(SWITCH_STATUS_MORE_DATA, end); + } } jb_debug(jb, 2, "GET PACKET %u/%u n:%d\n", jb->complete_frames , jb->frame_len, jb->visible_nodes); @@ -1254,8 +1270,8 @@ SWITCH_DECLARE(switch_status_t) switch_jb_get_packet(switch_jb_t *jb, switch_rtp } } } - } + jb->period_miss_pct = ((double)jb->period_miss_count / jb->period_count) * 100; @@ -1263,7 +1279,7 @@ SWITCH_DECLARE(switch_status_t) switch_jb_get_packet(switch_jb_t *jb, switch_rtp jb_debug(jb, 2, "Miss percent %02f too high, resetting buffer.\n", jb->period_miss_pct); switch_jb_reset(jb); } - + if ((status = jb_next_packet(jb, &node)) == SWITCH_STATUS_SUCCESS) { jb_debug(jb, 2, "Found next frame cur ts: %u seq: %u\n", htonl(node->packet.header.ts), htons(node->packet.header.seq)); @@ -1272,7 +1288,7 @@ SWITCH_DECLARE(switch_status_t) switch_jb_get_packet(switch_jb_t *jb, switch_rtp jb->highest_read_seq = node->packet.header.seq; } - if (jb->read_init && htons(node->packet.header.seq) >= htons(jb->highest_read_seq) && (ntohl(node->packet.header.ts) > ntohl(jb->highest_read_ts))) { + if (jb->type == SJB_TEXT || (jb->read_init && htons(node->packet.header.seq) >= htons(jb->highest_read_seq) && (ntohl(node->packet.header.ts) > ntohl(jb->highest_read_ts)))) { jb->complete_frames--; jb_debug(jb, 2, "READ frame ts: %u complete=%u/%u n:%u\n", ntohl(node->packet.header.ts), jb->complete_frames , jb->frame_len, jb->visible_nodes); jb->highest_read_ts = node->packet.header.ts; diff --git a/src/switch_rtp.c b/src/switch_rtp.c index 9e61003009..05f9b63be5 100644 --- a/src/switch_rtp.c +++ b/src/switch_rtp.c @@ -511,7 +511,7 @@ typedef enum { static void do_2833(switch_rtp_t *rtp_session); -#define rtp_type(rtp_session) rtp_session->flags[SWITCH_RTP_FLAG_VIDEO] ? "video" : "audio" +#define rtp_type(rtp_session) rtp_session->flags[SWITCH_RTP_FLAG_TEXT] ? "text" : (rtp_session->flags[SWITCH_RTP_FLAG_VIDEO] ? "video" : "audio") static void switch_rtp_change_ice_dest(switch_rtp_t *rtp_session, switch_rtp_ice_t *ice, const char *host, switch_port_t port) @@ -1278,7 +1278,7 @@ static void handle_ice(switch_rtp_t *rtp_session, switch_rtp_ice_t *ice, void *d msg.message_id = SWITCH_MESSAGE_INDICATE_STUN_ERROR; switch_core_session_receive_message(rtp_session->session, &msg); switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(rtp_session->session), SWITCH_LOG_DEBUG, - "STUN/ICE binding error received on %s channel\n", rtp_session->flags[SWITCH_RTP_FLAG_VIDEO] ? "video" : "audio"); + "STUN/ICE binding error received on %s channel\n", rtp_type(rtp_session)); } } @@ -3626,7 +3626,7 @@ SWITCH_DECLARE(switch_status_t) switch_rtp_add_crypto_key(switch_rtp_t *rtp_sess if (status == SWITCH_STATUS_SUCCESS) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(rtp_session->session), SWITCH_LOG_INFO, "Activating %s Secure %s RECV\n", - rtp_session->flags[SWITCH_RTP_FLAG_VIDEO] ? "Video" : "Audio", idx ? "RTCP" : "RTP"); + rtp_type(rtp_session), idx ? "RTCP" : "RTP"); rtp_session->flags[SWITCH_RTP_FLAG_SECURE_RECV] = 1; } else { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(rtp_session->session), SWITCH_LOG_ERROR, "Error allocating srtp [%d]\n", stat); @@ -3648,7 +3648,7 @@ SWITCH_DECLARE(switch_status_t) switch_rtp_add_crypto_key(switch_rtp_t *rtp_sess if (status == SWITCH_STATUS_SUCCESS) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(rtp_session->session), SWITCH_LOG_INFO, "Activating %s Secure %s SEND\n", - rtp_session->flags[SWITCH_RTP_FLAG_VIDEO] ? "Video" : "Audio", idx ? "RTCP" : "RTP"); + rtp_type(rtp_session), idx ? "RTCP" : "RTP"); rtp_session->flags[SWITCH_RTP_FLAG_SECURE_SEND] = 1; } else { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(rtp_session->session), SWITCH_LOG_ERROR, "Error allocating SRTP [%d]\n", stat); @@ -4057,11 +4057,11 @@ SWITCH_DECLARE(switch_timer_t *) switch_rtp_get_media_timer(switch_rtp_t *rtp_se SWITCH_DECLARE(switch_jb_t *) switch_rtp_get_jitter_buffer(switch_rtp_t *rtp_session) { - if (!switch_rtp_ready(rtp_session) || !rtp_session->jb) { + if (!switch_rtp_ready(rtp_session)) { return NULL; } - return rtp_session->jb; + return rtp_session->jb ? rtp_session->jb : rtp_session->vb; } SWITCH_DECLARE(switch_status_t) switch_rtp_pause_jitter_buffer(switch_rtp_t *rtp_session, switch_bool_t pause) @@ -4123,7 +4123,7 @@ SWITCH_DECLARE(switch_status_t) switch_rtp_set_video_buffer_size(switch_rtp_t *r rtp_session->last_max_vb_frames = max_frames; if (!rtp_session->vb) { - switch_jb_create(&rtp_session->vb, SJB_VIDEO, frames, max_frames, rtp_session->pool); + switch_jb_create(&rtp_session->vb, rtp_session->flags[SWITCH_RTP_FLAG_TEXT] ? SJB_TEXT : SJB_VIDEO, frames, max_frames, rtp_session->pool); switch_jb_set_session(rtp_session->vb, rtp_session->session); } else { switch_jb_set_frames(rtp_session->vb, frames, max_frames); @@ -5659,7 +5659,7 @@ static switch_status_t read_rtp_packet(switch_rtp_t *rtp_session, switch_size_t switch_jb_destroy(&rtp_session->vb); } } - + if (rtp_session->has_rtp && *bytes) { uint32_t read_ssrc = ntohl(rtp_session->last_rtp_hdr.ssrc); @@ -5718,7 +5718,7 @@ static switch_status_t read_rtp_packet(switch_rtp_t *rtp_session, switch_size_t } if (!*bytes || rtp_session->has_rtp) { - + if (rtp_session->jb && !rtp_session->pause_jb && jb_valid(rtp_session)) { switch_status_t jstatus = switch_jb_get_packet(rtp_session->jb, (switch_rtp_packet_t *) &rtp_session->recv_msg, bytes); @@ -5778,9 +5778,21 @@ static switch_status_t read_rtp_packet(switch_rtp_t *rtp_session, switch_size_t default: break; } - + + if (vstatus == SWITCH_STATUS_NOTFOUND && rtp_session->flags[SWITCH_RTP_FLAG_TEXT]) { + int pt = get_recv_payload(rtp_session); + (*flags) |= SFF_PLC; + status = SWITCH_STATUS_SUCCESS; + *bytes = switch_jb_get_last_read_len(rtp_session->vb); + rtp_session->last_rtp_hdr = rtp_session->recv_msg.header; + if (pt > -1) { + rtp_session->last_rtp_hdr.pt = pt; + } + } + if (vstatus == SWITCH_STATUS_SUCCESS) { rtp_session->last_rtp_hdr = rtp_session->recv_msg.header; + if (!xcheck_jitter) { check_jitter(rtp_session); xcheck_jitter = *bytes; @@ -5854,6 +5866,7 @@ static void handle_nack(switch_rtp_t *rtp_session, uint32_t nack) } //switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG1, "RE----SEND %u\n", ntohs(send_msg->header.seq)); + switch_rtp_write_raw(rtp_session, (void *) &send_msg, &bytes, SWITCH_FALSE); } else { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(rtp_session->session), SWITCH_LOG_DEBUG1, "Cannot send NACK for seq %u\n", ntohs(seq) + i); @@ -6281,7 +6294,9 @@ static int rtp_common_read(switch_rtp_t *rtp_session, switch_payload_t *payload_ } } - if (hot_socket && (rtp_session->hot_hits % 10) != 0) { + if (rtp_session->flags[SWITCH_RTP_FLAG_TEXT]) { + ///NOOP + } else if (hot_socket && (rtp_session->hot_hits % 10) != 0) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(rtp_session->session), SWITCH_LOG_DEBUG10, "%s timer while HOT\n", rtp_session_name(rtp_session)); switch_core_timer_next(&rtp_session->timer); } else if (hot_socket) { @@ -6368,10 +6383,6 @@ static int rtp_common_read(switch_rtp_t *rtp_session, switch_payload_t *payload_ poll_status = switch_poll(rtp_session->read_pollfd, 1, &fdr, pt); - //if (rtp_session->flags[SWITCH_RTP_FLAG_VIDEO]) { - // switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(rtp_session->session), SWITCH_LOG_WARNING, "WTF Poll %d\n", poll_status); - //} - if (!rtp_session->flags[SWITCH_RTP_FLAG_VIDEO] && rtp_session->dtmf_data.out_digit_dur > 0) { return_cng_frame(); } @@ -6393,7 +6404,7 @@ static int rtp_common_read(switch_rtp_t *rtp_session, switch_payload_t *payload_ if (read_pretriggered) { read_pretriggered = 0; } else { - + status = read_rtp_packet(rtp_session, &bytes, flags, poll_status, SWITCH_TRUE); if (status == SWITCH_STATUS_GENERR) { @@ -6665,11 +6676,11 @@ static int rtp_common_read(switch_rtp_t *rtp_session, switch_payload_t *payload_ if (bytes && rtp_session->last_rtp_hdr.m && rtp_session->last_rtp_hdr.pt != rtp_session->recv_te && !rtp_session->flags[SWITCH_RTP_FLAG_VIDEO] && + !rtp_session->flags[SWITCH_RTP_FLAG_TEXT] && !(rtp_session->rtp_bugs & RTP_BUG_IGNORE_MARK_BIT)) { rtp_flush_read_buffer(rtp_session, SWITCH_RTP_FLUSH_ONCE); } - if (rtp_session->last_rtp_hdr.pt == rtp_session->cng_pt || rtp_session->last_rtp_hdr.pt == 13) { *flags |= SFF_NOT_AUDIO; } else { @@ -6752,6 +6763,19 @@ static int rtp_common_read(switch_rtp_t *rtp_session, switch_payload_t *payload_ } } + if (rtp_session->flags[SWITCH_RTP_FLAG_TEXT]) { + if (!bytes) { + if (rtp_session->flags[SWITCH_RTP_FLAG_USE_TIMER]) { + switch_core_timer_next(&rtp_session->timer); + } + return_cng_frame(); + } else { + *payload_type = rtp_session->last_rtp_hdr.pt; + ret = (int) bytes; + goto end; + } + } + if (bytes && (rtp_session->flags[SWITCH_RTP_FLAG_PROXY_MEDIA] || rtp_session->flags[SWITCH_RTP_FLAG_UDPTL])) { /* Fast PASS! */ *flags |= SFF_PROXY_PACKET; @@ -7923,14 +7947,19 @@ SWITCH_DECLARE(int) switch_rtp_write_frame(switch_rtp_t *rtp_session, switch_fra #endif } - if (switch_test_flag(frame, SFF_RTP_HEADER)) { - switch_size_t wrote = switch_rtp_write_manual(rtp_session, frame->data, frame->datalen, - frame->m, frame->payload, (uint32_t) (frame->timestamp), &frame->flags); + + if (switch_test_flag(frame, SFF_RTP_HEADER) || rtp_session->flags[SWITCH_RTP_FLAG_TEXT]) { + switch_size_t wrote; + + wrote = switch_rtp_write_manual(rtp_session, frame->data, frame->datalen, + frame->m, frame->payload, (uint32_t) (frame->timestamp), &frame->flags); rtp_session->stats.outbound.raw_bytes += wrote; rtp_session->stats.outbound.media_bytes += wrote; rtp_session->stats.outbound.media_packet_count++; rtp_session->stats.outbound.packet_count++; + + return wrote; } if (frame->pmap && rtp_session->pmaps && *rtp_session->pmaps) { From 7dd872e9b87cc5114e7845f6cd7d45ae53b9aac6 Mon Sep 17 00:00:00 2001 From: Seven Du Date: Tue, 19 Jul 2016 00:48:43 +0800 Subject: [PATCH 7/7] FS-9575 #resolve [Add MRCP] --- Makefile.am | 2 + conf/vanilla/autoload_configs/msrp.conf.xml | 10 + src/include/switch_core_media.h | 5 +- src/include/switch_msrp.h | 146 ++ src/include/switch_types.h | 2 + .../applications/mod_commands/mod_commands.c | 2 + src/mod/endpoints/mod_sofia/mod_sofia.c | 17 + src/mod/endpoints/mod_sofia/sofia_glue.c | 1 + src/switch_core.c | 4 + src/switch_core_media.c | 246 ++- src/switch_msrp.c | 1569 +++++++++++++++++ 11 files changed, 1988 insertions(+), 16 deletions(-) create mode 100644 conf/vanilla/autoload_configs/msrp.conf.xml create mode 100644 src/include/switch_msrp.h create mode 100644 src/switch_msrp.c diff --git a/Makefile.am b/Makefile.am index ef5747e124..286cfe0536 100644 --- a/Makefile.am +++ b/Makefile.am @@ -296,6 +296,7 @@ library_include_HEADERS = \ src/include/switch_curl.h \ src/include/switch_json.h \ src/include/switch_utf8.h \ + src/include/switch_msrp.h \ src/include/switch_vpx.h \ libs/libteletone/src/libteletone_detect.h \ libs/libteletone/src/libteletone_generate.h \ @@ -376,6 +377,7 @@ libfreeswitch_la_SOURCES = \ src/switch_curl.c \ src/switch_hashtable.c\ src/switch_utf8.c \ + src/switch_msrp.c \ src/switch_vpx.c \ libs/libtpl-1.5/src/tpl.c \ libs/libteletone/src/libteletone_detect.c \ diff --git a/conf/vanilla/autoload_configs/msrp.conf.xml b/conf/vanilla/autoload_configs/msrp.conf.xml new file mode 100644 index 0000000000..ad5dbace8c --- /dev/null +++ b/conf/vanilla/autoload_configs/msrp.conf.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/include/switch_core_media.h b/src/include/switch_core_media.h index 5bca68d6c0..c85388509d 100644 --- a/src/include/switch_core_media.h +++ b/src/include/switch_core_media.h @@ -35,6 +35,7 @@ #include +#include SWITCH_BEGIN_EXTERN_C @@ -371,7 +372,9 @@ SWITCH_DECLARE(switch_status_t) switch_rtp_text_factory_destroy(switch_rtp_text_ SWITCH_DECLARE(switch_status_t) switch_core_session_print(switch_core_session_t *session, const char *data); SWITCH_DECLARE(switch_status_t) switch_core_session_printf(switch_core_session_t *session, const char *fmt, ...); - + +SWITCH_DECLARE(switch_msrp_session_t *) switch_core_media_get_msrp_session(switch_core_session_t *session); + SWITCH_END_EXTERN_C #endif /* For Emacs: diff --git a/src/include/switch_msrp.h b/src/include/switch_msrp.h new file mode 100644 index 0000000000..8a6cf62dfb --- /dev/null +++ b/src/include/switch_msrp.h @@ -0,0 +1,146 @@ +/* + * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * Copyright (C) 2011-2016, Seven Du + * + * Version: MPL 1.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * + * The Initial Developer of the Original Code is + * Seven Du + * Portions created by the Initial Developer are Copyright (C) + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * + * + * msrp.h -- MSRP lib + * + */ + +#ifndef _MSRP_H +#define _MSRP_H + +#include +#include + +#define MSRP_LISTEN_PORT 2855 +#define MSRP_SSL_LISTEN_PORT 2856 + +enum { + MSRP_ST_WAIT_HEADER, + MSRP_ST_PARSE_HEADER, + MSRP_ST_WAIT_BODY, + MSRP_ST_DONE, + MSRP_ST_ERROR, + + MSRP_METHOD_REPLY, + MSRP_METHOD_SEND, + MSRP_METHOD_AUTH, + MSRP_METHOD_REPORT, + +}; + +enum { + MSRP_H_FROM_PATH, + MSRP_H_TO_PATH, + MSRP_H_MESSAGE_ID, + MSRP_H_CONTENT_TYPE, + MSRP_H_SUCCESS_REPORT, + MSRP_H_FAILURE_REPORT, + MSRP_H_STATUS, + MSRP_H_KEEPALIVE, + MSRP_H_UNKNOWN +}; + +typedef struct msrp_msg_s { + int state; + int method; + char *headers[12]; + int last_header; + char *transaction_id; + char *delimiter; + int code_number; + char *code_description; + switch_size_t byte_start; + switch_size_t byte_end; + switch_size_t bytes; + switch_size_t payload_bytes; + int range_star; /* range-end is '*' */ + char *last_p; + char *payload; + struct msrp_msg_s *next; +} msrp_msg_t; + +typedef struct msrp_msg_s switch_msrp_msg_t; + +typedef struct msrp_socket_s { + switch_port_t port; + switch_socket_t *sock; + switch_thread_t *thread; + int secure; +} msrp_socket_t; + +typedef struct msrp_client_socket_s { + switch_socket_t *sock; + int secure; +} msrp_client_socket_t; + +typedef struct { + switch_memory_pool_t *pool; + int secure; + char *remote_path; + char *remote_accept_types; + char *remote_accept_wrapped_types; + char *remote_setup; + char *remote_file_selector; + char *local_path; + char *local_accept_types; + char *local_accept_wrapped_types; + char *local_setup; + char *local_file_selector; + int local_port; + char *call_id; + msrp_msg_t *msrp_msg; + msrp_msg_t *last_msg; + switch_mutex_t *mutex; + switch_size_t msrp_msg_buffer_size; + switch_size_t msrp_msg_count; + msrp_socket_t *msock; + msrp_client_socket_t *csock; + switch_frame_t frame; + uint8_t frame_data[SWITCH_RTP_MAX_BUF_LEN]; +} switch_msrp_session_t; + +SWITCH_DECLARE(switch_status_t) switch_msrp_init(); +SWITCH_DECLARE(switch_status_t) switch_msrp_destroy(); +SWITCH_DECLARE(switch_msrp_session_t *)switch_msrp_session_new(switch_memory_pool_t *pool, switch_bool_t secure); +SWITCH_DECLARE(switch_status_t) switch_msrp_session_destroy(switch_msrp_session_t **ms); +// switch_status_t switch_msrp_session_push_msg(switch_msrp_session_t *ms, msrp_msg_t *msg); +SWITCH_DECLARE(switch_msrp_msg_t *)switch_msrp_session_pop_msg(switch_msrp_session_t *ms); +SWITCH_DECLARE(switch_status_t) switch_msrp_send(switch_msrp_session_t *ms, msrp_msg_t *msg); + +SWITCH_DECLARE(void) switch_msrp_load_apis_and_applications(switch_loadable_module_interface_t **moudle_interface); +#endif + +/* For Emacs: + * Local Variables: + * mode:c + * indent-tabs-mode:t + * tab-width:4 + * c-basic-offset:4 + * End: + * For VIM: + * vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet: + */ diff --git a/src/include/switch_types.h b/src/include/switch_types.h index 7b81a07e8f..c00a76a5cc 100644 --- a/src/include/switch_types.h +++ b/src/include/switch_types.h @@ -1376,6 +1376,7 @@ typedef enum { CC_QUEUEABLE_DTMF_DELAY, CC_IO_OVERRIDE, CC_RTP_RTT, + CC_MSRP, /* WARNING: DO NOT ADD ANY FLAGS BELOW THIS LINE */ CC_FLAG_MAX } switch_channel_cap_t; @@ -1528,6 +1529,7 @@ typedef enum { CF_TEXT_ECHO, CF_TEXT_ACTIVE, CF_TEXT_IDLE, + CF_MSRP, /* WARNING: DO NOT ADD ANY FLAGS BELOW THIS LINE */ /* IF YOU ADD NEW ONES CHECK IF THEY SHOULD PERSIST OR ZERO THEM IN switch_core_session.c switch_core_session_request_xml() */ CF_FLAG_MAX diff --git a/src/mod/applications/mod_commands/mod_commands.c b/src/mod/applications/mod_commands/mod_commands.c index f44b4ab55b..e1cb3f0063 100644 --- a/src/mod/applications/mod_commands/mod_commands.c +++ b/src/mod/applications/mod_commands/mod_commands.c @@ -7498,6 +7498,8 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_commands_load) switch_console_set_complete("add file_exists"); switch_console_set_complete("add getcputime"); + switch_msrp_load_apis_and_applications(module_interface); + /* indicate that the module should continue to be loaded */ return SWITCH_STATUS_NOUNLOAD; } diff --git a/src/mod/endpoints/mod_sofia/mod_sofia.c b/src/mod/endpoints/mod_sofia/mod_sofia.c index 017150d6ed..8b5f67c811 100644 --- a/src/mod/endpoints/mod_sofia/mod_sofia.c +++ b/src/mod/endpoints/mod_sofia/mod_sofia.c @@ -927,6 +927,23 @@ static switch_status_t sofia_read_text_frame(switch_core_session_t *session, swi static switch_status_t sofia_write_text_frame(switch_core_session_t *session, switch_frame_t *frame, switch_io_flag_t flags, int stream_id) { + if (switch_channel_test_flag(switch_core_session_get_channel(session), CF_MSRP)) { + switch_msrp_session_t *msrp_session = switch_core_media_get_msrp_session(session); + + if (frame && msrp_session) { + switch_msrp_msg_t msrp_msg = { 0 }; + + msrp_msg.headers[MSRP_H_CONTENT_TYPE] = "message/cpim"; + // msrp_msg.headers[MSRP_H_CONTENT_TYPE] = "text/plain"; + msrp_msg.payload = frame->data; + msrp_msg.payload_bytes = frame->datalen; + + return switch_msrp_send(msrp_session, &msrp_msg); + } + + return SWITCH_STATUS_FALSE; + } + return switch_core_media_write_frame(session, frame, flags, stream_id, SWITCH_MEDIA_TYPE_TEXT); } diff --git a/src/mod/endpoints/mod_sofia/sofia_glue.c b/src/mod/endpoints/mod_sofia/sofia_glue.c index 9c493bf9a9..ac5b812388 100644 --- a/src/mod/endpoints/mod_sofia/sofia_glue.c +++ b/src/mod/endpoints/mod_sofia/sofia_glue.c @@ -160,6 +160,7 @@ void sofia_glue_attach_private(switch_core_session_t *session, sofia_profile_t * switch_channel_set_cap(tech_pvt->channel, CC_JITTERBUFFER); switch_channel_set_cap(tech_pvt->channel, CC_FS_RTP); switch_channel_set_cap(tech_pvt->channel, CC_RTP_RTT); + switch_channel_set_cap(tech_pvt->channel, CC_MSRP); switch_channel_set_cap(tech_pvt->channel, CC_QUEUEABLE_DTMF_DELAY); diff --git a/src/switch_core.c b/src/switch_core.c index e06b6b1f39..d19bc75ed2 100644 --- a/src/switch_core.c +++ b/src/switch_core.c @@ -42,6 +42,7 @@ #include #include "private/switch_core_pvt.h" #include +#include #ifndef WIN32 #include #ifdef HAVE_SETRLIMIT @@ -2391,6 +2392,8 @@ SWITCH_DECLARE(switch_status_t) switch_core_init_and_modload(switch_core_flag_t switch_core_set_signal_handlers(); switch_load_network_lists(SWITCH_FALSE); + switch_msrp_init(); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CONSOLE, "Bringing up environment.\n"); switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CONSOLE, "Loading Modules.\n"); if (switch_loadable_module_init(SWITCH_TRUE) != SWITCH_STATUS_SUCCESS) { @@ -2905,6 +2908,7 @@ SWITCH_DECLARE(switch_status_t) switch_core_destroy(void) switch_scheduler_task_thread_stop(); switch_rtp_shutdown(); + switch_msrp_destroy(); if (switch_test_flag((&runtime), SCF_USE_AUTO_NAT)) { switch_nat_shutdown(); diff --git a/src/switch_core_media.c b/src/switch_core_media.c index 06b4d312f8..69923c6fa4 100644 --- a/src/switch_core_media.c +++ b/src/switch_core_media.c @@ -24,6 +24,7 @@ * Contributor(s): * * Anthony Minessale II + * Seven Du * * switch_core_media.c -- Core Media * @@ -56,7 +57,6 @@ static void gen_ice(switch_core_session_t *session, switch_media_type_t type, co #define MAX_RED_FRAMES 25 #define RED_PACKET_SIZE 100 - typedef enum { SMF_INIT = (1 << 0), SMF_READY = (1 << 1), @@ -217,6 +217,7 @@ struct switch_media_handle_s { switch_core_media_flag_t media_flags[SCMF_MAX]; smh_flag_t flags; switch_rtp_engine_t engines[SWITCH_MEDIA_TYPE_TOTAL]; + switch_msrp_session_t *msrp_session; switch_mutex_t *read_mutex[SWITCH_MEDIA_TYPE_TOTAL]; switch_mutex_t *write_mutex[SWITCH_MEDIA_TYPE_TOTAL]; char *codec_order[SWITCH_MAX_CODECS]; @@ -1702,8 +1703,7 @@ SWITCH_DECLARE(void) switch_media_handle_destroy(switch_core_session_t *session) switch_core_session_unset_write_codec(session); switch_core_media_deactivate_rtp(session); - - + if (smh->msrp_session) switch_msrp_session_destroy(&smh->msrp_session); } @@ -2417,6 +2417,42 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_read_frame(switch_core_session return SWITCH_STATUS_FALSE; } + if (type == SWITCH_MEDIA_TYPE_TEXT && smh->msrp_session) { + switch_msrp_session_t *msrp_session = smh->msrp_session; + switch_frame_t *rframe = &msrp_session->frame; + + msrp_msg_t *msrp_msg = switch_msrp_session_pop_msg(msrp_session); + + if (0 && msrp_msg && msrp_msg->method == MSRP_METHOD_SEND) { /*echo back*/ + char *p; + p = msrp_msg->headers[MSRP_H_TO_PATH]; + msrp_msg->headers[MSRP_H_TO_PATH] = msrp_msg->headers[MSRP_H_FROM_PATH]; + msrp_msg->headers[MSRP_H_FROM_PATH] = p; + switch_msrp_send(msrp_session, msrp_msg); + } + + if (msrp_msg && msrp_msg->method == MSRP_METHOD_SEND) { + rframe->data = msrp_session->frame_data; + rframe->datalen = msrp_msg->payload_bytes; + rframe->packetlen = msrp_msg->payload_bytes; + memcpy(rframe->data, msrp_msg->payload, msrp_msg->payload_bytes); + rframe->m = 1; + + *frame = rframe; + + switch_safe_free(msrp_msg); + msrp_msg = NULL; + status = SWITCH_STATUS_SUCCESS; + + return status; + } + + *frame = NULL; + status = SWITCH_STATUS_FALSE; + + return status; + } + engine = &smh->engines[type]; if (type != SWITCH_MEDIA_TYPE_TEXT && (!engine->read_codec.implementation || !switch_core_codec_ready(&engine->read_codec))) { @@ -4051,7 +4087,7 @@ SWITCH_DECLARE(uint8_t) switch_core_media_negotiate_sdp(switch_core_session_t *s switch_channel_t *channel = switch_core_session_get_channel(session); const char *val; const char *crypto = NULL; - int got_crypto = 0, got_video_crypto = 0, got_audio = 0, saw_audio = 0, got_avp = 0, got_video_avp = 0, got_video_savp = 0, got_savp = 0, got_udptl = 0, got_webrtc = 0, got_text = 0, got_text_crypto = 0; + int got_crypto = 0, got_video_crypto = 0, got_audio = 0, saw_audio = 0, got_avp = 0, got_video_avp = 0, got_video_savp = 0, got_savp = 0, got_udptl = 0, got_webrtc = 0, got_text = 0, got_text_crypto = 0, got_msrp = 0; int scrooge = 0; sdp_parser_t *parser = NULL; sdp_session_t *sdp; @@ -4231,6 +4267,97 @@ SWITCH_DECLARE(uint8_t) switch_core_media_negotiate_sdp(switch_core_session_t *s } } else if (m->m_proto == sdp_proto_udptl) { got_udptl++; + } else if (m->m_proto == sdp_proto_msrp || m->m_proto == sdp_proto_msrps){ + got_msrp++; + } + + if(got_msrp && m->m_type == sdp_media_message) { + if (!smh->msrp_session) { + smh->msrp_session = switch_msrp_session_new(switch_core_session_get_pool(session), m->m_proto == sdp_proto_msrps); + } + + if (!smh->msrp_session) { + goto endmsrp; + } + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "MSRP session created\n"); + + for (attr = m->m_attributes; attr; attr = attr->a_next) { + // switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "[%s]=[%s]\n", attr->a_name, attr->a_value); + if (!strcasecmp(attr->a_name, "path") && attr->a_value) { + smh->msrp_session->remote_path = switch_core_session_strdup(session, attr->a_value); + switch_channel_set_variable(session->channel, "sip_msrp_remote_path", attr->a_value); + } else if (!strcasecmp(attr->a_name, "accept-types") && attr->a_value) { + smh->msrp_session->remote_accept_types = switch_core_session_strdup(session, attr->a_value); + switch_channel_set_variable(session->channel, "sip_msrp_remote_accept_types", attr->a_value); + } else if (!strcasecmp(attr->a_name, "accept-wrapped-types") && attr->a_value) { + smh->msrp_session->remote_accept_wrapped_types = switch_core_session_strdup(session, attr->a_value); + switch_channel_set_variable(session->channel, "sip_msrp_remote_accept_wrapped_types", attr->a_value); + } else if (!strcasecmp(attr->a_name, "setup") && attr->a_value) { + smh->msrp_session->remote_setup = switch_core_session_strdup(session, attr->a_value); + switch_channel_set_variable(session->channel, "sip_msrp_remote_setup", attr->a_value); + } else if (!strcasecmp(attr->a_name, "file-selector") && attr->a_value) { + char *tmp = switch_mprintf("%s", attr->a_value); + char *argv[4] = { 0 }; + int argc; + int i; + + smh->msrp_session->remote_file_selector = switch_core_session_strdup(session, attr->a_value); + switch_channel_set_variable(session->channel, "sip_msrp_remote_file_selector", attr->a_value); + + argc = switch_separate_string(tmp, ' ', argv, (sizeof(argv) / sizeof(argv[0]))); + + for(i = 0; ichannel, "sip_msrp_file_name", p); + } else if (!strncasecmp(argv[i], "type:", 5)) { + switch_channel_set_variable(session->channel, "sip_msrp_file_type", argv[i] + 5); + } + if (!strncasecmp(argv[i], "size:", 5)) { + switch_channel_set_variable(session->channel, "sip_msrp_file_size", argv[i] + 5); + } + if (!strncasecmp(argv[i], "hash:", 5)) { + switch_channel_set_variable(session->channel, "sip_msrp_file_hash", argv[i] + 5); + } + } + switch_safe_free(tmp); + } else if (!strcasecmp(attr->a_name, "file-transfer-id") && attr->a_value) { + switch_channel_set_variable(session->channel, "sip_msrp_file_transfer_id", attr->a_value); + } else if (!strcasecmp(attr->a_name, "file-disposition") && attr->a_value) { + switch_channel_set_variable(session->channel, "sip_msrp_file_disposition", attr->a_value); + } else if (!strcasecmp(attr->a_name, "file-date") && attr->a_value) { + switch_channel_set_variable(session->channel, "sip_msrp_file_date", attr->a_value); + } else if (!strcasecmp(attr->a_name, "file-icon") && attr->a_value) { + switch_channel_set_variable(session->channel, "sip_msrp_file_icon", attr->a_value); + } else if (!strcasecmp(attr->a_name, "file-range") && attr->a_value) { + switch_channel_set_variable(session->channel, "sip_msrp_file_range", attr->a_value); + } + } + + smh->msrp_session->call_id = switch_core_session_get_uuid(session); + smh->msrp_session->local_accept_types = smh->msrp_session->remote_accept_types; + smh->msrp_session->local_accept_wrapped_types = smh->msrp_session->remote_accept_types; + smh->msrp_session->local_setup = smh->msrp_session->remote_setup; + + switch_channel_set_flag(session->channel, CF_TEXT); + switch_channel_set_flag(session->channel, CF_TEXT_POSSIBLE); + switch_channel_set_flag(session->channel, CF_MSRP); + + switch_core_session_start_text_thread(session); + + endmsrp:; } if (got_udptl && m->m_type == sdp_media_image) { @@ -6748,8 +6875,8 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_choose_port(switch_core_sessio if (!zstr(smh->mparams->extrtpip)) { /* and we've got an ext-rtp-ip, eg, from verto config */ use_ip = smh->mparams->extrtpip; /* let's use it for composing local sdp to send to client */ /* - switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, - "%s will use %s instead of %s in SDP, because we're originating and we have an ext-rtp-ip setting\n", + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, + "%s will use %s instead of %s in SDP, because we're originating and we have an ext-rtp-ip setting\n", switch_channel_get_name(smh->session->channel), smh->mparams->extrtpip, smh->mparams->rtpip); */ } @@ -7508,7 +7635,9 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_activate_rtp(switch_core_sessi text: - + if (switch_channel_test_flag(session->channel, CF_MSRP)) { // skip RTP RTT + goto video; + } if (switch_channel_test_flag(session->channel, CF_TEXT_POSSIBLE) && t_engine->cur_payload_map->rm_encoding && t_engine->cur_payload_map->remote_sdp_port) { /******************************************************************************************/ @@ -9729,7 +9858,85 @@ SWITCH_DECLARE(void) switch_core_media_gen_local_sdp(switch_core_session_t *sess } - ///TEXT +msrp: + + if (smh->msrp_session) { + switch_msrp_session_t *msrp_session = smh->msrp_session; + + if (!zstr(msrp_session->remote_path)) { + if (zstr(msrp_session->local_path)) { + msrp_session->local_path = switch_core_session_sprintf(session, + "msrp%s://%s:%d/%s;tcp", + msrp_session->secure ? "s" : "", + ip, msrp_session->local_port, msrp_session->call_id); + } + + switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), + "m=message %d TCP/%sMSRP *\n" + "a=path:%s\n" + "a=accept-types:%s\n" + "a=accept-wrapped-types:%s\n" + "a=setup:passive\n", + msrp_session->local_port, + msrp_session->secure ? "TLS/" : "", + msrp_session->local_path, + msrp_session->local_accept_types, + msrp_session->local_accept_wrapped_types); + } else { + char *uuid = switch_core_session_get_uuid(session); + const char *file_selector = switch_channel_get_variable(session->channel, "sip_msrp_local_file_selector"); + + if (zstr(msrp_session->local_path)) { + msrp_session->local_path = switch_core_session_sprintf(session, + "msrp%s://%s:%d/%s;tcp", + msrp_session->secure ? "s" : "", + ip, msrp_session->local_port, uuid); + } + + switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), + "m=message %d TCP/%sMSRP *\n" + "a=path:%s\n" + "a=accept-types:message/cpim text/* application/im-iscomposing+xml\n" + "a=accept-wrapped-types:*\n" + "a=setup:passive\n", + msrp_session->local_port, + msrp_session->secure ? "TLS/" : "", + msrp_session->local_path); + + if (!zstr(file_selector)) { + switch_snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + "a=sendonly\na=file-selector:%s\n", file_selector); + } + } + + goto no_rtt; + } else if (switch_channel_test_cap(session->channel, CC_RTP_RTT) && ( + // switch_channel_test_flag(session->channel, CF_TEXT_POSSIBLE) || + switch_channel_var_true(session->channel, "sip_enable_msrp") || + switch_channel_var_true(session->channel, "sip_enable_msrps"))) { + + smh->msrp_session = switch_msrp_session_new(switch_core_session_get_pool(session), switch_channel_var_true(session->channel, "sip_enable_msrps")); + + if (!smh->msrp_session) { + goto endmsrp; + } + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "MSRP session created\n"); + + smh->msrp_session->call_id = switch_core_session_get_uuid(session); + + switch_channel_set_flag(session->channel, CF_TEXT); + switch_channel_set_flag(session->channel, CF_TEXT_POSSIBLE); + switch_channel_set_flag(session->channel, CF_MSRP); + + switch_core_session_start_text_thread(session); + + goto msrp; + + endmsrp: ; + } + + // RTP TEXT if (sdp_type == SDP_TYPE_RESPONSE && !switch_channel_test_flag(session->channel, CF_TEXT_POSSIBLE)) { if (switch_channel_test_flag(session->channel, CF_TEXT_SDP_RECVD)) { @@ -9993,9 +10200,8 @@ SWITCH_DECLARE(void) switch_core_media_gen_local_sdp(switch_core_session_t *sess } } - - + no_rtt: if (map) { switch_event_destroy(&map); @@ -10767,7 +10973,7 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_reset_jb(switch_core_session_t SWITCH_DECLARE(switch_status_t) switch_core_media_receive_message(switch_core_session_t *session, switch_core_session_message_t *msg) { switch_media_handle_t *smh; - switch_rtp_engine_t *a_engine, *v_engine;//, *t_engine; + switch_rtp_engine_t *a_engine, *v_engine, *t_engine; switch_status_t status = SWITCH_STATUS_SUCCESS; switch_assert(session); @@ -10782,7 +10988,7 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_receive_message(switch_core_se a_engine = &smh->engines[SWITCH_MEDIA_TYPE_AUDIO]; v_engine = &smh->engines[SWITCH_MEDIA_TYPE_VIDEO]; - //t_engine = &smh->engines[SWITCH_MEDIA_TYPE_TEXT]; + t_engine = &smh->engines[SWITCH_MEDIA_TYPE_TEXT]; switch (msg->message_id) { @@ -10925,6 +11131,9 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_receive_message(switch_core_se if (direction && *direction == 'v') { direction++; rtp = v_engine->rtp_session; + } else if (direction && *direction == 't' && t_engine) { + direction++; + rtp = t_engine->rtp_session; } if (switch_rtp_ready(rtp) && !zstr(direction) && !zstr(msg->string_array_arg[1])) { @@ -13427,6 +13636,7 @@ SWITCH_DECLARE(switch_status_t) switch_core_session_write_text_frame(switch_core switch_io_event_hook_text_write_frame_t *ptr; switch_rtp_engine_t *t_engine; switch_io_write_text_frame_t write_text_frame = NULL; + int is_msrp = switch_channel_test_flag(session->channel, CF_MSRP); switch_assert(session); @@ -13456,7 +13666,7 @@ SWITCH_DECLARE(switch_status_t) switch_core_session_write_text_frame(switch_core t_engine = &smh->engines[SWITCH_MEDIA_TYPE_TEXT]; - if (switch_channel_test_cap(session->channel, CC_RTP_RTT)) { + if (!is_msrp && switch_channel_test_cap(session->channel, CC_RTP_RTT)) { if (frame) { char *str = (char *) frame->data; @@ -13522,7 +13732,7 @@ SWITCH_DECLARE(switch_status_t) switch_core_session_write_text_frame(switch_core } - if (switch_channel_test_cap(session->channel, CC_RTP_RTT)) { + if (!is_msrp && switch_channel_test_cap(session->channel, CC_RTP_RTT)) { if (t_engine->red_pt) { t_engine->tf->red_pos++; if (t_engine->tf->red_pos == t_engine->tf->red_max) { @@ -13538,7 +13748,7 @@ SWITCH_DECLARE(switch_status_t) switch_core_session_write_text_frame(switch_core } return status; - } +} SWITCH_DECLARE(switch_status_t) switch_core_session_printf(switch_core_session_t *session, const char *fmt, ...) { @@ -13615,6 +13825,12 @@ SWITCH_DECLARE(switch_status_t) switch_core_session_print(switch_core_session_t return SWITCH_STATUS_SUCCESS; } +SWITCH_DECLARE(switch_msrp_session_t *) switch_core_media_get_msrp_session(switch_core_session_t *session) +{ + if (!session->media_handle) return NULL; + + return session->media_handle->msrp_session; +} /* For Emacs: diff --git a/src/switch_msrp.c b/src/switch_msrp.c new file mode 100644 index 0000000000..8e2a968116 --- /dev/null +++ b/src/switch_msrp.c @@ -0,0 +1,1569 @@ +/* + * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * Copyright (C) 2011-2016, Seven Du + * + * Version: MPL 1.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * + * The Initial Developer of the Original Code is + * Seven Du + * Portions created by the Initial Developer are Copyright (C) + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * + * + * msrp.c -- MSRP lib + * + */ + +#include +#include + +#define MSRP_BUFF_SIZE SWITCH_RTP_MAX_BUF_LEN +#define DEBUG_MSRP 0 + +static struct { + int running; + int debug; + // switch_mutex_t *mutex; + char *ip; + int message_buffer_size; + + const SSL_METHOD *ssl_method; + SSL_CTX *ssl_ctx; + SSL *ssl; + int ssl_ready; + + msrp_socket_t msock; + msrp_socket_t msock_ssl; +} globals; + +typedef struct worker_helper{ + int debug; + switch_memory_pool_t *pool; + msrp_client_socket_t csock; +} worker_helper_t; + +static void msrp_deinit_ssl() +{ + if (globals.ssl_ctx) { + SSL_CTX_free(globals.ssl_ctx); + globals.ssl_ctx = NULL; + } +} + +static int msrp_init_ssl() +{ + const char *err = ""; + char *cert = "/usr/local/freeswitch/certs/wss.pem"; + char *key = cert; + + + SSL_library_init(); + + globals.ssl_method = SSLv23_server_method(); /* create server instance */ + globals.ssl_ctx = SSL_CTX_new(globals.ssl_method); /* create context */ + assert(globals.ssl_ctx); + globals.ssl_ready = 1; + + /* Disable SSLv2 */ + SSL_CTX_set_options(globals.ssl_ctx, SSL_OP_NO_SSLv2); + /* Disable SSLv3 */ + SSL_CTX_set_options(globals.ssl_ctx, SSL_OP_NO_SSLv3); + /* Disable TLSv1 */ + SSL_CTX_set_options(globals.ssl_ctx, SSL_OP_NO_TLSv1); + /* Disable Compression CRIME (Compression Ratio Info-leak Made Easy) */ + SSL_CTX_set_options(globals.ssl_ctx, SSL_OP_NO_COMPRESSION); + + // /* set the local certificate from CertFile */ + // if (!zstr(profile->chain)) { + // if (switch_file_exists(profile->chain, NULL) != SWITCH_STATUS_SUCCESS) { + // err = "SUPPLIED CHAIN FILE NOT FOUND\n"; + // goto fail; + // } + + // if (!SSL_CTX_use_certificate_chain_file(profile->ssl_ctx, profile->chain)) { + // err = "CERT CHAIN FILE ERROR"; + // goto fail; + // } + // } + + if (switch_file_exists(cert, NULL) != SWITCH_STATUS_SUCCESS) { + err = "SUPPLIED CERT FILE NOT FOUND\n"; + goto fail; + } + + if (!SSL_CTX_use_certificate_file(globals.ssl_ctx, cert, SSL_FILETYPE_PEM)) { + err = "CERT FILE ERROR"; + goto fail; + } + + /* set the private key from KeyFile */ + + if (switch_file_exists(key, NULL) != SWITCH_STATUS_SUCCESS) { + err = "SUPPLIED KEY FILE NOT FOUND\n"; + goto fail; + } + + if (!SSL_CTX_use_PrivateKey_file(globals.ssl_ctx, key, SSL_FILETYPE_PEM)) { + err = "PRIVATE KEY FILE ERROR"; + goto fail; + } + + /* verify private key */ + if ( !SSL_CTX_check_private_key(globals.ssl_ctx) ) { + err = "PRIVATE KEY FILE ERROR"; + goto fail; + } + + SSL_CTX_set_cipher_list(globals.ssl_ctx, "HIGH:!DSS:!aNULL@STRENGTH"); + + return 1; + + fail: + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "SSL ERR: %s\n", err); + + globals.ssl_ready = 0; + msrp_deinit_ssl(); + + return 0; +} + +SWITCH_DECLARE_GLOBAL_STRING_FUNC(set_global_ip, globals.ip); + +static switch_status_t load_config() +{ + char *cf = "msrp.conf"; + switch_xml_t cfg, xml = NULL, settings, param; + switch_status_t status = SWITCH_STATUS_SUCCESS; + + if (!(xml = switch_xml_open_cfg(cf, &cfg, NULL))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Open of %s failed\n", cf); + status = SWITCH_STATUS_FALSE; + return status; + } + + if ((settings = switch_xml_child(cfg, "settings"))) { + for (param = switch_xml_child(settings, "param"); param; param = param->next) { + char *var = (char *) switch_xml_attr_soft(param, "name"); + char *val = (char *) switch_xml_attr_soft(param, "value"); + if (!strcasecmp(var, "listen-ip")) { + set_global_ip(val); + } else if (!strcasecmp(var, "listen-port")) { + globals.msock.port = atoi(val); + } else if (!strcasecmp(var, "listen-ssl-port")) { + globals.msock_ssl.port = atoi(val); + } else if (!strcasecmp(var, "debug")) { + globals.debug = switch_true(val); + } else if (!strcasecmp(var, "message-buffer-size") && val) { + globals.message_buffer_size = atoi(val); + if (globals.message_buffer_size == 0) globals.message_buffer_size = 50; + } + } + } + + switch_xml_free(xml); + + return status; +} + +static void *SWITCH_THREAD_FUNC msrp_listener(switch_thread_t *thread, void *obj); + +static void close_socket(switch_socket_t ** sock) +{ + // switch_mutex_lock(globals.sock_mutex); + if (*sock) { + switch_socket_shutdown(*sock, SWITCH_SHUTDOWN_READWRITE); + switch_socket_close(*sock); + *sock = NULL; + } + // switch_mutex_unlock(globals.sock_mutex); +} + +static switch_status_t msock_init(char *ip, switch_port_t port, switch_socket_t **sock, switch_memory_pool_t *pool) +{ + switch_sockaddr_t *sa; + switch_status_t rv; + + rv = switch_sockaddr_info_get(&sa, ip, SWITCH_INET, port, 0, pool); + if (rv) goto sock_fail; + + rv = switch_socket_create(sock, switch_sockaddr_get_family(sa), SOCK_STREAM, SWITCH_PROTO_TCP, pool); + if (rv) goto sock_fail; + + rv = switch_socket_opt_set(*sock, SWITCH_SO_REUSEADDR, 1); + if (rv) goto sock_fail; + + rv = switch_socket_bind(*sock, sa); + if (rv) goto sock_fail; + + rv = switch_socket_listen(*sock, 5); + if (rv) goto sock_fail; + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "Socket up listening on %s:%u\n", ip, port); + + return SWITCH_STATUS_SUCCESS; + +sock_fail: + return rv; +} + +SWITCH_DECLARE(switch_status_t) switch_msrp_init() +{ + switch_memory_pool_t *pool; + switch_thread_t *thread; + switch_threadattr_t *thd_attr = NULL; + switch_status_t status; + + if (switch_core_new_memory_pool(&pool) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "OH OH no pool\n"); + return SWITCH_STATUS_FALSE; + } + + memset(&globals, 0, sizeof(globals)); + set_global_ip("0.0.0.0"); + globals.msock.port = (switch_port_t)MSRP_LISTEN_PORT; + globals.msock_ssl.port = (switch_port_t)MSRP_SSL_LISTEN_PORT; + globals.msock_ssl.secure = 1; + globals.message_buffer_size = 50; + globals.debug = DEBUG_MSRP; + + load_config(); + + globals.running = 1; + + status = msock_init(globals.ip, globals.msock.port, &globals.msock.sock, pool); + + if (status == SWITCH_STATUS_SUCCESS) { + switch_threadattr_create(&thd_attr, pool); + // switch_threadattr_detach_set(thd_attr, 1); + switch_threadattr_stacksize_set(thd_attr, SWITCH_THREAD_STACKSIZE); + switch_thread_create(&thread, thd_attr, msrp_listener, &globals.msock, pool); + globals.msock.thread = thread; + } + + msrp_init_ssl(); + status = msock_init(globals.ip, globals.msock_ssl.port, &globals.msock_ssl.sock, pool); + + if (status == SWITCH_STATUS_SUCCESS) { + switch_threadattr_create(&thd_attr, pool); + // switch_threadattr_detach_set(thd_attr, 1); + switch_threadattr_stacksize_set(thd_attr, SWITCH_THREAD_STACKSIZE); + switch_thread_create(&thread, thd_attr, msrp_listener, &globals.msock_ssl, pool); + globals.msock_ssl.thread = thread; + } + + return SWITCH_STATUS_SUCCESS; +} + +SWITCH_DECLARE(switch_status_t) switch_msrp_destroy() +{ + switch_status_t st = SWITCH_STATUS_SUCCESS; + switch_socket_t *sock; + globals.running = 0; + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "destroying thread\n"); + + sock = globals.msock.sock; + close_socket(&sock); + + sock = globals.msock_ssl.sock; + close_socket(&sock); + + if (globals.msock.thread) switch_thread_join(&st, globals.msock.thread); + if (globals.msock_ssl.thread) switch_thread_join(&st, globals.msock_ssl.thread); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "destroy thread done\n"); + + globals.msock.thread = NULL; + globals.msock_ssl.thread = NULL; + + msrp_deinit_ssl(); + + return st; +} + +SWITCH_DECLARE(switch_msrp_session_t *)switch_msrp_session_new(switch_memory_pool_t *pool, switch_bool_t secure) +{ + switch_msrp_session_t *ms; + ms = switch_core_alloc(pool, sizeof(switch_msrp_session_t)); + switch_assert(ms); + ms->pool = pool; + ms->secure = secure; + ms->local_port = secure ? globals.msock_ssl.port : globals.msock.port; + ms->msrp_msg_buffer_size = globals.message_buffer_size; + switch_mutex_init(&ms->mutex, SWITCH_MUTEX_NESTED, pool); + return ms; +} + +SWITCH_DECLARE(switch_status_t) switch_msrp_session_destroy(switch_msrp_session_t **ms) +{ + switch_mutex_destroy((*ms)->mutex); + ms = NULL; + return SWITCH_STATUS_SUCCESS; +} + +switch_status_t switch_msrp_session_push_msg(switch_msrp_session_t *ms, msrp_msg_t *msg) +{ + switch_mutex_lock(ms->mutex); + if (ms->last_msg == NULL) { + ms->last_msg = msg; + ms->msrp_msg = msg; + } else { + ms->last_msg->next = msg; + ms->last_msg = msg; + } + ms->msrp_msg_count++; + switch_mutex_unlock(ms->mutex); + return SWITCH_STATUS_SUCCESS; +} + +SWITCH_DECLARE(msrp_msg_t *)switch_msrp_session_pop_msg(switch_msrp_session_t *ms) +{ + msrp_msg_t *m = ms->msrp_msg; + if (m == NULL) { + switch_yield(20000); + return NULL; + } + + switch_mutex_lock(ms->mutex); + ms->msrp_msg = ms->msrp_msg->next; + ms->msrp_msg_count--; + if (ms->msrp_msg == NULL) ms->last_msg = NULL; + switch_mutex_unlock(ms->mutex); + return m; +} + +switch_status_t msrp_msg_serialize(msrp_msg_t *msrp_msg, char *buf) +{ + char *code_number_str = switch_mprintf("%d", msrp_msg->code_number); + char method[10]; + + switch(msrp_msg->method) { + case MSRP_METHOD_SEND: sprintf(method, "SEND"); break; + case MSRP_METHOD_AUTH: sprintf(method, "REPORT"); break; + case MSRP_METHOD_REPORT: sprintf(method, "REPORT"); break; + default: sprintf(method, "%d", msrp_msg->method); break; + } + sprintf(buf, "=================================\n" + "MSRP %s %s%s\nFrom: %s\nTo: %s\nMessage-ID: %s\n" + "Content-Type: %s\n" + "Byte-Range: %" SWITCH_SIZE_T_FMT "-%" SWITCH_SIZE_T_FMT"/%" SWITCH_SIZE_T_FMT "\n" + "Payload:\n%s\n%s\n" + "=================================\n", + msrp_msg->transaction_id ? switch_str_nil(msrp_msg->transaction_id) : code_number_str, + msrp_msg->transaction_id ? "" : " ", + msrp_msg->transaction_id ? method : switch_str_nil(msrp_msg->code_description), + switch_str_nil(msrp_msg->headers[MSRP_H_FROM_PATH]), + switch_str_nil(msrp_msg->headers[MSRP_H_TO_PATH]), + switch_str_nil(msrp_msg->headers[MSRP_H_MESSAGE_ID]), + switch_str_nil(msrp_msg->headers[MSRP_H_CONTENT_TYPE]), + msrp_msg->byte_start, + msrp_msg->byte_end, + msrp_msg->bytes, + msrp_msg->payload, + msrp_msg->delimiter); + switch_safe_free(code_number_str) + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t msrp_socket_recv(msrp_client_socket_t *csock, char *buf, switch_size_t *len) +{ + switch_status_t status = SWITCH_STATUS_FALSE; + + if (csock->secure) { + *len = SSL_read(globals.ssl, buf, *len); + if (*len) status = SWITCH_STATUS_SUCCESS; + } else { + status = switch_socket_recv(csock->sock, buf, len); + } + + return status; +} + +static switch_status_t msrp_socket_send(msrp_client_socket_t *csock, char *buf, switch_size_t *len) +{ + if (csock->secure) { + *len = SSL_write(globals.ssl, buf, *len); + return *len ? SWITCH_STATUS_SUCCESS : SWITCH_STATUS_FALSE; + } else { + return switch_socket_send(csock->sock, buf, len); + } +} + +void dump_buffer(char *buf, switch_size_t len, int line) +{ + int i, j, k = 0; + char buff[MSRP_BUFF_SIZE * 2]; + // return; + for(i=0,j=0; i= MSRP_BUFF_SIZE * 2) break; + } + + buff[j] = '\0'; + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "%d:%ldDUMP:%s:DUMP\n", line, len, buff); +} + +char *find_delim(char *buf, int len, char *delim) +{ + char *p, *q, *s = NULL; + char *end; + + p = buf; + q = delim; + + if (p == NULL) return NULL; + if (q == NULL) return NULL; + + end = buf + len - strlen(delim); + + while(p < end && *q) { + if (*p == *q) { + if (s == NULL) s = p; + p++; + q++; + } else { + s = NULL; + p++; + q = delim; + } + if (*q == '\0') return s; + } + return NULL; +} + +/* +MSRP d4c667b2351e958f SEND +To-Path: msrp://192.168.0.56:2856/73671a97c9dec690d303;tcp +From-Path: msrp://192.168.0.56:2855/2fb5dfec96f3609f7b48;tcp +Message-ID: 7b7c9965ffa8533c +Byte-Range: 1-0/0 +-------d4c667b2351e958f$ +*/ + +char *msrp_parse_header(char *start, int skip, const char *end, msrp_msg_t *msrp_msg, int index, switch_memory_pool_t *pool) +{ + char *p = start + skip; + char *q; + if (*p && *p == ' ') p++; + q = p; + while(*q != '\n' && q < end) q++; + if (q > p) { + if (*(q-1) == '\r') *(q-1) = '\0'; + *q = '\0'; + msrp_msg->headers[index] = switch_core_strdup(pool, p); + msrp_msg->last_header = msrp_msg->last_header > index ? msrp_msg->last_header : index; + return q + 1; + } + return start; +} + +msrp_msg_t *msrp_parse_headers(const char *start, int len, msrp_msg_t *msrp_msg, switch_memory_pool_t *pool) +{ + char *p = (char *)start; + char *q = p; + const char *end = start + len; + int headers = 0; + char line[1024]; + + + while(p < end) { + if (!strncasecmp(p, "MSRP ", 5)) { + p += 5; + q = p; + while(*q && q < end && *q != ' ') q++; + if (q > p) { + *q = '\0'; + msrp_msg->transaction_id = switch_core_strdup(pool, p); + switch_snprintf(line, 128, "-------%s", msrp_msg->transaction_id); + msrp_msg->delimiter = switch_core_strdup(pool, line); + msrp_msg->state = MSRP_ST_PARSE_HEADER; + } + p = q; + if (++p >= end) goto done; + + if (!strncasecmp(p, "SEND", 4)) { + msrp_msg->method = MSRP_METHOD_SEND; + p +=6; /*skip \r\n*/ + } else if (!strncasecmp(p, "REPORT", 6)) { + msrp_msg->method = MSRP_METHOD_REPORT; + p += 8; + } else if (!strncasecmp(p, "AUTH", 4)) { + msrp_msg->method = MSRP_METHOD_AUTH; + p += 6; + } else {/* MSRP transaction_id coden_number codede_scription */ + msrp_msg->method = MSRP_METHOD_REPLY; + q = p; + while(*q && q < end && *q != ' ') q++; + if (q > p) { + *q = '\0'; + msrp_msg->code_number = atoi(p); + p = ++q; + while(*q && q < end && *q != '\n') q++; + if (q > p) { + if (*(q-1) == '\r') *(q-1) = '\0'; + *q = '\0'; + msrp_msg->code_description = switch_core_strdup(pool, p); + p = ++q; + } + } + } + headers++; + } else if (!strncasecmp(p, "From-Path:", 10)) { + q = msrp_parse_header(p, 10, end, msrp_msg, MSRP_H_FROM_PATH, pool); + if (q == p) break; /* incomplete header*/ + p = q; + } else if (!strncasecmp(p, "To-Path:", 8)) { + q = msrp_parse_header(p, 8, end, msrp_msg, MSRP_H_TO_PATH, pool); + if (q == p) break; /* incomplete header*/ + p = q; + } else if (!strncasecmp(p, "Status:", 7)) { + q = msrp_parse_header(p, 7, end, msrp_msg, MSRP_H_STATUS, pool); + if (q == p) break; /* incomplete header*/ + p = q; + } else if (!strncasecmp(p, "Keep-Alive:", 11)) { + q = msrp_parse_header(p, 11, end, msrp_msg, MSRP_H_KEEPALIVE, pool); + if (q == p) break; /* incomplete header*/ + p = q; + } else if (!strncasecmp(p, "Message-ID:", 11)) { + q = msrp_parse_header(p, 11, end, msrp_msg, MSRP_H_MESSAGE_ID, pool); + if (q == p) break; /* incomplete header*/ + p = q; + } else if (!strncasecmp(p, "Content-Type:", 13)) { + q = msrp_parse_header(p, 13, end, msrp_msg, MSRP_H_CONTENT_TYPE, pool); + if (q == p) break; /* incomplete header*/ + p = q; + } else if (!strncasecmp(p, "Success-Report:", 15)) { + q = msrp_parse_header(p, 15, end, msrp_msg, MSRP_H_SUCCESS_REPORT, pool); + if (q == p) break; /* incomplete header*/ + p = q; + } else if (!strncasecmp(p, "Failure-Report:", 15)) { + q = msrp_parse_header(p, 15, end, msrp_msg, MSRP_H_FAILURE_REPORT, pool); + if (q == p) break; /* incomplete header*/ + p = q; + } else if (!strncasecmp(p, "Byte-Range:", 11)) { + p += 11; + if (*p && *p == ' ') p++; + q = p; + while(*q && q < end && *q != '-') q++; + if (q > p) { + *q = '\0'; + msrp_msg->byte_start = atoi(p); + switch_assert(msrp_msg->byte_start > 0); + p = ++q; + if (*p && *p == '*') { + msrp_msg->range_star = 1; + } + while(*q && q < end && *q != '/') q++; + if (q > p) { + *q = '\0'; + msrp_msg->byte_end = msrp_msg->range_star ? 0 : atoi(p); + p = ++q; + while(*q && q < end && *q != '\n') q++; + if (q > p) { + if (*(q-1) == '\r') *(q-1) = '\0'; + *q = '\0'; + msrp_msg->bytes = atoi(p); + + if (!msrp_msg->range_star) { + msrp_msg->payload_bytes = msrp_msg->byte_end + 1 - msrp_msg->byte_start; + } + + if (globals.debug) switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "%" SWITCH_SIZE_T_FMT " payload bytes\n", msrp_msg->payload_bytes); + + /*Fixme sanity check to avoid large byte-range attack*/ + if (!msrp_msg->range_star && msrp_msg->payload_bytes > msrp_msg->bytes) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "payload size does't match %" SWITCH_SIZE_T_FMT " != %" SWITCH_SIZE_T_FMT "\n", msrp_msg->payload_bytes, msrp_msg->bytes); + msrp_msg->state = MSRP_ST_ERROR; + p = ++q; + break; + } + p = ++q; + } + } + } + headers++; + } else if (*p == '\r' && *(p+1) == '\n') { + msrp_msg->state = MSRP_ST_WAIT_BODY; + p += 2; + break; + } else if (msrp_msg->delimiter && + !strncasecmp(p, msrp_msg->delimiter, strlen(msrp_msg->delimiter))) { + char *x = p + strlen(msrp_msg->delimiter); + if (x < end) { + if (*x == '$') { + p = x + 1; + msrp_msg->state = MSRP_ST_DONE; + if (*p == '\r') p++; + if (*p == '\n') p++; + break; + } else if(*x == '+') { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unsupported %c\n", *x); + if (*p == '\r') p++; + if (*p == '\n') p++; + break; + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unsupported %c\n", *x); + msrp_msg->state = MSRP_ST_ERROR; //TODO support # etc. + break; + } + } + break; + } else {/* unsupported header*/ + q = p; + while(*q && q < end && *q != ':') q++; + if (q > p) { + char *last_p = p; + *q = '\0'; + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "unsupported header [%s]\n", p); + p = q + 1; + msrp_msg->last_header = msrp_msg->last_header == 0 ? MSRP_H_UNKNOWN : msrp_msg->last_header + 1; + q = msrp_parse_header(p, 0, end, msrp_msg, msrp_msg->last_header, pool); + if (q == p) { + p = last_p; + break; /* incomplete header*/ + } + p = q; + } + } + } + +done: + msrp_msg->last_p = p; + return msrp_msg; +} + +msrp_msg_t *msrp_parse_buffer(char *buf, int len, msrp_msg_t *msrp_msg, switch_memory_pool_t *pool) +{ + const char *start; + + if (!msrp_msg) { + switch_zmalloc(msrp_msg, sizeof(msrp_msg_t)); + switch_assert(msrp_msg); + msrp_msg->state = MSRP_ST_WAIT_HEADER; + } + + if (globals.debug) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "parse state: %d\n", msrp_msg->state); + dump_buffer(buf, len, __LINE__); + } + + if (msrp_msg->state == MSRP_ST_WAIT_HEADER) { + if ((start = switch_stristr("MSRP ", buf)) == NULL) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Not an MSRP packet, Skip!\n"); + return msrp_msg; + } + msrp_msg = msrp_parse_headers(start, len - (start - buf), msrp_msg, pool); + + if (msrp_msg->state == MSRP_ST_ERROR) return msrp_msg; + if (msrp_msg->state == MSRP_ST_DONE) return msrp_msg; + + if (msrp_msg->last_p && msrp_msg->last_p < buf + len) { + msrp_msg = msrp_parse_buffer(msrp_msg->last_p, len - (msrp_msg->last_p - buf), msrp_msg, pool); + } + } else if (msrp_msg->state == MSRP_ST_WAIT_BODY) { + if(!msrp_msg->range_star && msrp_msg->byte_end == 0) { + msrp_msg->state = MSRP_ST_DONE; + return msrp_msg; + } + if (msrp_msg->range_star) { /* the * case */ + /*hope we can find the delimiter at the end*/ + int dlen; + char *delim_pos = NULL; + switch_assert(msrp_msg->delimiter); + dlen = strlen(msrp_msg->delimiter); + if (!strncmp(buf + len - dlen - 3, msrp_msg->delimiter, dlen)) { /*bingo*/ + msrp_msg->payload_bytes = len - dlen - 5; + msrp_msg->payload = switch_core_alloc(pool, msrp_msg->payload_bytes + 1); + switch_assert(msrp_msg->payload); + memcpy(msrp_msg->payload, buf, msrp_msg->payload_bytes); + msrp_msg->byte_end = msrp_msg->byte_start + msrp_msg->payload_bytes - 1; + msrp_msg->state = MSRP_ST_DONE; + msrp_msg->last_p = buf + len; + return msrp_msg; + } else if ((delim_pos = find_delim(buf, len, msrp_msg->delimiter))){ + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "=======================================delimiter: %s\n", delim_pos); + msrp_msg->payload_bytes = delim_pos - buf - 2; + // if (msrp_msg->payload_bytes < 0) msrp_msg->payload_bytes = 0; + msrp_msg->payload = switch_core_alloc(pool, msrp_msg->payload_bytes + 1); + switch_assert(msrp_msg->payload); + memcpy(msrp_msg->payload, buf, msrp_msg->payload_bytes); + msrp_msg->byte_end = msrp_msg->byte_start + msrp_msg->payload_bytes - 1; + msrp_msg->state = MSRP_ST_DONE; + msrp_msg->last_p = delim_pos + dlen + 3; + return msrp_msg; + } else {/* keep waiting*/ + /*TODO: fix potential overflow here*/ + msrp_msg->last_p = buf; + return msrp_msg; + } + } else if (msrp_msg->payload_bytes == 0) { + int dlen = strlen(msrp_msg->delimiter); + if(strncasecmp(buf, msrp_msg->delimiter, dlen)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error find delimiter\n"); + msrp_msg->state = MSRP_ST_ERROR; + return msrp_msg; + } + msrp_msg->payload = NULL; + msrp_msg->state = MSRP_ST_DONE; + msrp_msg->last_p = buf + dlen + 3; /*Fixme: assuming end with $\r\n*/ + return msrp_msg; + } else { + int dlen = strlen(msrp_msg->delimiter); + + if (msrp_msg->payload_bytes > len) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Waiting payload...%d < %d\n", (int)msrp_msg->payload_bytes, (int)len); + return msrp_msg; /*keep waiting ?*/ + } + + msrp_msg->payload = switch_core_alloc(pool, msrp_msg->payload_bytes); + switch_assert(msrp_msg->payload); + memcpy(msrp_msg->payload, buf, msrp_msg->payload_bytes); + msrp_msg->last_p = buf + msrp_msg->payload_bytes; + msrp_msg->state = MSRP_ST_DONE; + msrp_msg->last_p = buf + msrp_msg->payload_bytes; + if (msrp_msg->payload_bytes == len - dlen - 5) { + msrp_msg->last_p = buf + len; + + if (globals.debug) switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "payload bytes:%d\n", (int)msrp_msg->payload_bytes); + + return msrp_msg; /*Fixme: assuming \r\ndelimiter$\r\n present*/ + } + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "%ld %d %d\n", msrp_msg->payload_bytes, len, dlen); + + msrp_msg->state = MSRP_ST_ERROR; + return msrp_msg; + } + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "error here! code:%d\n", msrp_msg->state); + } + return msrp_msg; +} + + +switch_status_t msrp_reply(msrp_client_socket_t *csock, msrp_msg_t *msrp_msg) +{ + char buf[2048]; + switch_size_t len; + sprintf(buf, "MSRP %s 200 OK\r\nTo-Path: %s\r\nFrom-Path: %s\r\n" + "%s$\r\n", + switch_str_nil(msrp_msg->transaction_id), + switch_str_nil(msrp_msg->headers[MSRP_H_FROM_PATH]), + switch_str_nil(msrp_msg->headers[MSRP_H_TO_PATH]), + msrp_msg->delimiter); + len = strlen(buf); + + return msrp_socket_send(csock, buf, &len); +} + +switch_status_t msrp_report(msrp_client_socket_t *csock, msrp_msg_t *msrp_msg, char *status_code) +{ + char buf[2048]; + switch_size_t len; + sprintf(buf, "MSRP %s REPORT\r\nTo-Path: %s\r\nFrom-Path: %s\r\nMessage-ID: %s\r\n" + "Status: 000 %s\r\nByte-Range: 1-%" SWITCH_SIZE_T_FMT "/%" SWITCH_SIZE_T_FMT + "\r\n%s$\r\n", + switch_str_nil(msrp_msg->transaction_id), + switch_str_nil(msrp_msg->headers[MSRP_H_FROM_PATH]), + switch_str_nil(msrp_msg->headers[MSRP_H_TO_PATH]), + switch_str_nil(msrp_msg->headers[MSRP_H_MESSAGE_ID]), + switch_str_nil(status_code), + msrp_msg->byte_end, + msrp_msg->bytes, + msrp_msg->delimiter); + len = strlen(buf); + if (globals.debug) switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "report: %" SWITCH_SIZE_T_FMT " bytes\n%s", len, buf); + return msrp_socket_send(csock, buf, &len); +} + +static switch_bool_t msrp_find_uuid(char *uuid, char *to_path) +{ + int len = strlen(to_path); + int i; + int slash_count = 0; + switch_assert(to_path); + for(i=0; icsock; + switch_memory_pool_t *pool = helper->pool; + char buf[MSRP_BUFF_SIZE]; + char *p; + char *last_p; + switch_size_t len = MSRP_BUFF_SIZE; + switch_status_t status; + msrp_msg_t *msrp_msg = NULL; + char uuid[128] = { 0 }; + switch_core_session_t *session = NULL; + switch_msrp_session_t *msrp_session = NULL; + switch_channel_t *channel = NULL; + int sanity = 10; + SSL *ssl = NULL; + + switch_socket_opt_set(csock->sock, SWITCH_SO_TCP_NODELAY, TRUE); + // switch_socket_opt_set(csock->sock, SWITCH_SO_NONBLOCK, TRUE); + + if (csock->secure) { // tls? + int secure_established = 0; + int sanity = 10; + switch_os_socket_t sockdes = -1; + + switch_os_sock_get(&sockdes, csock->sock); + // switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "socket: %d\n", sockdes); + switch_assert(sockdes > -1); + + ssl = SSL_new(globals.ssl_ctx); + assert(ssl); + globals.ssl = ssl; + + SSL_set_fd(ssl, sockdes); + + do { + int code = SSL_accept(ssl); + + if (code == 1) { + secure_established = 1; + goto done; + } + + if (code == 0) { + goto err; + } + + if (code < 0) { + if (code == -1 && SSL_get_error(ssl, code) != SSL_ERROR_WANT_READ) { + goto err; + } + } + } while(sanity--); + + err: + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "SSL ERR\n"); + goto end; + + done: + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "SSL established = %d\n", secure_established); + } + + len = MSRP_BUFF_SIZE; + status = msrp_socket_recv(csock, buf, &len); + + if (helper->debug) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "status:%d, len:%" SWITCH_SIZE_T_FMT "\n", status, len); + } + + if (status == SWITCH_STATUS_SUCCESS) { + msrp_msg = msrp_parse_buffer(buf, len, NULL, pool); + switch_assert(msrp_msg); + } else { + goto end; + } + + if (helper->debug) { + msrp_msg_serialize(msrp_msg, buf); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "%s\n", buf); + } + + if (msrp_msg->state == MSRP_ST_DONE && msrp_msg->method == MSRP_METHOD_SEND) { + msrp_reply(csock, msrp_msg); + if (msrp_msg->headers[MSRP_H_SUCCESS_REPORT] && + !strcmp(msrp_msg->headers[MSRP_H_SUCCESS_REPORT], "yes")) { + msrp_report(csock, msrp_msg, "200 OK"); + } + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Parse initial message error!\n"); + goto end; + } + + if (msrp_find_uuid(uuid, msrp_msg->headers[MSRP_H_TO_PATH]) != SWITCH_TRUE) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Invalid MSRP to-path!\n"); + goto end; + } + + while (sanity-- && !(session = switch_core_session_locate(uuid))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "waiting for session\n"); + switch_yield(1000000); + } + + if(!session) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "No such session %s\n", uuid); + goto end; + } + + channel = switch_core_session_get_channel(session); + msrp_session = switch_core_media_get_msrp_session(session); + switch_assert(msrp_session); + msrp_session->csock = csock; + + len = MSRP_BUFF_SIZE; + p = buf; + last_p = buf; + switch_safe_free(msrp_msg); + msrp_msg = NULL; + + while(msrp_socket_recv(csock, p, &len) == SWITCH_STATUS_SUCCESS) { + // switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "read bytes:%ld\n", len); + + if (helper->debug) dump_buffer(buf, (p - buf) + len, __LINE__); + + msrp_msg = msrp_parse_buffer(last_p, p - last_p + len, msrp_msg, pool); + + switch_assert(msrp_msg); + + if (msrp_msg->state == MSRP_ST_ERROR) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "msrp parse error!\n"); + goto end; + } + + if (helper->debug) { + // char msg_buf[MSRP_BUFF_SIZE * 2]; + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "state:%d, len:%" SWITCH_SIZE_T_FMT " payload_bytes:%" SWITCH_SIZE_T_FMT "\n", msrp_msg->state, len, msrp_msg->payload_bytes); + // { + // char bbb[MSRP_BUFF_SIZE * 2]; + // msrp_msg_serialize(msrp_msg_tmp, bbb), + // + } + + if (msrp_msg->state == MSRP_ST_DONE && msrp_msg->method == MSRP_METHOD_SEND) { + msrp_reply(csock, msrp_msg); + if (msrp_msg->headers[MSRP_H_SUCCESS_REPORT] && + !strcmp(msrp_msg->headers[MSRP_H_SUCCESS_REPORT], "yes")) { + msrp_report(csock, msrp_msg, "200 OK"); + } + last_p = msrp_msg->last_p; + switch_msrp_session_push_msg(msrp_session, msrp_msg); + msrp_msg = NULL; + } else if (msrp_msg->state == MSRP_ST_DONE) { /* throw away */ + last_p = msrp_msg->last_p; + switch_safe_free(msrp_msg); + msrp_msg = NULL; + } else { + last_p = msrp_msg->last_p; + } + + while (msrp_session && msrp_session->msrp_msg_count > msrp_session->msrp_msg_buffer_size) { + if (!switch_channel_ready(channel)) break; + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "%s reading too fast, relax...\n", uuid); + switch_yield(100000); + } + + if (p + len > last_p) { + p += len; + if (!msrp_msg) { + int rest_len = p - last_p; + + memmove(buf, last_p, rest_len); + p = buf + rest_len; + len = MSRP_BUFF_SIZE - rest_len; + last_p = buf; + continue; + } + + if (p >= buf + MSRP_BUFF_SIZE) { + + if (msrp_msg->state != MSRP_ST_WAIT_BODY || !msrp_msg->range_star) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "buffer overflow\n"); + /*todo, do a strstr in the whole buffer ?*/ + break; + } + + /* buffer full*/ + msrp_msg->payload_bytes = p - last_p; + msrp_msg->byte_end = msrp_msg->byte_start + msrp_msg->payload_bytes - 1; + msrp_msg->payload = switch_core_alloc(pool, msrp_msg->payload_bytes); + switch_assert(msrp_msg->payload); + memcpy(msrp_msg->payload, last_p, msrp_msg->payload_bytes); + + { + int i; + msrp_msg_t *msrp_msg_old = msrp_msg; + msrp_msg = NULL; + /*dup msrp_msg*/ + switch_zmalloc(msrp_msg, sizeof(msrp_msg_t)); + switch_assert(msrp_msg); + msrp_msg->state = msrp_msg_old->state; + msrp_msg->byte_start = msrp_msg_old->byte_end + 1; + msrp_msg->bytes = msrp_msg_old->bytes; + msrp_msg->range_star = msrp_msg_old->range_star; + msrp_msg->method = msrp_msg_old->method; + msrp_msg->transaction_id = switch_core_strdup(pool, msrp_msg_old->transaction_id); + msrp_msg->delimiter = switch_core_strdup(pool, msrp_msg_old->delimiter); + msrp_msg->last_header = msrp_msg_old->last_header; + for (i = 0; i < msrp_msg->last_header; i++) { + msrp_msg->headers[i] = switch_core_strdup(pool, msrp_msg_old->headers[i]); + } + + msrp_msg_old->state = MSRP_ST_DONE; + + if (msrp_msg_old->headers[MSRP_H_SUCCESS_REPORT] && + !strcmp(msrp_msg_old->headers[MSRP_H_SUCCESS_REPORT], "yes")) { + // msrp_report(csock, msrp_msg_old, "200 OK"); + } + + switch_msrp_session_push_msg(msrp_session, msrp_msg_old); + } + + p = buf; + len = MSRP_BUFF_SIZE; + last_p = buf; + msrp_msg->last_p = buf; + } + } else { /* all buffer parsed */ + p = buf; + len = MSRP_BUFF_SIZE; + last_p = buf; + } + if (!switch_channel_ready(channel)) break; + } + +end: + if (session) switch_core_session_rwunlock(session); + + switch_socket_close(csock->sock); + switch_core_destroy_memory_pool(&pool); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "msrp worker %s down\n", uuid); + + return NULL; +} + +static void *SWITCH_THREAD_FUNC msrp_listener(switch_thread_t *thread, void *obj) +{ + msrp_socket_t *msock = (msrp_socket_t *)obj; + switch_status_t rv; + switch_memory_pool_t *pool = NULL; + switch_threadattr_t *thd_attr = NULL; + switch_socket_t *sock = NULL; + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "MSRP listener start%s\n", msock->secure ? " ssl" : ""); + + if (switch_core_new_memory_pool(&pool) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "OH OH no pool\n"); + return NULL; + } + + switch_socket_opt_set(msock->sock, SWITCH_SO_TCP_NODELAY, TRUE); + // switch_socket_opt_set(msock->sock, SWITCH_SO_NONBLOCK, TRUE); + + while (globals.running && (rv = switch_socket_accept(&sock, msock->sock, pool)) == SWITCH_STATUS_SUCCESS) { + switch_memory_pool_t *worker_pool; + worker_helper_t *helper; + + if (globals.debug > 0) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Connection Open%s\n", msock->secure ? " SSL" : ""); + } + + if (switch_core_new_memory_pool(&worker_pool) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "OH OH no pool\n"); + return NULL; + } + + switch_zmalloc(helper, sizeof(worker_helper_t)); + + switch_assert(helper != NULL); + helper->pool = worker_pool; + helper->debug = globals.debug; + helper->csock.sock = sock; + helper->csock.secure = msock->secure; + + switch_threadattr_create(&thd_attr, pool); + switch_threadattr_detach_set(thd_attr, 1); + switch_threadattr_stacksize_set(thd_attr, SWITCH_THREAD_STACKSIZE); + switch_thread_create(&thread, thd_attr, msrp_worker, helper, worker_pool); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "msrp worker new thread spawned!\n"); + } + + if (pool) switch_core_destroy_memory_pool(&pool); + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "MSRP listener down\n"); + + return NULL; +} + +void random_string(char *buf, switch_size_t size) +{ + long val[4]; + int x; + + for (x = 0; x < 4; x++) + val[x] = random(); + snprintf(buf, size, "%08lx%08lx%08lx%08lx", val[0], val[1], val[2], val[3]); + *(buf+size) = '\0'; +} + +SWITCH_DECLARE(switch_status_t) switch_msrp_send(switch_msrp_session_t *ms, msrp_msg_t *msrp_msg) +{ + char transaction_id[32]; + char buf[MSRP_BUFF_SIZE]; + switch_size_t len; + char *to_path = msrp_msg->headers[MSRP_H_TO_PATH] ? msrp_msg->headers[MSRP_H_TO_PATH] : ms->remote_path; + char *from_path = msrp_msg->headers[MSRP_H_FROM_PATH] ? msrp_msg->headers[MSRP_H_FROM_PATH] : ms->local_path; + + if (!from_path) return SWITCH_STATUS_SUCCESS; + + random_string(transaction_id, 16); + + sprintf(buf, "MSRP %s SEND\r\nTo-Path: %s\r\nFrom-Path: %s\r\n" + "Content-Type: %s\r\n" + "Byte-Range: 1-%" SWITCH_SIZE_T_FMT "/%" SWITCH_SIZE_T_FMT "%s", + transaction_id, + to_path, + from_path, + switch_str_nil(msrp_msg->headers[MSRP_H_CONTENT_TYPE]), + msrp_msg->payload ? msrp_msg->payload_bytes : 0, + msrp_msg->payload ? msrp_msg->payload_bytes : 0, + msrp_msg->payload ? "\r\n\r\n" : ""); + + len = strlen(buf); + + if (msrp_msg->payload) { + if (len + msrp_msg->payload_bytes >= MSRP_BUFF_SIZE) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "payload too large! %" SWITCH_SIZE_T_FMT "\n", len + msrp_msg->payload_bytes); + return SWITCH_STATUS_FALSE; + } + memcpy(buf + len, msrp_msg->payload, msrp_msg->payload_bytes); + len += msrp_msg->payload_bytes; + } + sprintf(buf + len, "\r\n-------%s$\r\n", transaction_id); + len += (12 + strlen(transaction_id)); + if (globals.debug) switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "---------------------send: %" SWITCH_SIZE_T_FMT " bytes\n%s\n", len, buf); + + return ms->csock ? msrp_socket_send(ms->csock, buf, &len) : SWITCH_STATUS_FALSE; +} + +#if 0 +SWITCH_STANDARD_APP(msrp_echo_function) +{ + msrp_session_t *msrp_session = NULL; + msrp_msg_t *msrp_msg = NULL; + switch_channel_t *channel = switch_core_session_get_channel(session); + // private_object_t *tech_pvt = switch_core_session_get_private(session); + + if (!tech_pvt) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "No tech_pvt!\n"); + return; + } + + if(!tech_pvt->msrp_session) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "No msrp_session!\n"); + return; + } + + while (switch_channel_ready(channel) && (msrp_session = tech_pvt->msrp_session)) { + if ((msrp_msg = msrp_session_pop_msg(msrp_session)) == NULL) { + switch_yield(100000); + continue; + } + + if (msrp_msg->method == MSRP_METHOD_SEND) { /*echo back*/ + char *p; + p = msrp_msg->headers[MSRP_H_TO_PATH]; + msrp_msg->headers[MSRP_H_TO_PATH] = msrp_msg->headers[MSRP_H_FROM_PATH]; + msrp_msg->headers[MSRP_H_FROM_PATH] = p; + msrp_send(msrp_session->socket, msrp_msg); + } + + switch_safe_free(msrp_msg); + msrp_msg = NULL; + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "eat one message, left:%d\n", (int)msrp_session->msrp_msg_count); + } + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Echo down!\n"); + +} + +SWITCH_STANDARD_APP(msrp_recv_function) +{ + msrp_session_t *msrp_session = NULL; + msrp_msg_t *msrp_msg = NULL; + switch_channel_t *channel = switch_core_session_get_channel(session); + private_object_t *tech_pvt = switch_core_session_get_private(session); + switch_memory_pool_t *pool = switch_core_session_get_pool(session); + switch_file_t *fd; + const char *filename = data; + + if (!tech_pvt) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "No tech_pvt!\n"); + return; + } + + if(!tech_pvt->msrp_session) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "No msrp_session!\n"); + return; + } + + if (zstr(data)) { + filename = switch_channel_get_variable(channel, "sip_msrp_file_name"); + if (zstr(filename)) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "No file specified.\n"); + return; + } + filename = switch_core_session_sprintf(session, "%s%s%s", SWITCH_GLOBAL_dirs.base_dir, SWITCH_PATH_SEPARATOR, filename); + } + + if (!(msrp_session = tech_pvt->msrp_session)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Not a msrp session!\n"); + return; + } + + if (switch_file_open(&fd, filename, SWITCH_FOPEN_WRITE | SWITCH_FOPEN_TRUNCATE | SWITCH_FOPEN_CREATE, SWITCH_FPROT_OS_DEFAULT, pool) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error Open File %s\n", filename); + return; + } + + while (1) { + if ((msrp_msg = msrp_session_pop_msg(msrp_session)) == NULL) { + if (!switch_channel_ready(channel)) break; + switch_yield(10000); + continue; + } + + if (msrp_msg->method == MSRP_METHOD_SEND) { + switch_size_t bytes = msrp_msg->payload_bytes; + char *msg = switch_str_nil(msrp_msg->headers[MSRP_H_MESSAGE_ID]); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "%s %" SWITCH_SIZE_T_FMT "bytes writing\n", msg, bytes); + switch_file_write(fd, msrp_msg->payload, &bytes); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "%" SWITCH_SIZE_T_FMT "bytes written\n", bytes); + if (bytes != msrp_msg->payload_bytes) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "write fail, bytes lost!\n"); + } + } + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "eat one message, left:%d\n", (int)msrp_session->msrp_msg_count); + + switch_safe_free(msrp_msg); + } + + switch_file_close(fd); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "File closed!\n"); +} + +SWITCH_STANDARD_APP(msrp_send_function) +{ + msrp_session_t *msrp_session = NULL; + msrp_msg_t *msrp_msg = NULL; + switch_channel_t *channel = switch_core_session_get_channel(session); + private_object_t *tech_pvt = switch_core_session_get_private(session); + switch_memory_pool_t *pool = switch_core_session_get_pool(session); + switch_file_t *fd; + const char *filename = data; + switch_size_t len = 2048; + char buf[2048]; + int sanity = 10; + + if (!tech_pvt) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "No tech_pvt!\n"); + return; + } + + if(!tech_pvt->msrp_session) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "No msrp_session!\n"); + return; + } + + if (!(msrp_session = tech_pvt->msrp_session)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Not a msrp session!\n"); + return; + } + + if (switch_file_open(&fd, filename, SWITCH_FOPEN_READ, SWITCH_FPROT_OS_DEFAULT, pool) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error Open File %s\n", filename); + return; + } + + switch_assert(pool); + + switch_zmalloc(msrp_msg, sizeof(msrp_msg_t)); + switch_assert(msrp_msg); + + msrp_msg->headers[MSRP_H_FROM_PATH] = switch_mprintf("msrp://%s:%d/%s;tcp", + tech_pvt->rtpip, tech_pvt->msrp_session->local_port, tech_pvt->msrp_session->call_id); + msrp_msg->headers[MSRP_H_TO_PATH] = tech_pvt->msrp_session->remote_path; + /*TODO: send file in octet or maybe guess mime?*/ + msrp_msg->headers[MSRP_H_CONTENT_TYPE] = "application/octet-stream"; + msrp_msg->headers[MSRP_H_CONTENT_TYPE] = "text/plain"; + + msrp_msg->bytes = switch_file_get_size(fd); + msrp_msg->byte_start = 1; + + while(sanity-- && tech_pvt->msrp_session->socket == NULL) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Waiting socket\n"); + switch_yield(1000000); + } + + if (tech_pvt->msrp_session->socket == NULL) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Waiting for socket timedout, exiting...\n"); + goto end; + } + + while (switch_file_read(fd, buf, &len) == SWITCH_STATUS_SUCCESS && + switch_channel_ready(channel)) { + msrp_msg->payload = buf; + msrp_msg->byte_end = msrp_msg->byte_start + len - 1; + msrp_msg->payload_bytes = len; + + /*TODO: send in chunk should ending in + but not $ after delimiter*/ + msrp_send(tech_pvt->msrp_session->socket, msrp_msg); + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "%ld bytes sent\n", len); + + msrp_msg->byte_start += len; + } + + sanity = 10; + + while(sanity-- && switch_channel_ready(channel)) { + switch_yield(1000000); + } + +end: + switch_file_close(fd); + + switch_safe_free(msrp_msg->headers[MSRP_H_FROM_PATH]); + switch_safe_free(msrp_msg); + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "File sent, closed!\n"); +} + +SWITCH_STANDARD_APP(msrp_bridge_function) +{ + switch_channel_t *caller_channel = switch_core_session_get_channel(session); + switch_core_session_t *peer_session = NULL; + switch_channel_t *peer_channel = NULL; + msrp_session_t *caller_msrp_session = NULL; + msrp_session_t *peer_msrp_session = NULL; + private_object_t *tech_pvt = NULL; + private_object_t *ptech_pvt = NULL; + msrp_msg_t *msrp_msg = NULL; + switch_call_cause_t cause = SWITCH_CAUSE_NORMAL_CLEARING; + switch_status_t status; + + if (zstr(data)) { + return; + } + + if ((status = + switch_ivr_originate(session, &peer_session, &cause, data, 0, NULL, NULL, NULL, NULL, NULL, SOF_NONE, NULL)) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Originate Failed. Cause: %s\n", switch_channel_cause2str(cause)); + return; + } + + switch_ivr_signal_bridge(session, peer_session); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "msrp channel bridged\n"); + + peer_channel = switch_core_session_get_channel(session); + tech_pvt = switch_core_session_get_private(session); + ptech_pvt = switch_core_session_get_private(peer_session); + + caller_msrp_session = tech_pvt->msrp_session; + peer_msrp_session = ptech_pvt->msrp_session; + switch_assert(caller_msrp_session); + switch_assert(peer_msrp_session); + + if (switch_channel_test_flag(peer_channel, CF_ANSWERED) && !switch_channel_test_flag(caller_channel, CF_ANSWERED)) { + switch_channel_pass_callee_id(peer_channel, caller_channel); + switch_channel_answer(caller_channel); + } + + // TODO we need to run the following code in a new thread + // TODO we cannot test channel_ready as we don't have (audio) media + // while (switch_channel_ready(caller_channel) && switch_channel_ready(peer_channel)){ + while (switch_channel_get_state(caller_channel) == CS_HIBERNATE && + switch_channel_get_state(peer_channel) == CS_HIBERNATE){ + int found = 0; + if ((msrp_msg = msrp_session_pop_msg(caller_msrp_session))) { + if (msrp_msg->method == MSRP_METHOD_SEND) { /* write to peer */ + msrp_msg->headers[MSRP_H_FROM_PATH] = switch_mprintf("msrp://%s:%d/%s;tcp", + ptech_pvt->rtpip, peer_msrp_session->local_port, peer_msrp_session->call_id); + msrp_msg->headers[MSRP_H_TO_PATH] = peer_msrp_session->remote_path; + + if (peer_msrp_session->socket) { + msrp_send(peer_msrp_session->socket, msrp_msg); + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "socket not ready, discarding one message!!\n"); + } + } + switch_safe_free(msrp_msg); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "eat one message, left:%d\n", (int)caller_msrp_session->msrp_msg_count); + found++; + } + + if ((msrp_msg = msrp_session_pop_msg(peer_msrp_session))) { + if (msrp_msg->method == MSRP_METHOD_SEND) { /* write to caller */ + msrp_msg->headers[MSRP_H_FROM_PATH] = switch_mprintf("msrp://%s:%d/%s;tcp", + tech_pvt->rtpip, caller_msrp_session->local_port, caller_msrp_session->call_id); + msrp_msg->headers[MSRP_H_TO_PATH] = caller_msrp_session->remote_path; + msrp_send(caller_msrp_session->socket, msrp_msg); + } + switch_safe_free(msrp_msg); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "eat one message, left:%d\n", (int)peer_msrp_session->msrp_msg_count); + found++; + } + + msrp_msg = NULL; + if (!found) switch_yield(100000); + } + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Bridge down!\n"); + + if (peer_session) { + switch_core_session_rwunlock(peer_session); + } +} + +SWITCH_STANDARD_API(uuid_msrp_send_function) +{ + char *mycmd = NULL, *argv[3] = { 0 }; + int argc; + switch_core_session_t *msession = NULL; + // msrp_session_t *msrp_session = NULL; + msrp_msg_t *msrp_msg = NULL; + private_object_t *tech_pvt = NULL; + switch_memory_pool_t *pool = NULL; + + if (zstr(cmd) || !(mycmd = strdup(cmd))) { + goto error; + } + + argc = switch_separate_string(mycmd, ' ', argv, (sizeof(argv) / sizeof(argv[0]))); + + if (argc < 2 || !argv[0]) { + goto error; + } + + if (!(msession = switch_core_session_locate(argv[0]))) { + stream->write_function(stream, "-ERR Usage: cannot locate session.\n"); + return SWITCH_STATUS_SUCCESS; + } + + tech_pvt = switch_core_session_get_private(msession); + pool = switch_core_session_get_pool(msession); + switch_assert(pool); + + if (!tech_pvt) { + stream->write_function(stream, "-ERR No tech_pvt.\n"); + switch_core_session_rwunlock(msession); + return SWITCH_STATUS_SUCCESS; + } + + if(!tech_pvt->msrp_session) { + stream->write_function(stream, "-ERR No msrp_session.\n"); + switch_core_session_rwunlock(msession); + return SWITCH_STATUS_SUCCESS; + } + + switch_zmalloc(msrp_msg, sizeof(msrp_msg_t)); + switch_assert(msrp_msg); + + msrp_msg->headers[MSRP_H_FROM_PATH] = switch_mprintf("msrp://%s:%d/%s;tcp", + tech_pvt->rtpip, tech_pvt->msrp_session->local_port, tech_pvt->msrp_session->call_id); + msrp_msg->headers[MSRP_H_TO_PATH] = tech_pvt->msrp_session->remote_path; + msrp_msg->headers[MSRP_H_CONTENT_TYPE] = "text/plain"; + msrp_msg->payload = switch_core_strdup(pool, argv[1]); + + msrp_send(tech_pvt->msrp_session->socket, msrp_msg); + + switch_safe_free(msrp_msg->headers[MSRP_H_FROM_PATH]); + switch_safe_free(msrp_msg); + stream->write_function(stream, "+OK sent\n"); + switch_core_session_rwunlock(msession); + return SWITCH_STATUS_SUCCESS; +error: + stream->write_function(stream, "-ERR Usage: uuid_msrp_send msg\n"); + return SWITCH_STATUS_SUCCESS; +} + +#endif + + +#define MSRP_SYNTAX "debug |restart" +SWITCH_STANDARD_API(msrp_api_function) +{ + if (zstr(cmd)) { + stream->write_function(stream, "-ERR usage: " MSRP_SYNTAX "\n"); + return SWITCH_STATUS_SUCCESS; + } + + if (!strcmp(cmd, "debug on")) { + globals.debug = 1; + stream->write_function(stream, "+OK debug on\n"); + } else if(!strcmp(cmd, "debug off")) { + globals.debug = 0; + stream->write_function(stream, "+OK debug off\n"); + } else if(!strcmp(cmd, "restart")) { + switch_msrp_destroy(); + switch_msrp_init(); + } + + return SWITCH_STATUS_SUCCESS; +} + +SWITCH_DECLARE(void) switch_msrp_load_apis_and_applications(switch_loadable_module_interface_t **module_interface) +{ + // switch_application_interface_t *app_interface; + switch_api_interface_t *api_interface; + + SWITCH_ADD_API(api_interface, "msrp", "MSRP Functions", msrp_api_function, "JSON"); + +#if 0 + SWITCH_ADD_API(api_interface, "uuid_msrp_send", "send msrp text", uuid_msrp_send_function, " "); + SWITCH_ADD_APP(app_interface, "msrp_echo", "Echo msrp message", "Perform an echo test against the msrp channel", msrp_echo_function, "", SAF_NONE); + SWITCH_ADD_APP(app_interface, "msrp_recv", "Recv msrp message to file", "Recv msrp message", msrp_recv_function, "", SAF_NONE); + SWITCH_ADD_APP(app_interface, "msrp_send", "Send file via msrp", "Send file via msrp", msrp_send_function, "", SAF_NONE); + SWITCH_ADD_APP(app_interface, "msrp_bridge", "Bridge msrp channels", "Bridge msrp channels", msrp_bridge_function, "dialstr", SAF_NONE); +#endif + + switch_console_set_complete("add msrp debug on"); + switch_console_set_complete("add msrp debug off"); + switch_console_set_complete("restart"); +} + + +/* For Emacs: + * Local Variables: + * mode:c + * indent-tabs-mode:t + * tab-width:4 + * c-basic-offset:4 + * End: + * For VIM: + * vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet: + */