From c36001f861d05d54305819148cbfca43ec22a868 Mon Sep 17 00:00:00 2001 From: isymchych Date: Wed, 30 Dec 2015 14:34:10 +0200 Subject: [PATCH] removed xmpp and RTC modules --- modules/RTC/DataChannels.js | 211 ---- modules/RTC/LocalStream.js | 145 --- modules/RTC/MediaStream.js | 57 - modules/RTC/RTC.js | 335 ----- modules/RTC/RTCUtils.js | 574 --------- modules/RTC/adapter.screenshare.js | 1168 ----------------- modules/UI/videolayout/VideoLayout.js | 3 +- modules/xmpp/JingleSession.js | 127 -- modules/xmpp/JingleSessionPC.js | 1520 ----------------------- modules/xmpp/LocalSSRCReplacement.js | 268 ---- modules/xmpp/SDP.js | 645 ---------- modules/xmpp/SDPDiffer.js | 168 --- modules/xmpp/SDPUtil.js | 361 ------ modules/xmpp/TraceablePeerConnection.js | 449 ------- modules/xmpp/moderator.js | 434 ------- modules/xmpp/recording.js | 178 --- modules/xmpp/strophe.emuc.js | 706 ----------- modules/xmpp/strophe.jingle.js | 341 ----- modules/xmpp/strophe.logger.js | 20 - modules/xmpp/strophe.moderate.js | 60 - modules/xmpp/strophe.ping.js | 121 -- modules/xmpp/strophe.rayo.js | 96 -- modules/xmpp/strophe.util.js | 43 - modules/xmpp/xmpp.js | 616 --------- 24 files changed, 1 insertion(+), 8645 deletions(-) delete mode 100644 modules/RTC/DataChannels.js delete mode 100644 modules/RTC/LocalStream.js delete mode 100644 modules/RTC/MediaStream.js delete mode 100644 modules/RTC/RTC.js delete mode 100644 modules/RTC/RTCUtils.js delete mode 100644 modules/RTC/adapter.screenshare.js delete mode 100644 modules/xmpp/JingleSession.js delete mode 100644 modules/xmpp/JingleSessionPC.js delete mode 100644 modules/xmpp/LocalSSRCReplacement.js delete mode 100644 modules/xmpp/SDP.js delete mode 100644 modules/xmpp/SDPDiffer.js delete mode 100644 modules/xmpp/SDPUtil.js delete mode 100644 modules/xmpp/TraceablePeerConnection.js delete mode 100644 modules/xmpp/moderator.js delete mode 100644 modules/xmpp/recording.js delete mode 100644 modules/xmpp/strophe.emuc.js delete mode 100644 modules/xmpp/strophe.jingle.js delete mode 100644 modules/xmpp/strophe.logger.js delete mode 100644 modules/xmpp/strophe.moderate.js delete mode 100644 modules/xmpp/strophe.ping.js delete mode 100644 modules/xmpp/strophe.rayo.js delete mode 100644 modules/xmpp/strophe.util.js delete mode 100644 modules/xmpp/xmpp.js diff --git a/modules/RTC/DataChannels.js b/modules/RTC/DataChannels.js deleted file mode 100644 index de0efa043..000000000 --- a/modules/RTC/DataChannels.js +++ /dev/null @@ -1,211 +0,0 @@ -/* global config, APP, Strophe */ -/* jshint -W101 */ - -// cache datachannels to avoid garbage collection -// https://code.google.com/p/chromium/issues/detail?id=405545 -var RTCEvents = require("../../service/RTC/RTCEvents"); - -var _dataChannels = []; -var eventEmitter = null; - -var DataChannels = { - /** - * Callback triggered by PeerConnection when new data channel is opened - * on the bridge. - * @param event the event info object. - */ - onDataChannel: function (event) { - var dataChannel = event.channel; - - dataChannel.onopen = function () { - console.info("Data channel opened by the Videobridge!", dataChannel); - - // Code sample for sending string and/or binary data - // Sends String message to the bridge - //dataChannel.send("Hello bridge!"); - // Sends 12 bytes binary message to the bridge - //dataChannel.send(new ArrayBuffer(12)); - - eventEmitter.emit(RTCEvents.DATA_CHANNEL_OPEN); - }; - - dataChannel.onerror = function (error) { - console.error("Data Channel Error:", error, dataChannel); - }; - - dataChannel.onmessage = function (event) { - var data = event.data; - // JSON - var obj; - - try { - obj = JSON.parse(data); - } - catch (e) { - console.error( - "Failed to parse data channel message as JSON: ", - data, - dataChannel); - } - if (('undefined' !== typeof(obj)) && (null !== obj)) { - var colibriClass = obj.colibriClass; - - if ("DominantSpeakerEndpointChangeEvent" === colibriClass) { - // Endpoint ID from the Videobridge. - var dominantSpeakerEndpoint = obj.dominantSpeakerEndpoint; - - console.info( - "Data channel new dominant speaker event: ", - dominantSpeakerEndpoint); - eventEmitter.emit(RTCEvents.DOMINANTSPEAKER_CHANGED, dominantSpeakerEndpoint); - } - else if ("InLastNChangeEvent" === colibriClass) { - var oldValue = obj.oldValue; - var newValue = obj.newValue; - // Make sure that oldValue and newValue are of type boolean. - var type; - - if ((type = typeof oldValue) !== 'boolean') { - if (type === 'string') { - oldValue = (oldValue == "true"); - } else { - oldValue = Boolean(oldValue).valueOf(); - } - } - if ((type = typeof newValue) !== 'boolean') { - if (type === 'string') { - newValue = (newValue == "true"); - } else { - newValue = Boolean(newValue).valueOf(); - } - } - - eventEmitter.emit(RTCEvents.LASTN_CHANGED, oldValue, newValue); - } - else if ("LastNEndpointsChangeEvent" === colibriClass) { - // The new/latest list of last-n endpoint IDs. - var lastNEndpoints = obj.lastNEndpoints; - // The list of endpoint IDs which are entering the list of - // last-n at this time i.e. were not in the old list of last-n - // endpoint IDs. - var endpointsEnteringLastN = obj.endpointsEnteringLastN; - - console.info( - "Data channel new last-n event: ", - lastNEndpoints, endpointsEnteringLastN, obj); - eventEmitter.emit(RTCEvents.LASTN_ENDPOINT_CHANGED, - lastNEndpoints, endpointsEnteringLastN, obj); - } - else { - console.debug("Data channel JSON-formatted message: ", obj); - // The received message appears to be appropriately - // formatted (i.e. is a JSON object which assigns a value to - // the mandatory property colibriClass) so don't just - // swallow it, expose it to public consumption. - eventEmitter.emit("rtc.datachannel." + colibriClass, obj); - } - } - }; - - dataChannel.onclose = function () { - console.info("The Data Channel closed", dataChannel); - var idx = _dataChannels.indexOf(dataChannel); - if (idx > -1) - _dataChannels = _dataChannels.splice(idx, 1); - }; - _dataChannels.push(dataChannel); - }, - - /** - * Binds "ondatachannel" event listener to given PeerConnection instance. - * @param peerConnection WebRTC peer connection instance. - */ - init: function (peerConnection, emitter) { - if(!config.openSctp) - return; - - peerConnection.ondatachannel = this.onDataChannel; - eventEmitter = emitter; - - // Sample code for opening new data channel from Jitsi Meet to the bridge. - // Although it's not a requirement to open separate channels from both bridge - // and peer as single channel can be used for sending and receiving data. - // So either channel opened by the bridge or the one opened here is enough - // for communication with the bridge. - /*var dataChannelOptions = - { - reliable: true - }; - var dataChannel - = peerConnection.createDataChannel("myChannel", dataChannelOptions); - - // Can be used only when is in open state - dataChannel.onopen = function () - { - dataChannel.send("My channel !!!"); - }; - dataChannel.onmessage = function (event) - { - var msgData = event.data; - console.info("Got My Data Channel Message:", msgData, dataChannel); - };*/ - }, - - handleSelectedEndpointEvent: function (userResource) { - onXXXEndpointChanged("selected", userResource); - }, - handlePinnedEndpointEvent: function (userResource) { - onXXXEndpointChanged("pinned", userResource); - }, - - some: function (callback, thisArg) { - if (_dataChannels && _dataChannels.length !== 0) { - if (thisArg) - return _dataChannels.some(callback, thisArg); - else - return _dataChannels.some(callback); - } else { - return false; - } - } -}; - -/** - * Notifies Videobridge about a change in the value of a specific - * endpoint-related property such as selected endpoint and pinnned endpoint. - * - * @param xxx the name of the endpoint-related property whose value changed - * @param userResource the new value of the endpoint-related property after the - * change - */ -function onXXXEndpointChanged(xxx, userResource) { - // Derive the correct words from xxx such as selected and Selected, pinned - // and Pinned. - var head = xxx.charAt(0); - var tail = xxx.substring(1); - var lower = head.toLowerCase() + tail; - var upper = head.toUpperCase() + tail; - - // Notify Videobridge about the specified endpoint change. - console.log(lower + ' endpoint changed: ', userResource); - DataChannels.some(function (dataChannel) { - if (dataChannel.readyState == 'open') { - console.log( - 'sending ' + lower - + ' endpoint changed notification to the bridge: ', - userResource); - - var jsonObject = {}; - - jsonObject.colibriClass = (upper + 'EndpointChangedEvent'); - jsonObject[lower + "Endpoint"] - = (userResource ? userResource : null); - dataChannel.send(JSON.stringify(jsonObject)); - - return true; - } - }); -} - -module.exports = DataChannels; - diff --git a/modules/RTC/LocalStream.js b/modules/RTC/LocalStream.js deleted file mode 100644 index bf30305f1..000000000 --- a/modules/RTC/LocalStream.js +++ /dev/null @@ -1,145 +0,0 @@ -/* global APP */ -var MediaStreamType = require("../../service/RTC/MediaStreamTypes"); -var RTCEvents = require("../../service/RTC/RTCEvents"); -var RTCBrowserType = require("./RTCBrowserType"); -var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js"); - -/** - * This implements 'onended' callback normally fired by WebRTC after the stream - * is stopped. There is no such behaviour yet in FF, so we have to add it. - * @param stream original WebRTC stream object to which 'onended' handling - * will be added. - */ -function implementOnEndedHandling(localStream) { - var stream = localStream.getOriginalStream(); - var originalStop = stream.stop; - stream.stop = function () { - originalStop.apply(stream); - if (localStream.isActive()) { - stream.onended(); - } - }; -} - -function LocalStream(stream, type, eventEmitter, videoType, isGUMStream) { - this.stream = stream; - this.eventEmitter = eventEmitter; - this.type = type; - this.videoType = videoType; - this.isGUMStream = true; - if(isGUMStream === false) - this.isGUMStream = isGUMStream; - var self = this; - if (MediaStreamType.AUDIO_TYPE === type) { - this.getTracks = function () { - return self.stream.getAudioTracks(); - }; - } else { - this.getTracks = function () { - return self.stream.getVideoTracks(); - }; - } - - APP.RTC.addMediaStreamInactiveHandler( - this.stream, - function () { - self.streamEnded(); - }); - - if (RTCBrowserType.isFirefox()) { - implementOnEndedHandling(this); - } -} - -LocalStream.prototype.streamEnded = function () { - this.eventEmitter.emit(StreamEventTypes.EVENT_TYPE_LOCAL_ENDED, this); -}; - -LocalStream.prototype.getOriginalStream = function() -{ - return this.stream; -}; - -LocalStream.prototype.isAudioStream = function () { - return MediaStreamType.AUDIO_TYPE === this.type; -}; - -LocalStream.prototype.isVideoStream = function () { - return MediaStreamType.VIDEO_TYPE === this.type; -}; - -LocalStream.prototype.setMute = function (mute) -{ - var isAudio = this.isAudioStream(); - var eventType = isAudio ? RTCEvents.AUDIO_MUTE : RTCEvents.VIDEO_MUTE; - - if ((window.location.protocol != "https:" && this.isGUMStream) || - (isAudio && this.isGUMStream) || this.videoType === "screen" || - // FIXME FF does not support 'removeStream' method used to mute - RTCBrowserType.isFirefox()) { - - var tracks = this.getTracks(); - for (var idx = 0; idx < tracks.length; idx++) { - tracks[idx].enabled = !mute; - } - this.eventEmitter.emit(eventType, mute); - } else { - if (mute) { - APP.xmpp.removeStream(this.stream); - APP.RTC.stopMediaStream(this.stream); - this.eventEmitter.emit(eventType, true); - } else { - var self = this; - APP.RTC.rtcUtils.obtainAudioAndVideoPermissions( - (this.isAudioStream() ? ["audio"] : ["video"]), - function (stream) { - if (isAudio) { - APP.RTC.changeLocalAudio(stream, - function () { - self.eventEmitter.emit(eventType, false); - }); - } else { - APP.RTC.changeLocalVideo(stream, false, - function () { - self.eventEmitter.emit(eventType, false); - }); - } - }); - } - } -}; - -LocalStream.prototype.isMuted = function () { - var tracks = []; - if (this.isAudioStream()) { - tracks = this.stream.getAudioTracks(); - } else { - if (!this.isActive()) - return true; - tracks = this.stream.getVideoTracks(); - } - for (var idx = 0; idx < tracks.length; idx++) { - if(tracks[idx].enabled) - return false; - } - return true; -}; - -LocalStream.prototype.getId = function () { - return this.stream.getTracks()[0].id; -}; - -/** - * Checks whether the MediaStream is avtive/not ended. - * When there is no check for active we don't have information and so - * will return that stream is active (in case of FF). - * @returns {boolean} whether MediaStream is active. - */ -LocalStream.prototype.isActive = function () { - if((typeof this.stream.active !== "undefined")) - return this.stream.active; - else - return true; -}; - -module.exports = LocalStream; diff --git a/modules/RTC/MediaStream.js b/modules/RTC/MediaStream.js deleted file mode 100644 index fc500781e..000000000 --- a/modules/RTC/MediaStream.js +++ /dev/null @@ -1,57 +0,0 @@ -var MediaStreamType = require("../../service/RTC/MediaStreamTypes"); - -/** - * Creates a MediaStream object for the given data, session id and ssrc. - * It is a wrapper class for the MediaStream. - * - * @param data the data object from which we obtain the stream, - * the peerjid, etc. - * @param ssrc the ssrc corresponding to this MediaStream - * @param mute the whether this MediaStream is muted - * - * @constructor - */ -function MediaStream(data, ssrc, browser, eventEmitter, muted, type) { - - // XXX(gp) to minimize headaches in the future, we should build our - // abstractions around tracks and not streams. ORTC is track based API. - // Mozilla expects m-lines to represent media tracks. - // - // Practically, what I'm saying is that we should have a MediaTrack class - // and not a MediaStream class. - // - // Also, we should be able to associate multiple SSRCs with a MediaTrack as - // a track might have an associated RTX and FEC sources. - - if (!type) { - console.log("Errrm...some code needs an update..."); - } - - this.stream = data.stream; - this.peerjid = data.peerjid; - this.videoType = data.videoType; - this.ssrc = ssrc; - this.type = type; - this.muted = muted; - this.eventEmitter = eventEmitter; -} - -// FIXME duplicated with LocalStream methods - extract base class -MediaStream.prototype.isAudioStream = function () { - return MediaStreamType.AUDIO_TYPE === this.type; -}; - -MediaStream.prototype.isVideoStream = function () { - return MediaStreamType.VIDEO_TYPE === this.type; -}; - -MediaStream.prototype.getOriginalStream = function () { - return this.stream; -}; - -MediaStream.prototype.setMute = function (value) { - this.stream.muted = value; - this.muted = value; -}; - -module.exports = MediaStream; diff --git a/modules/RTC/RTC.js b/modules/RTC/RTC.js deleted file mode 100644 index 790e81708..000000000 --- a/modules/RTC/RTC.js +++ /dev/null @@ -1,335 +0,0 @@ -/* global APP */ -var EventEmitter = require("events"); -var RTCBrowserType = require("./RTCBrowserType"); -var RTCUtils = require("./RTCUtils.js"); -var LocalStream = require("./LocalStream.js"); -var DataChannels = require("./DataChannels"); -var MediaStream = require("./MediaStream.js"); -var DesktopSharingEventTypes - = require("../../service/desktopsharing/DesktopSharingEventTypes"); -var MediaStreamType = require("../../service/RTC/MediaStreamTypes"); -var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js"); -var RTCEvents = require("../../service/RTC/RTCEvents.js"); -var XMPPEvents = require("../../service/xmpp/XMPPEvents"); -var UIEvents = require("../../service/UI/UIEvents"); - -var eventEmitter = new EventEmitter(); - - -function getMediaStreamUsage() -{ - var result = { - audio: true, - video: true - }; - - /** There are some issues with the desktop sharing - * when this property is enabled. - * WARNING: We must change the implementation to start video/audio if we - * receive from the focus that the peer is not muted. - - var isSecureConnection = window.location.protocol == "https:"; - - if(config.disableEarlyMediaPermissionRequests || !isSecureConnection) - { - result = { - audio: false, - video: false - }; - - } - **/ - - return result; -} - -var RTC = { - // Exposes DataChannels to public consumption (e.g. jitsi-meet-torture) - // without the necessity to require the module. - "DataChannels": DataChannels, - - rtcUtils: null, - devices: { - audio: true, - video: true - }, - remoteStreams: {}, - localAudio: null, - localVideo: null, - addStreamListener: function (listener, eventType) { - eventEmitter.on(eventType, listener); - }, - addListener: function (type, listener) { - eventEmitter.on(type, listener); - }, - removeStreamListener: function (listener, eventType) { - if(!(eventType instanceof StreamEventTypes)) - throw "Illegal argument"; - - eventEmitter.removeListener(eventType, listener); - }, - createLocalStream: function (stream, type, change, videoType, - isMuted, isGUMStream) { - - var localStream = - new LocalStream(stream, type, eventEmitter, videoType, isGUMStream); - if(isMuted === true) - localStream.setMute(true); - - if (MediaStreamType.AUDIO_TYPE === type) { - this.localAudio = localStream; - } else { - this.localVideo = localStream; - } - var eventType = StreamEventTypes.EVENT_TYPE_LOCAL_CREATED; - if(change) - eventType = StreamEventTypes.EVENT_TYPE_LOCAL_CHANGED; - - eventEmitter.emit(eventType, localStream, isMuted); - return localStream; - }, - createRemoteStream: function (data, ssrc) { - var jid = data.peerjid || APP.xmpp.myJid(); - - // check the video muted state from last stored presence if any - var muted = false; - var pres = APP.xmpp.getLastPresence(jid); - if (pres && pres.videoMuted) { - muted = pres.videoMuted; - } - - var self = this; - [MediaStreamType.AUDIO_TYPE, MediaStreamType.VIDEO_TYPE].forEach( - function (type) { - var tracks = - type == MediaStreamType.AUDIO_TYPE - ? data.stream.getAudioTracks() : data.stream.getVideoTracks(); - if (!tracks || !Array.isArray(tracks) || !tracks.length) { - console.log("Not creating a(n) " + type + " stream: no tracks"); - return; - } - - var remoteStream = new MediaStream(data, ssrc, - RTCBrowserType.getBrowserType(), eventEmitter, muted, type); - - if (!self.remoteStreams[jid]) { - self.remoteStreams[jid] = {}; - } - self.remoteStreams[jid][type] = remoteStream; - eventEmitter.emit(StreamEventTypes.EVENT_TYPE_REMOTE_CREATED, - remoteStream); - }); - }, - getPCConstraints: function () { - return this.rtcUtils.pc_constraints; - }, - getUserMediaWithConstraints:function(um, success_callback, - failure_callback, resolution, - bandwidth, fps, desktopStream) - { - return this.rtcUtils.getUserMediaWithConstraints(um, success_callback, - failure_callback, resolution, bandwidth, fps, desktopStream); - }, - attachMediaStream: function (elSelector, stream) { - this.rtcUtils.attachMediaStream(elSelector, stream); - }, - getStreamID: function (stream) { - return this.rtcUtils.getStreamID(stream); - }, - getVideoSrc: function (element) { - return this.rtcUtils.getVideoSrc(element); - }, - setVideoSrc: function (element, src) { - this.rtcUtils.setVideoSrc(element, src); - }, - getVideoElementName: function () { - return RTCBrowserType.isTemasysPluginUsed() ? 'object' : 'video'; - }, - dispose: function() { - if (this.rtcUtils) { - this.rtcUtils = null; - } - }, - stop: function () { - this.dispose(); - }, - start: function () { - var self = this; - APP.desktopsharing.addListener( - DesktopSharingEventTypes.NEW_STREAM_CREATED, - function (stream, isUsingScreenStream, callback) { - self.changeLocalVideo(stream, isUsingScreenStream, callback); - }); - APP.xmpp.addListener(XMPPEvents.CALL_INCOMING, function(event) { - DataChannels.init(event.peerconnection, eventEmitter); - }); - APP.UI.addListener(UIEvents.SELECTED_ENDPOINT, - DataChannels.handleSelectedEndpointEvent); - APP.UI.addListener(UIEvents.PINNED_ENDPOINT, - DataChannels.handlePinnedEndpointEvent); - - // In case of IE we continue from 'onReady' callback - // passed to RTCUtils constructor. It will be invoked by Temasys plugin - // once it is initialized. - var onReady = function () { - eventEmitter.emit(RTCEvents.RTC_READY, true); - self.rtcUtils.obtainAudioAndVideoPermissions( - null, null, getMediaStreamUsage()); - }; - - this.rtcUtils = new RTCUtils(this, eventEmitter, onReady); - - // Call onReady() if Temasys plugin is not used - if (!RTCBrowserType.isTemasysPluginUsed()) { - onReady(); - } - }, - muteRemoteVideoStream: function (jid, value) { - var stream; - - if(this.remoteStreams[jid] && - this.remoteStreams[jid][MediaStreamType.VIDEO_TYPE]) { - stream = this.remoteStreams[jid][MediaStreamType.VIDEO_TYPE]; - } - - if(!stream) - return true; - - if (value != stream.muted) { - stream.setMute(value); - return true; - } - return false; - }, - changeLocalVideo: function (stream, isUsingScreenStream, callback) { - var oldStream = this.localVideo.getOriginalStream(); - var type = (isUsingScreenStream ? "screen" : "camera"); - var localCallback = callback; - if(this.localVideo.isMuted() && this.localVideo.videoType !== type) { - localCallback = function() { - APP.xmpp.setVideoMute(false, function(mute) { - eventEmitter.emit(RTCEvents.VIDEO_MUTE, mute); - }); - - callback(); - }; - } - // FIXME: Workaround for FF/IE/Safari - if (stream && stream.videoStream) { - stream = stream.videoStream; - } - var videoStream = this.rtcUtils.createStream(stream, true); - this.localVideo = - this.createLocalStream(videoStream, "video", true, type); - // Stop the stream - this.stopMediaStream(oldStream); - - APP.xmpp.switchStreams(videoStream, oldStream,localCallback); - }, - changeLocalAudio: function (stream, callback) { - var oldStream = this.localAudio.getOriginalStream(); - var newStream = this.rtcUtils.createStream(stream); - this.localAudio - = this.createLocalStream( - newStream, MediaStreamType.AUDIO_TYPE, true); - // Stop the stream - this.stopMediaStream(oldStream); - APP.xmpp.switchStreams(newStream, oldStream, callback, true); - }, - isVideoMuted: function (jid) { - if (jid === APP.xmpp.myJid()) { - var localVideo = APP.RTC.localVideo; - return (!localVideo || localVideo.isMuted()); - } else { - if (!APP.RTC.remoteStreams[jid] || - !APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE]) { - return null; - } - return APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE].muted; - } - }, - setVideoMute: function (mute, callback, options) { - if (!this.localVideo) - return; - - if (mute == APP.RTC.localVideo.isMuted()) - { - APP.xmpp.sendVideoInfoPresence(mute); - if (callback) - callback(mute); - } - else - { - APP.RTC.localVideo.setMute(mute); - APP.xmpp.setVideoMute( - mute, - callback, - options); - } - }, - setDeviceAvailability: function (devices) { - if(!devices) - return; - if(devices.audio === true || devices.audio === false) - this.devices.audio = devices.audio; - if(devices.video === true || devices.video === false) - this.devices.video = devices.video; - eventEmitter.emit(RTCEvents.AVAILABLE_DEVICES_CHANGED, this.devices); - }, - /** - * A method to handle stopping of the stream. - * One point to handle the differences in various implementations. - * @param mediaStream MediaStream object to stop. - */ - stopMediaStream: function (mediaStream) { - mediaStream.getTracks().forEach(function (track) { - // stop() not supported with IE - if (track.stop) { - track.stop(); - } - }); - - // leave stop for implementation still using it - if (mediaStream.stop) { - mediaStream.stop(); - } - }, - /** - * Adds onended/inactive handler to a MediaStream. - * @param mediaStream a MediaStream to attach onended/inactive handler - * @param handler the handler - */ - addMediaStreamInactiveHandler: function (mediaStream, handler) { - if (mediaStream.addEventListener) { - // chrome - if(typeof mediaStream.active !== "undefined") - mediaStream.oninactive = handler; - else - mediaStream.onended = handler; - } else { - // themasys - mediaStream.attachEvent('ended', function () { - handler(mediaStream); - }); - } - }, - /** - * Removes onended/inactive handler. - * @param mediaStream the MediaStream to remove the handler from. - * @param handler the handler to remove. - */ - removeMediaStreamInactiveHandler: function (mediaStream, handler) { - if (mediaStream.removeEventListener) { - // chrome - if(typeof mediaStream.active !== "undefined") - mediaStream.oninactive = null; - else - mediaStream.onended = null; - } else { - // themasys - mediaStream.detachEvent('ended', handler); - } - } -}; - -module.exports = RTC; diff --git a/modules/RTC/RTCUtils.js b/modules/RTC/RTCUtils.js deleted file mode 100644 index 67872b76f..000000000 --- a/modules/RTC/RTCUtils.js +++ /dev/null @@ -1,574 +0,0 @@ -/* global APP, config, require, attachMediaStream, getUserMedia, - RTCPeerConnection, webkitMediaStream, webkitURL, webkitRTCPeerConnection, - mozRTCIceCandidate, mozRTCSessionDescription, mozRTCPeerConnection */ -/* jshint -W101 */ -var MediaStreamType = require("../../service/RTC/MediaStreamTypes"); -var RTCBrowserType = require("./RTCBrowserType"); -var Resolutions = require("../../service/RTC/Resolutions"); -var RTCEvents = require("../../service/RTC/RTCEvents"); -var AdapterJS = require("./adapter.screenshare"); - -var currentResolution = null; - -function getPreviousResolution(resolution) { - if(!Resolutions[resolution]) - return null; - var order = Resolutions[resolution].order; - var res = null; - var resName = null; - for(var i in Resolutions) { - var tmp = Resolutions[i]; - if (!res || (res.order < tmp.order && tmp.order < order)) { - resName = i; - res = tmp; - } - } - return resName; -} - -function setResolutionConstraints(constraints, resolution) { - var isAndroid = RTCBrowserType.isAndroid(); - - if (Resolutions[resolution]) { - constraints.video.mandatory.minWidth = Resolutions[resolution].width; - constraints.video.mandatory.minHeight = Resolutions[resolution].height; - } - else if (isAndroid) { - // FIXME can't remember if the purpose of this was to always request - // low resolution on Android ? if yes it should be moved up front - constraints.video.mandatory.minWidth = 320; - constraints.video.mandatory.minHeight = 240; - constraints.video.mandatory.maxFrameRate = 15; - } - - if (constraints.video.mandatory.minWidth) - constraints.video.mandatory.maxWidth = - constraints.video.mandatory.minWidth; - if (constraints.video.mandatory.minHeight) - constraints.video.mandatory.maxHeight = - constraints.video.mandatory.minHeight; -} - -function getConstraints(um, resolution, bandwidth, fps, desktopStream) { - var constraints = {audio: false, video: false}; - - if (um.indexOf('video') >= 0) { - // same behaviour as true - constraints.video = { mandatory: {}, optional: [] }; - - constraints.video.optional.push({ googLeakyBucket: true }); - - setResolutionConstraints(constraints, resolution); - } - if (um.indexOf('audio') >= 0) { - if (!RTCBrowserType.isFirefox()) { - // same behaviour as true - constraints.audio = { mandatory: {}, optional: []}; - // if it is good enough for hangouts... - constraints.audio.optional.push( - {googEchoCancellation: true}, - {googAutoGainControl: true}, - {googNoiseSupression: true}, - {googHighpassFilter: true}, - {googNoisesuppression2: true}, - {googEchoCancellation2: true}, - {googAutoGainControl2: true} - ); - } else { - constraints.audio = true; - } - } - if (um.indexOf('screen') >= 0) { - if (RTCBrowserType.isChrome()) { - constraints.video = { - mandatory: { - chromeMediaSource: "screen", - googLeakyBucket: true, - maxWidth: window.screen.width, - maxHeight: window.screen.height, - maxFrameRate: 3 - }, - optional: [] - }; - } else if (RTCBrowserType.isTemasysPluginUsed()) { - constraints.video = { - optional: [ - { - sourceId: AdapterJS.WebRTCPlugin.plugin.screensharingKey - } - ] - }; - } else if (RTCBrowserType.isFirefox()) { - constraints.video = { - mozMediaSource: "window", - mediaSource: "window" - }; - - } else { - console.error( - "'screen' WebRTC media source is supported only in Chrome" + - " and with Temasys plugin"); - } - } - if (um.indexOf('desktop') >= 0) { - constraints.video = { - mandatory: { - chromeMediaSource: "desktop", - chromeMediaSourceId: desktopStream, - googLeakyBucket: true, - maxWidth: window.screen.width, - maxHeight: window.screen.height, - maxFrameRate: 3 - }, - optional: [] - }; - } - - if (bandwidth) { - if (!constraints.video) { - //same behaviour as true - constraints.video = {mandatory: {}, optional: []}; - } - constraints.video.optional.push({bandwidth: bandwidth}); - } - if (fps) { - // for some cameras it might be necessary to request 30fps - // so they choose 30fps mjpg over 10fps yuy2 - if (!constraints.video) { - // same behaviour as true; - constraints.video = {mandatory: {}, optional: []}; - } - constraints.video.mandatory.minFrameRate = fps; - } - - // we turn audio for both audio and video tracks, the fake audio & video seems to work - // only when enabled in one getUserMedia call, we cannot get fake audio separate by fake video - // this later can be a problem with some of the tests - if(RTCBrowserType.isFirefox() && config.firefox_fake_device) - { - constraints.audio = true; - constraints.fake = true; - } - - return constraints; -} - - -function RTCUtils(RTCService, eventEmitter, onTemasysPluginReady) -{ - var self = this; - this.service = RTCService; - this.eventEmitter = eventEmitter; - if (RTCBrowserType.isFirefox()) { - var FFversion = RTCBrowserType.getFirefoxVersion(); - if (FFversion >= 40) { - this.peerconnection = mozRTCPeerConnection; - this.getUserMedia = navigator.mozGetUserMedia.bind(navigator); - this.pc_constraints = {}; - this.attachMediaStream = function (element, stream) { - // srcObject is being standardized and FF will eventually - // support that unprefixed. FF also supports the - // "element.src = URL.createObjectURL(...)" combo, but that - // will be deprecated in favour of srcObject. - // - // https://groups.google.com/forum/#!topic/mozilla.dev.media/pKOiioXonJg - // https://github.com/webrtc/samples/issues/302 - if(!element[0]) - return; - element[0].mozSrcObject = stream; - element[0].play(); - }; - this.getStreamID = function (stream) { - var id = stream.id; - if (!id) { - var tracks = stream.getVideoTracks(); - if (!tracks || tracks.length === 0) { - tracks = stream.getAudioTracks(); - } - id = tracks[0].id; - } - return APP.xmpp.filter_special_chars(id); - }; - this.getVideoSrc = function (element) { - if(!element) - return null; - return element.mozSrcObject; - }; - this.setVideoSrc = function (element, src) { - if(element) - element.mozSrcObject = src; - }; - window.RTCSessionDescription = mozRTCSessionDescription; - window.RTCIceCandidate = mozRTCIceCandidate; - } else { - console.error( - "Firefox version too old: " + FFversion + ". Required >= 40."); - window.location.href = 'unsupported_browser.html'; - return; - } - - } else if (RTCBrowserType.isChrome() || RTCBrowserType.isOpera()) { - this.peerconnection = webkitRTCPeerConnection; - this.getUserMedia = navigator.webkitGetUserMedia.bind(navigator); - this.attachMediaStream = function (element, stream) { - element.attr('src', webkitURL.createObjectURL(stream)); - }; - this.getStreamID = function (stream) { - // streams from FF endpoints have the characters '{' and '}' - // that make jQuery choke. - return APP.xmpp.filter_special_chars(stream.id); - }; - this.getVideoSrc = function (element) { - if(!element) - return null; - return element.getAttribute("src"); - }; - this.setVideoSrc = function (element, src) { - if(element) - element.setAttribute("src", src); - }; - // DTLS should now be enabled by default but.. - this.pc_constraints = {'optional': [{'DtlsSrtpKeyAgreement': 'true'}]}; - if (RTCBrowserType.isAndroid()) { - this.pc_constraints = {}; // disable DTLS on Android - } - if (!webkitMediaStream.prototype.getVideoTracks) { - webkitMediaStream.prototype.getVideoTracks = function () { - return this.videoTracks; - }; - } - if (!webkitMediaStream.prototype.getAudioTracks) { - webkitMediaStream.prototype.getAudioTracks = function () { - return this.audioTracks; - }; - } - } - // Detect IE/Safari - else if (RTCBrowserType.isTemasysPluginUsed()) { - - //AdapterJS.WebRTCPlugin.setLogLevel( - // AdapterJS.WebRTCPlugin.PLUGIN_LOG_LEVELS.VERBOSE); - - AdapterJS.webRTCReady(function (isPlugin) { - - self.peerconnection = RTCPeerConnection; - self.getUserMedia = getUserMedia; - self.attachMediaStream = function (elSel, stream) { - - if (stream.id === "dummyAudio" || stream.id === "dummyVideo") { - return; - } - - attachMediaStream(elSel[0], stream); - }; - self.getStreamID = function (stream) { - return APP.xmpp.filter_special_chars(stream.label); - }; - self.getVideoSrc = function (element) { - if (!element) { - console.warn("Attempt to get video SRC of null element"); - return null; - } - var children = element.children; - for (var i = 0; i !== children.length; ++i) { - if (children[i].name === 'streamId') { - return children[i].value; - } - } - //console.info(element.id + " SRC: " + src); - return null; - }; - self.setVideoSrc = function (element, src) { - //console.info("Set video src: ", element, src); - if (!src) { - console.warn("Not attaching video stream, 'src' is null"); - return; - } - AdapterJS.WebRTCPlugin.WaitForPluginReady(); - var stream = AdapterJS.WebRTCPlugin.plugin - .getStreamWithId(AdapterJS.WebRTCPlugin.pageId, src); - attachMediaStream(element, stream); - }; - - onTemasysPluginReady(isPlugin); - }); - } else { - try { - console.log('Browser does not appear to be WebRTC-capable'); - } catch (e) { } - window.location.href = 'unsupported_browser.html'; - } -} - - -RTCUtils.prototype.getUserMediaWithConstraints = function( - um, success_callback, failure_callback, resolution,bandwidth, fps, - desktopStream) { - currentResolution = resolution; - - var constraints = getConstraints( - um, resolution, bandwidth, fps, desktopStream); - - console.info("Get media constraints", constraints); - - var self = this; - - try { - this.getUserMedia(constraints, - function (stream) { - console.log('onUserMediaSuccess'); - self.setAvailableDevices(um, true); - success_callback(stream); - }, - function (error) { - self.setAvailableDevices(um, false); - console.warn('Failed to get access to local media. Error ', - error, constraints); - self.eventEmitter.emit(RTCEvents.GET_USER_MEDIA_FAILED, error); - if (failure_callback) { - failure_callback(error); - } - }); - } catch (e) { - console.error('GUM failed: ', e); - self.eventEmitter.emit(RTCEvents.GET_USER_MEDIA_FAILED, e); - if(failure_callback) { - failure_callback(e); - } - } -}; - -RTCUtils.prototype.setAvailableDevices = function (um, available) { - var devices = {}; - if(um.indexOf("video") != -1) { - devices.video = available; - } - if(um.indexOf("audio") != -1) { - devices.audio = available; - } - this.service.setDeviceAvailability(devices); -}; - -/** - * We ask for audio and video combined stream in order to get permissions and - * not to ask twice. - */ -RTCUtils.prototype.obtainAudioAndVideoPermissions = - function(devices, callback, usageOptions) -{ - var self = this; - // Get AV - - var successCallback = function (stream) { - if(callback) - callback(stream, usageOptions); - else - self.successCallback(stream, usageOptions); - }; - - if(!devices) - devices = ['audio', 'video']; - - var newDevices = []; - - - if(usageOptions) - for(var i = 0; i < devices.length; i++) { - var device = devices[i]; - if(usageOptions[device] === true) - newDevices.push(device); - } - else - newDevices = devices; - - if(newDevices.length === 0) { - successCallback(); - return; - } - - if (RTCBrowserType.isFirefox() || RTCBrowserType.isTemasysPluginUsed()) { - - // With FF/IE we can't split the stream into audio and video because FF - // doesn't support media stream constructors. So, we need to get the - // audio stream separately from the video stream using two distinct GUM - // calls. Not very user friendly :-( but we don't have many other - // options neither. - // - // Note that we pack those 2 streams in a single object and pass it to - // the successCallback method. - var obtainVideo = function (audioStream) { - self.getUserMediaWithConstraints( - ['video'], - function (videoStream) { - return successCallback({ - audioStream: audioStream, - videoStream: videoStream - }); - }, - function (error) { - console.error( - 'failed to obtain video stream - stop', error); - self.errorCallback(error); - }, - config.resolution || '360'); - }; - var obtainAudio = function () { - self.getUserMediaWithConstraints( - ['audio'], - function (audioStream) { - if (newDevices.indexOf('video') !== -1) - obtainVideo(audioStream); - }, - function (error) { - console.error( - 'failed to obtain audio stream - stop', error); - self.errorCallback(error); - } - ); - }; - if (newDevices.indexOf('audio') !== -1) { - obtainAudio(); - } else { - obtainVideo(null); - } - } else { - this.getUserMediaWithConstraints( - newDevices, - function (stream) { - successCallback(stream); - }, - function (error) { - self.errorCallback(error); - }, - config.resolution || '360'); - } -}; - -RTCUtils.prototype.successCallback = function (stream, usageOptions) { - // If this is FF or IE, the stream parameter is *not* a MediaStream object, - // it's an object with two properties: audioStream, videoStream. - if (stream && stream.getAudioTracks && stream.getVideoTracks) - console.log('got', stream, stream.getAudioTracks().length, - stream.getVideoTracks().length); - this.handleLocalStream(stream, usageOptions); -}; - -RTCUtils.prototype.errorCallback = function (error) { - var self = this; - console.error('failed to obtain audio/video stream - trying audio only', error); - var resolution = getPreviousResolution(currentResolution); - if(typeof error == "object" && error.constraintName && error.name - && (error.name == "ConstraintNotSatisfiedError" || - error.name == "OverconstrainedError") && - (error.constraintName == "minWidth" || error.constraintName == "maxWidth" || - error.constraintName == "minHeight" || error.constraintName == "maxHeight") - && resolution) - { - self.getUserMediaWithConstraints(['audio', 'video'], - function (stream) { - return self.successCallback(stream); - }, function (error) { - return self.errorCallback(error); - }, resolution); - } - else { - self.getUserMediaWithConstraints( - ['audio'], - function (stream) { - return self.successCallback(stream); - }, - function (error) { - console.error('failed to obtain audio/video stream - stop', - error); - return self.successCallback(null); - } - ); - } -}; - -RTCUtils.prototype.handleLocalStream = function(stream, usageOptions) { - // If this is FF, the stream parameter is *not* a MediaStream object, it's - // an object with two properties: audioStream, videoStream. - var audioStream, videoStream; - if(window.webkitMediaStream) - { - audioStream = new webkitMediaStream(); - videoStream = new webkitMediaStream(); - if(stream) { - var audioTracks = stream.getAudioTracks(); - - for (var i = 0; i < audioTracks.length; i++) { - audioStream.addTrack(audioTracks[i]); - } - - var videoTracks = stream.getVideoTracks(); - - for (i = 0; i < videoTracks.length; i++) { - videoStream.addTrack(videoTracks[i]); - } - } - } - else if (RTCBrowserType.isFirefox() || RTCBrowserType.isTemasysPluginUsed()) - { // Firefox and Temasys plugin - if (stream && stream.audioStream) - audioStream = stream.audioStream; - else - audioStream = new DummyMediaStream("dummyAudio"); - - if (stream && stream.videoStream) - videoStream = stream.videoStream; - else - videoStream = new DummyMediaStream("dummyVideo"); - } - - var audioMuted = (usageOptions && usageOptions.audio === false), - videoMuted = (usageOptions && usageOptions.video === false); - - var audioGUM = (!usageOptions || usageOptions.audio !== false), - videoGUM = (!usageOptions || usageOptions.video !== false); - - - this.service.createLocalStream( - audioStream, MediaStreamType.AUDIO_TYPE, null, null, - audioMuted, audioGUM); - - this.service.createLocalStream( - videoStream, MediaStreamType.VIDEO_TYPE, null, 'camera', - videoMuted, videoGUM); -}; - -function DummyMediaStream(id) { - this.id = id; - this.label = id; - this.stop = function() { }; - this.getAudioTracks = function() { return []; }; - this.getVideoTracks = function() { return []; }; -} - -RTCUtils.prototype.createStream = function(stream, isVideo) { - var newStream = null; - if (window.webkitMediaStream) { - newStream = new webkitMediaStream(); - if (newStream) { - var tracks = (isVideo ? stream.getVideoTracks() : stream.getAudioTracks()); - - for (var i = 0; i < tracks.length; i++) { - newStream.addTrack(tracks[i]); - } - } - - } - else { - // FIXME: this is duplicated with 'handleLocalStream' !!! - if (stream) { - newStream = stream; - } else { - newStream = - new DummyMediaStream(isVideo ? "dummyVideo" : "dummyAudio"); - } - } - - return newStream; -}; - -module.exports = RTCUtils; diff --git a/modules/RTC/adapter.screenshare.js b/modules/RTC/adapter.screenshare.js deleted file mode 100644 index 345c9fb0b..000000000 --- a/modules/RTC/adapter.screenshare.js +++ /dev/null @@ -1,1168 +0,0 @@ -/*! adapterjs - v0.12.0 - 2015-09-04 */ - -// 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.12.0'; - -// 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 -}; - -// 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.onwebrtcready = callback; - } -}; - -// Plugin namespace -AdapterJS.WebRTCPlugin = AdapterJS.WebRTCPlugin || {}; - -// The object to store plugin information -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'; -} - -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 { - AdapterJS.WebRTCPlugin.documentReadyInterval = setInterval(function () { - if (document.readyState === 'complete') { - // TODO: update comments, we wait for the document to be ready - clearInterval(AdapterJS.WebRTCPlugin.documentReadyInterval); - AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY; - - AdapterJS.maybeThroughWebRTCReady(); - } - }, 100); - } -}; - -AdapterJS.maybeThroughWebRTCReady = function() { - if (!AdapterJS.onwebrtcreadyDone) { - AdapterJS.onwebrtcreadyDone = true; - - 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. -// - 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, checkMatch = navigator.userAgent.match( - /(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || []; - if (/trident/i.test(checkMatch[1])) { - hasMatch = /\brv[ :]+(\d+)/g.exec(navigator.userAgent) || []; - webrtcDetectedBrowser = 'IE'; - webrtcDetectedVersion = parseInt(hasMatch[1] || '0', 10); - } else if (checkMatch[1] === 'Chrome') { - hasMatch = navigator.userAgent.match(/\bOPR\/(\d+)/); - if (hasMatch !== null) { - webrtcDetectedBrowser = 'opera'; - webrtcDetectedVersion = parseInt(hasMatch[1], 10); - } - } - if (navigator.userAgent.indexOf('Safari')) { - if (typeof InstallTrigger !== 'undefined') { - webrtcDetectedBrowser = 'firefox'; - } else if (/*@cc_on!@*/ false || !!document.documentMode) { - webrtcDetectedBrowser = 'IE'; - } else if ( - Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0) { - webrtcDetectedBrowser = 'safari'; - } else if (!!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0) { - webrtcDetectedBrowser = 'opera'; - } else if (!!window.chrome) { - webrtcDetectedBrowser = 'chrome'; - } - } - if (!webrtcDetectedBrowser) { - webrtcDetectedVersion = checkMatch[1]; - } - if (!webrtcDetectedVersion) { - try { - checkMatch = (checkMatch[2]) ? [checkMatch[1], checkMatch[2]] : - [navigator.appName, navigator.appVersion, '-?']; - if ((hasMatch = navigator.userAgent.match(/version\/(\d+)/i)) !== null) { - checkMatch.splice(1, 1, hasMatch[1]); - } - webrtcDetectedVersion = parseInt(checkMatch[1], 10); - } catch (error) { } - } -}; - -// To fix configuration as some browsers does not support -// the 'urls' attribute. -AdapterJS.maybeFixConfiguration = function (pcConfig) { - if (pcConfig === null) { - return; - } - for (var i = 0; i < pcConfig.iceServers.length; i++) { - if (pcConfig.iceServers[i].hasOwnProperty('urls')) { - pcConfig.iceServers[i].url = pcConfig.iceServers[i].urls; - delete pcConfig.iceServers[i].urls; - } - } -}; - -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.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); - 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()'); - } - window.open(buttonLink, !!openNewTab ? '_blank' : '_top'); - - e.preventDefault(); - try { - event.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; - -// Detected webrtc datachannel support. Types are: -// - 'SCTP': SCTP datachannel support. -// - 'RTP': RTP datachannel support. -webrtcDetectedDCSupport = 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; - -// Check for browser types and react accordingly -if (navigator.mozGetUserMedia) { - webrtcDetectedBrowser = 'firefox'; - webrtcDetectedVersion = parseInt(navigator - .userAgent.match(/Firefox\/([0-9]+)\./)[1], 10); - webrtcDetectedType = 'moz'; - webrtcDetectedDCSupport = 'SCTP'; - - RTCPeerConnection = function (pcConfig, pcConstraints) { - AdapterJS.maybeFixConfiguration(pcConfig); - return new mozRTCPeerConnection(pcConfig, pcConstraints); - }; - - // The RTCSessionDescription object. - RTCSessionDescription = mozRTCSessionDescription; - window.RTCSessionDescription = RTCSessionDescription; - - // The RTCIceCandidate object. - RTCIceCandidate = mozRTCIceCandidate; - window.RTCIceCandidate = RTCIceCandidate; - - window.getUserMedia = navigator.mozGetUserMedia.bind(navigator); - navigator.getUserMedia = window.getUserMedia; - - // 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) { - var iceServer = null; - var url_parts = url.split(':'); - if (url_parts[0].indexOf('stun') === 0) { - iceServer = { url : url }; - } else if (url_parts[0].indexOf('turn') === 0) { - if (webrtcDetectedVersion < 27) { - var turn_url_parts = url.split('?'); - if (turn_url_parts.length === 1 || - turn_url_parts[1].indexOf('transport=udp') === 0) { - iceServer = { - url : turn_url_parts[0], - credential : password, - username : username - }; - } - } else { - iceServer = { - url : url, - credential : password, - username : username - }; - } - } - return iceServer; - }; - - createIceServers = function (urls, username, password) { - var iceServers = []; - for (i = 0; i < urls.length; i++) { - var iceServer = createIceServer(urls[i], username, password); - if (iceServer !== null) { - iceServers.push(iceServer); - } - } - return iceServers; - }; - - attachMediaStream = function (element, stream) { - element.mozSrcObject = stream; - if (stream !== null) - element.play(); - - return element; - }; - - reattachMediaStream = function (to, from) { - to.mozSrcObject = from.mozSrcObject; - to.play(); - return to; - }; - - MediaStreamTrack.getSources = MediaStreamTrack.getSources || function (callback) { - if (!callback) { - throw new TypeError('Failed to execute \'getSources\' on \'MediaStreamTrack\'' + - ': 1 argument required, but only 0 present.'); - } - return callback([]); - }; - - // Fake get{Video,Audio}Tracks - if (!MediaStream.prototype.getVideoTracks) { - MediaStream.prototype.getVideoTracks = function () { - return []; - }; - } - if (!MediaStream.prototype.getAudioTracks) { - MediaStream.prototype.getAudioTracks = function () { - return []; - }; - } - - AdapterJS.maybeThroughWebRTCReady(); -} else if (navigator.webkitGetUserMedia) { - webrtcDetectedBrowser = 'chrome'; - webrtcDetectedType = 'webkit'; - webrtcDetectedVersion = parseInt(navigator - .userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10); - // check if browser is opera 20+ - var checkIfOpera = navigator.userAgent.match(/\bOPR\/(\d+)/); - if (checkIfOpera !== null) { - webrtcDetectedBrowser = 'opera'; - webrtcDetectedVersion = parseInt(checkIfOpera[1], 10); - } - // check browser datachannel support - if ((webrtcDetectedBrowser === 'chrome' && webrtcDetectedVersion >= 31) || - (webrtcDetectedBrowser === 'opera' && webrtcDetectedVersion >= 20)) { - webrtcDetectedDCSupport = 'SCTP'; - } else if (webrtcDetectedBrowser === 'chrome' && webrtcDetectedVersion < 30 && - webrtcDetectedVersion > 24) { - webrtcDetectedDCSupport = 'RTP'; - } else { - webrtcDetectedDCSupport = ''; - } - - createIceServer = function (url, username, password) { - var iceServer = null; - var url_parts = url.split(':'); - if (url_parts[0].indexOf('stun') === 0) { - iceServer = { 'url' : url }; - } else if (url_parts[0].indexOf('turn') === 0) { - iceServer = { - 'url' : url, - 'credential' : password, - 'username' : username - }; - } - return iceServer; - }; - - createIceServers = function (urls, username, password) { - 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; - }; - - RTCPeerConnection = function (pcConfig, pcConstraints) { - if (webrtcDetectedVersion < 34) { - AdapterJS.maybeFixConfiguration(pcConfig); - } - return new webkitRTCPeerConnection(pcConfig, pcConstraints); - }; - - window.getUserMedia = navigator.webkitGetUserMedia.bind(navigator); - navigator.getUserMedia = window.getUserMedia; - - attachMediaStream = function (element, stream) { - 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 = (stream === null ? '' : URL.createObjectURL(stream)); - } else { - console.log('Error attaching stream to element.'); - } - return element; - }; - - reattachMediaStream = function (to, from) { - to.src = from.src; - return to; - }; - - AdapterJS.maybeThroughWebRTCReady(); -} else if (navigator.mediaDevices && navigator.userAgent.match( - /Edge\/(\d+).(\d+)$/)) { - webrtcDetectedBrowser = 'edge'; - - webrtcDetectedVersion = - parseInt(navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)[2], 10); - - // the minimum version still supported by adapter. - webrtcMinimumVersion = 12; - - 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; - }; - - AdapterJS.maybeThroughWebRTCReady(); -} else { // TRY TO USE PLUGIN - // 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 */ - } - webrtcDetectedType = 'plugin'; - webrtcDetectedDCSupport = 'plugin'; - 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 url_parts = url.split(':'); - if (url_parts[0].indexOf('stun') === 0) { - iceServer = { - 'url' : url, - 'hasCredentials' : false - }; - } else if (url_parts[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) { - var iceServers = null; - if (servers) { - iceServers = servers.iceServers; - for (var i = 0; i < iceServers.length; i++) { - 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); - } - } - var mandatory = (constraints && constraints.mandatory) ? - constraints.mandatory : null; - var optional = (constraints && constraints.optional) ? - constraints.optional : null; - - AdapterJS.WebRTCPlugin.WaitForPluginReady(); - return AdapterJS.WebRTCPlugin.plugin. - PeerConnection(AdapterJS.WebRTCPlugin.pageId, - iceServers, mandatory, optional); - }; - - MediaStreamTrack = {}; - MediaStreamTrack.getSources = function (callback) { - AdapterJS.WebRTCPlugin.callWhenPluginReady(function() { - AdapterJS.WebRTCPlugin.plugin.GetSources(callback); - }); - }; - - window.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 = window.getUserMedia; - - attachMediaStream = function (element, stream) { - if (!element || !element.parentNode) { - return; - } - - var streamId - if (stream === null) { - streamId = ''; - } - else { - stream.enableSoundTracks(true); // TODO: remove on 0.12.0 - 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.getBoundingClientRect) { - var rectObject = element.getBoundingClientRect(); - width = rectObject.width + 'px'; - height = rectObject.height + 'px'; - } - else if (element.width) { - width = element.width; - height = element.height; - } else { - // TODO: What scenario could bring us here? - } - - 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); - newElement.onplaying = (element.onplaying) ? element.onplaying : function (arg) {}; - newElement.onplay = (element.onplay) ? element.onplay : function (arg) {}; - newElement.onclick = (element.onclick) ? element.onclick : function (arg) {}; - if (isIE) { // on IE the event needs to be plugged manually - newElement.attachEvent('onplaying', newElement.onplaying); - newElement.attachEvent('onplay', newElement.onplay); - newElement._TemOnClick = function (id) { - var arg = { - srcElement : document.getElementById(id) - }; - newElement.onclick(arg); - }; - } - - 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'); - } - }; - - 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); -} diff --git a/modules/UI/videolayout/VideoLayout.js b/modules/UI/videolayout/VideoLayout.js index 9142ca736..95d040715 100644 --- a/modules/UI/videolayout/VideoLayout.js +++ b/modules/UI/videolayout/VideoLayout.js @@ -12,7 +12,6 @@ import LargeVideoManager, {VideoContainerType} from "./LargeVideo"; import {PreziContainerType} from '../prezi/Prezi'; import LocalVideo from "./LocalVideo"; -var MediaStreamType = require("../../../service/RTC/MediaStreamTypes"); var RTCBrowserType = require('../../RTC/RTCBrowserType'); var remoteVideos = {}; @@ -674,7 +673,7 @@ var VideoLayout = { var jid = APP.xmpp.findJidFromResource(resourceJid); var mediaStream = - APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE]; + APP.RTC.remoteStreams[jid]['video']; var sel = remoteVideo.selectVideoElement(); APP.RTC.attachMediaStream(sel, mediaStream.stream); diff --git a/modules/xmpp/JingleSession.js b/modules/xmpp/JingleSession.js deleted file mode 100644 index 1b40a9d85..000000000 --- a/modules/xmpp/JingleSession.js +++ /dev/null @@ -1,127 +0,0 @@ -/* - * JingleSession provides an API to manage a single Jingle session. We will - * have different implementations depending on the underlying interface used - * (i.e. WebRTC and ORTC) and here we hold the code common to all of them. - */ -function JingleSession(me, sid, connection, service, eventEmitter) { - /** - * Our JID. - */ - this.me = me; - - /** - * The Jingle session identifier. - */ - this.sid = sid; - - /** - * The XMPP connection. - */ - this.connection = connection; - - /** - * The XMPP service. - */ - this.service = service; - - /** - * The event emitter. - */ - this.eventEmitter = eventEmitter; - - /** - * Whether to use dripping or not. Dripping is sending trickle candidates - * not one-by-one. - * Note: currently we do not support 'false'. - */ - this.usedrip = true; - - /** - * When dripping is used, stores ICE candidates which are to be sent. - */ - this.drip_container = []; - - // Media constraints. Is this WebRTC only? - this.media_constraints = null; - - // ICE servers config (RTCConfiguration?). - this.ice_config = {}; -} - -/** - * Prepares this object to initiate a session. - * @param peerjid the JID of the remote peer. - * @param isInitiator whether we will be the Jingle initiator. - * @param media_constraints - * @param ice_config - */ -JingleSession.prototype.initialize = function(peerjid, isInitiator, - media_constraints, ice_config) { - this.media_constraints = media_constraints; - this.ice_config = ice_config; - - if (this.state !== null) { - console.error('attempt to initiate on session ' + this.sid + - 'in state ' + this.state); - return; - } - this.state = 'pending'; - this.initiator = isInitiator ? this.me : peerjid; - this.responder = !isInitiator ? this.me : peerjid; - this.peerjid = peerjid; - - this.doInitialize(); -}; - -/** - * Finishes initialization. - */ -JingleSession.prototype.doInitialize = function() {}; - -/** - * Adds the ICE candidates found in the 'contents' array as remote candidates? - * Note: currently only used on transport-info - */ -JingleSession.prototype.addIceCandidates = function(contents) {}; - -/** - * Handles an 'add-source' event. - * - * @param contents an array of Jingle 'content' elements. - */ -JingleSession.prototype.addSources = function(contents) {}; - -/** - * Handles a 'remove-source' event. - * - * @param contents an array of Jingle 'content' elements. - */ -JingleSession.prototype.removeSources = function(contents) {}; - -/** - * Terminates this Jingle session (stops sending media and closes the streams?) - */ -JingleSession.prototype.terminate = function() {}; - -/** - * Sends a Jingle session-terminate message to the peer and terminates the - * session. - * @param reason - * @param text - */ -JingleSession.prototype.sendTerminate = function(reason, text) {}; - -/** - * Handles an offer from the remote peer (prepares to accept a session). - * @param jingle the 'jingle' XML element. - */ -JingleSession.prototype.setOffer = function(jingle) {}; - -/** - * Handles an answer from the remote peer (prepares to accept a session). - * @param jingle the 'jingle' XML element. - */ -JingleSession.prototype.setAnswer = function(jingle) {}; - - -module.exports = JingleSession; diff --git a/modules/xmpp/JingleSessionPC.js b/modules/xmpp/JingleSessionPC.js deleted file mode 100644 index 6500e6186..000000000 --- a/modules/xmpp/JingleSessionPC.js +++ /dev/null @@ -1,1520 +0,0 @@ -/* jshint -W117 */ -/* jshint -W101 */ -var JingleSession = require("./JingleSession"); -var TraceablePeerConnection = require("./TraceablePeerConnection"); -var SDPDiffer = require("./SDPDiffer"); -var SDPUtil = require("./SDPUtil"); -var SDP = require("./SDP"); -var async = require("async"); -var transform = require("sdp-transform"); -var XMPPEvents = require("../../service/xmpp/XMPPEvents"); -var RTCEvents = require("../../service/RTC/RTCEvents"); -var RTCBrowserType = require("../RTC/RTCBrowserType"); -var SSRCReplacement = require("./LocalSSRCReplacement"); - -// Jingle stuff -function JingleSessionPC(me, sid, connection, service, eventEmitter) { - JingleSession.call(this, me, sid, connection, service, eventEmitter); - this.initiator = null; - this.responder = null; - this.peerjid = null; - this.state = null; - this.localSDP = null; - this.remoteSDP = null; - this.pc_constraints = null; - - this.usetrickle = true; - this.usepranswer = false; // early transport warmup -- mind you, this might fail. depends on webrtc issue 1718 - - this.hadstuncandidate = false; - this.hadturncandidate = false; - this.lasticecandidate = false; - - this.statsinterval = null; - - this.reason = null; - - this.addssrc = []; - this.removessrc = []; - this.pendingop = null; - this.switchstreams = false; - - this.wait = true; - this.localStreamsSSRC = null; - this.ssrcOwners = {}; - this.ssrcVideoTypes = {}; - this.eventEmitter = eventEmitter; - - /** - * The indicator which determines whether the (local) video has been muted - * in response to a user command in contrast to an automatic decision made - * by the application logic. - */ - this.videoMuteByUser = false; - this.modifySourcesQueue = async.queue(this._modifySources.bind(this), 1); - // We start with the queue paused. We resume it when the signaling state is - // stable and the ice connection state is connected. - this.modifySourcesQueue.pause(); -} -//XXX this is badly broken... -JingleSessionPC.prototype = JingleSession.prototype; -JingleSessionPC.prototype.constructor = JingleSessionPC; - - -JingleSessionPC.prototype.setOffer = function(offer) { - this.setRemoteDescription(offer, 'offer'); -}; - -JingleSessionPC.prototype.setAnswer = function(answer) { - this.setRemoteDescription(answer, 'answer'); -}; - -JingleSessionPC.prototype.updateModifySourcesQueue = function() { - var signalingState = this.peerconnection.signalingState; - var iceConnectionState = this.peerconnection.iceConnectionState; - if (signalingState === 'stable' && iceConnectionState === 'connected') { - this.modifySourcesQueue.resume(); - } else { - this.modifySourcesQueue.pause(); - } -}; - -JingleSessionPC.prototype.doInitialize = function () { - var self = this; - - this.hadstuncandidate = false; - this.hadturncandidate = false; - this.lasticecandidate = false; - // True if reconnect is in progress - this.isreconnect = false; - // Set to true if the connection was ever stable - this.wasstable = false; - - this.peerconnection = new TraceablePeerConnection( - this.connection.jingle.ice_config, - this.connection.jingle.pc_constraints, - this); - - this.peerconnection.onicecandidate = function (event) { - var protocol; - if (event && event.candidate) { - protocol = (typeof event.candidate.protocol === 'string') - ? event.candidate.protocol.toLowerCase() : ''; - if ((config.webrtcIceTcpDisable && protocol == 'tcp') || - (config.webrtcIceUdpDisable && protocol == 'udp')) { - return; - } - } - self.sendIceCandidate(event.candidate); - }; - this.peerconnection.onaddstream = function (event) { - if (event.stream.id === 'default') { - // This is a recvonly stream. Clients that implement Unified Plan, - // such as Firefox use recvonly "streams/channels/tracks" for - // receiving remote stream/tracks, as opposed to Plan B where there - // are only 3 channels: audio, video and data. - console.log("RECVONLY REMOTE STREAM IGNORED: " + event.stream + - " - " + event.stream.id); - return; - } - - console.log("REMOTE STREAM ADDED: ", event.stream, event.stream.id); - self.remoteStreamAdded(event); - }; - this.peerconnection.onremovestream = function (event) { - // Remove the stream from remoteStreams? - console.log("We are ignoring a removestream event: " + event); - }; - this.peerconnection.onsignalingstatechange = function (event) { - if (!(self && self.peerconnection)) return; - console.info("Signaling: " + this.peerconnection.signalingState); - if (self.peerconnection.signalingState === 'stable') { - self.wasstable = true; - } - self.updateModifySourcesQueue(); - }; - /** - * The oniceconnectionstatechange event handler contains the code to execute when the iceconnectionstatechange event, - * of type Event, is received by this RTCPeerConnection. Such an event is sent when the value of - * RTCPeerConnection.iceConnectionState changes. - * - * @param event the event containing information about the change - */ - this.peerconnection.oniceconnectionstatechange = function (event) { - if (!(self && self.peerconnection)) return; - console.log("(TIME) ICE " + self.peerconnection.iceConnectionState + - ":\t", window.performance.now()); - self.updateModifySourcesQueue(); - switch (self.peerconnection.iceConnectionState) { - case 'connected': - - // Informs interested parties that the connection has been restored. - if (self.peerconnection.signalingState === 'stable' && self.isreconnect) - self.eventEmitter.emit(XMPPEvents.CONNECTION_RESTORED); - self.isreconnect = false; - - break; - case 'disconnected': - self.isreconnect = true; - // Informs interested parties that the connection has been interrupted. - if (self.wasstable) - self.eventEmitter.emit(XMPPEvents.CONNECTION_INTERRUPTED); - break; - case 'failed': - self.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED); - break; - } - onIceConnectionStateChange(self.sid, self); - }; - this.peerconnection.onnegotiationneeded = function (event) { - self.eventEmitter.emit(XMPPEvents.PEERCONNECTION_READY, self); - }; - if (APP.RTC.localAudio) { - self.peerconnection.addStream(APP.RTC.localAudio.getOriginalStream()); - } - if (APP.RTC.localVideo) { - self.peerconnection.addStream(APP.RTC.localVideo.getOriginalStream()); - } -}; - -function onIceConnectionStateChange(sid, session) { - switch (session.peerconnection.iceConnectionState) { - case 'checking': - session.timeChecking = (new Date()).getTime(); - session.firstconnect = true; - break; - case 'completed': // on caller side - case 'connected': - if (session.firstconnect) { - session.firstconnect = false; - var metadata = {}; - metadata.setupTime - = (new Date()).getTime() - session.timeChecking; - session.peerconnection.getStats(function (res) { - if(res && res.result) { - res.result().forEach(function (report) { - if (report.type == 'googCandidatePair' && - report.stat('googActiveConnection') == 'true') { - metadata.localCandidateType - = report.stat('googLocalCandidateType'); - metadata.remoteCandidateType - = report.stat('googRemoteCandidateType'); - - // log pair as well so we can get nice pie - // charts - metadata.candidatePair - = report.stat('googLocalCandidateType') + - ';' + - report.stat('googRemoteCandidateType'); - - if (report.stat('googRemoteAddress').indexOf('[') === 0) - { - metadata.ipv6 = true; - } - } - }); - } - }); - } - break; - } -} - -JingleSessionPC.prototype.accept = function () { - this.state = 'active'; - - var pranswer = this.peerconnection.localDescription; - if (!pranswer || pranswer.type != 'pranswer') { - return; - } - console.log('going from pranswer to answer'); - if (this.usetrickle) { - // remove candidates already sent from session-accept - var lines = SDPUtil.find_lines(pranswer.sdp, 'a=candidate:'); - for (var i = 0; i < lines.length; i++) { - pranswer.sdp = pranswer.sdp.replace(lines[i] + '\r\n', ''); - } - } - while (SDPUtil.find_line(pranswer.sdp, 'a=inactive')) { - // FIXME: change any inactive to sendrecv or whatever they were originally - pranswer.sdp = pranswer.sdp.replace('a=inactive', 'a=sendrecv'); - } - var prsdp = new SDP(pranswer.sdp); - if (config.webrtcIceTcpDisable) { - prsdp.removeTcpCandidates = true; - } - if (config.webrtcIceUdpDisable) { - prsdp.removeUdpCandidates = true; - } - var accept = $iq({to: this.peerjid, - type: 'set'}) - .c('jingle', {xmlns: 'urn:xmpp:jingle:1', - action: 'session-accept', - initiator: this.initiator, - responder: this.responder, - sid: this.sid }); - // FIXME why do we generate session-accept in 3 different places ? - prsdp.toJingle( - accept, - this.initiator == this.me ? 'initiator' : 'responder', - this.localStreamsSSRC); - var sdp = this.peerconnection.localDescription.sdp; - while (SDPUtil.find_line(sdp, 'a=inactive')) { - // FIXME: change any inactive to sendrecv or whatever they were originally - sdp = sdp.replace('a=inactive', 'a=sendrecv'); - } - var self = this; - this.peerconnection.setLocalDescription(new RTCSessionDescription({type: 'answer', sdp: sdp}), - function () { - //console.log('setLocalDescription success'); - self.setLocalDescription(); - - SSRCReplacement.processSessionInit(accept); - - self.connection.sendIQ(accept, - function () { - var ack = {}; - ack.source = 'answer'; - $(document).trigger('ack.jingle', [self.sid, ack]); - }, - function (stanza) { - var error = ($(stanza).find('error').length) ? { - code: $(stanza).find('error').attr('code'), - reason: $(stanza).find('error :first')[0].tagName - }:{}; - error.source = 'answer'; - JingleSessionPC.onJingleError(self.sid, error); - }, - 10000); - }, - function (e) { - console.error('setLocalDescription failed', e); - self.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED); - } - ); -}; - -JingleSessionPC.prototype.terminate = function (reason) { - this.state = 'ended'; - this.reason = reason; - this.peerconnection.close(); - if (this.statsinterval !== null) { - window.clearInterval(this.statsinterval); - this.statsinterval = null; - } -}; - -JingleSessionPC.prototype.active = function () { - return this.state == 'active'; -}; - -JingleSessionPC.prototype.sendIceCandidate = function (candidate) { - var self = this; - if (candidate && !this.lasticecandidate) { - var ice = SDPUtil.iceparams(this.localSDP.media[candidate.sdpMLineIndex], this.localSDP.session); - var jcand = SDPUtil.candidateToJingle(candidate.candidate); - if (!(ice && jcand)) { - console.error('failed to get ice && jcand'); - return; - } - ice.xmlns = 'urn:xmpp:jingle:transports:ice-udp:1'; - - if (jcand.type === 'srflx') { - this.hadstuncandidate = true; - } else if (jcand.type === 'relay') { - this.hadturncandidate = true; - } - - if (this.usetrickle) { - if (this.usedrip) { - if (this.drip_container.length === 0) { - // start 20ms callout - window.setTimeout(function () { - if (self.drip_container.length === 0) return; - self.sendIceCandidates(self.drip_container); - self.drip_container = []; - }, 20); - - } - this.drip_container.push(candidate); - return; - } else { - self.sendIceCandidate([candidate]); - } - } - } else { - //console.log('sendIceCandidate: last candidate.'); - if (!this.usetrickle) { - //console.log('should send full offer now...'); - //FIXME why do we generate session-accept in 3 different places ? - var init = $iq({to: this.peerjid, - type: 'set'}) - .c('jingle', {xmlns: 'urn:xmpp:jingle:1', - action: this.peerconnection.localDescription.type == 'offer' ? 'session-initiate' : 'session-accept', - initiator: this.initiator, - sid: this.sid}); - this.localSDP = new SDP(this.peerconnection.localDescription.sdp); - if (config.webrtcIceTcpDisable) { - this.localSDP.removeTcpCandidates = true; - } - if (config.webrtcIceUdpDisable) { - this.localSDP.removeUdpCandidates = true; - } - var sendJingle = function (ssrc) { - if(!ssrc) - ssrc = {}; - self.localSDP.toJingle( - init, - self.initiator == self.me ? 'initiator' : 'responder', - ssrc); - - SSRCReplacement.processSessionInit(init); - - self.connection.sendIQ(init, - function () { - //console.log('session initiate ack'); - var ack = {}; - ack.source = 'offer'; - $(document).trigger('ack.jingle', [self.sid, ack]); - }, - function (stanza) { - self.state = 'error'; - self.peerconnection.close(); - var error = ($(stanza).find('error').length) ? { - code: $(stanza).find('error').attr('code'), - reason: $(stanza).find('error :first')[0].tagName, - }:{}; - error.source = 'offer'; - JingleSessionPC.onJingleError(self.sid, error); - }, - 10000); - }; - sendJingle(); - } - this.lasticecandidate = true; - console.log('Have we encountered any srflx candidates? ' + this.hadstuncandidate); - console.log('Have we encountered any relay candidates? ' + this.hadturncandidate); - - if (!(this.hadstuncandidate || this.hadturncandidate) && this.peerconnection.signalingState != 'closed') { - $(document).trigger('nostuncandidates.jingle', [this.sid]); - } - } -}; - -JingleSessionPC.prototype.sendIceCandidates = function (candidates) { - console.log('sendIceCandidates', candidates); - var cand = $iq({to: this.peerjid, type: 'set'}) - .c('jingle', {xmlns: 'urn:xmpp:jingle:1', - action: 'transport-info', - initiator: this.initiator, - sid: this.sid}); - for (var mid = 0; mid < this.localSDP.media.length; mid++) { - var cands = candidates.filter(function (el) { return el.sdpMLineIndex == mid; }); - var mline = SDPUtil.parse_mline(this.localSDP.media[mid].split('\r\n')[0]); - if (cands.length > 0) { - var ice = SDPUtil.iceparams(this.localSDP.media[mid], this.localSDP.session); - ice.xmlns = 'urn:xmpp:jingle:transports:ice-udp:1'; - cand.c('content', {creator: this.initiator == this.me ? 'initiator' : 'responder', - name: (cands[0].sdpMid? cands[0].sdpMid : mline.media) - }).c('transport', ice); - for (var i = 0; i < cands.length; i++) { - cand.c('candidate', SDPUtil.candidateToJingle(cands[i].candidate)).up(); - } - // add fingerprint - if (SDPUtil.find_line(this.localSDP.media[mid], 'a=fingerprint:', this.localSDP.session)) { - var tmp = SDPUtil.parse_fingerprint(SDPUtil.find_line(this.localSDP.media[mid], 'a=fingerprint:', this.localSDP.session)); - tmp.required = true; - cand.c( - 'fingerprint', - {xmlns: 'urn:xmpp:jingle:apps:dtls:0'}) - .t(tmp.fingerprint); - delete tmp.fingerprint; - cand.attrs(tmp); - cand.up(); - } - cand.up(); // transport - cand.up(); // content - } - } - // might merge last-candidate notification into this, but it is called alot later. See webrtc issue #2340 - //console.log('was this the last candidate', this.lasticecandidate); - this.connection.sendIQ(cand, - function () { - var ack = {}; - ack.source = 'transportinfo'; - $(document).trigger('ack.jingle', [this.sid, ack]); - }, - function (stanza) { - var error = ($(stanza).find('error').length) ? { - code: $(stanza).find('error').attr('code'), - reason: $(stanza).find('error :first')[0].tagName, - }:{}; - error.source = 'transportinfo'; - JingleSessionPC.onJingleError(this.sid, error); - }, - 10000); -}; - - -JingleSessionPC.prototype.sendOffer = function () { - //console.log('sendOffer...'); - var self = this; - this.peerconnection.createOffer(function (sdp) { - self.createdOffer(sdp); - }, - function (e) { - console.error('createOffer failed', e); - }, - this.media_constraints - ); -}; - -// FIXME createdOffer is never used in jitsi-meet -JingleSessionPC.prototype.createdOffer = function (sdp) { - //console.log('createdOffer', sdp); - var self = this; - this.localSDP = new SDP(sdp.sdp); - //this.localSDP.mangle(); - var sendJingle = function () { - var init = $iq({to: this.peerjid, - type: 'set'}) - .c('jingle', {xmlns: 'urn:xmpp:jingle:1', - action: 'session-initiate', - initiator: this.initiator, - sid: this.sid}); - self.localSDP.toJingle( - init, - this.initiator == this.me ? 'initiator' : 'responder', - this.localStreamsSSRC); - - SSRCReplacement.processSessionInit(init); - - self.connection.sendIQ(init, - function () { - var ack = {}; - ack.source = 'offer'; - $(document).trigger('ack.jingle', [self.sid, ack]); - }, - function (stanza) { - self.state = 'error'; - self.peerconnection.close(); - var error = ($(stanza).find('error').length) ? { - code: $(stanza).find('error').attr('code'), - reason: $(stanza).find('error :first')[0].tagName, - }:{}; - error.source = 'offer'; - JingleSessionPC.onJingleError(self.sid, error); - }, - 10000); - }; - sdp.sdp = this.localSDP.raw; - this.peerconnection.setLocalDescription(sdp, - function () { - if(self.usetrickle) - { - sendJingle(); - } - self.setLocalDescription(); - //console.log('setLocalDescription success'); - }, - function (e) { - console.error('setLocalDescription failed', e); - self.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED); - } - ); - var cands = SDPUtil.find_lines(this.localSDP.raw, 'a=candidate:'); - for (var i = 0; i < cands.length; i++) { - var cand = SDPUtil.parse_icecandidate(cands[i]); - if (cand.type == 'srflx') { - this.hadstuncandidate = true; - } else if (cand.type == 'relay') { - this.hadturncandidate = true; - } - } -}; - -JingleSessionPC.prototype.readSsrcInfo = function (contents) { - var self = this; - $(contents).each(function (idx, content) { - var name = $(content).attr('name'); - var mediaType = this.getAttribute('name'); - var ssrcs = $(content).find('description>source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]'); - ssrcs.each(function () { - var ssrc = this.getAttribute('ssrc'); - $(this).find('>ssrc-info[xmlns="http://jitsi.org/jitmeet"]').each( - function () { - var owner = this.getAttribute('owner'); - self.ssrcOwners[ssrc] = owner; - } - ); - }); - }); -}; - -JingleSessionPC.prototype.getSsrcOwner = function (ssrc) { - return this.ssrcOwners[ssrc]; -}; - -JingleSessionPC.prototype.setRemoteDescription = function (elem, desctype) { - this.remoteSDP = new SDP(''); - if (config.webrtcIceTcpDisable) { - this.remoteSDP.removeTcpCandidates = true; - } - if (config.webrtcIceUdpDisable) { - this.remoteSDP.removeUdpCandidates = true; - } - this.remoteSDP.fromJingle(elem); - this.readSsrcInfo($(elem).find(">content")); - if (this.peerconnection.remoteDescription !== null) { - console.log('setRemoteDescription when remote description is not null, should be pranswer', this.peerconnection.remoteDescription); - if (this.peerconnection.remoteDescription.type == 'pranswer') { - var pranswer = new SDP(this.peerconnection.remoteDescription.sdp); - for (var i = 0; i < pranswer.media.length; i++) { - // make sure we have ice ufrag and pwd - if (!SDPUtil.find_line(this.remoteSDP.media[i], 'a=ice-ufrag:', this.remoteSDP.session)) { - if (SDPUtil.find_line(pranswer.media[i], 'a=ice-ufrag:', pranswer.session)) { - this.remoteSDP.media[i] += SDPUtil.find_line(pranswer.media[i], 'a=ice-ufrag:', pranswer.session) + '\r\n'; - } else { - console.warn('no ice ufrag?'); - } - if (SDPUtil.find_line(pranswer.media[i], 'a=ice-pwd:', pranswer.session)) { - this.remoteSDP.media[i] += SDPUtil.find_line(pranswer.media[i], 'a=ice-pwd:', pranswer.session) + '\r\n'; - } else { - console.warn('no ice pwd?'); - } - } - // copy over candidates - var lines = SDPUtil.find_lines(pranswer.media[i], 'a=candidate:'); - for (var j = 0; j < lines.length; j++) { - this.remoteSDP.media[i] += lines[j] + '\r\n'; - } - } - this.remoteSDP.raw = this.remoteSDP.session + this.remoteSDP.media.join(''); - } - } - var remotedesc = new RTCSessionDescription({type: desctype, sdp: this.remoteSDP.raw}); - - this.peerconnection.setRemoteDescription(remotedesc, - function () { - //console.log('setRemoteDescription success'); - }, - function (e) { - console.error('setRemoteDescription error', e); - JingleSessionPC.onJingleFatalError(self, e); - } - ); -}; - -/** - * Adds remote ICE candidates to this Jingle session. - * @param elem An array of Jingle "content" elements? - */ -JingleSessionPC.prototype.addIceCandidate = function (elem) { - var self = this; - if (this.peerconnection.signalingState == 'closed') { - return; - } - if (!this.peerconnection.remoteDescription && this.peerconnection.signalingState == 'have-local-offer') { - console.log('trickle ice candidate arriving before session accept...'); - // create a PRANSWER for setRemoteDescription - if (!this.remoteSDP) { - var cobbled = 'v=0\r\n' + - 'o=- 1923518516 2 IN IP4 0.0.0.0\r\n' +// FIXME - 's=-\r\n' + - 't=0 0\r\n'; - // first, take some things from the local description - for (var i = 0; i < this.localSDP.media.length; i++) { - cobbled += SDPUtil.find_line(this.localSDP.media[i], 'm=') + '\r\n'; - cobbled += SDPUtil.find_lines(this.localSDP.media[i], 'a=rtpmap:').join('\r\n') + '\r\n'; - if (SDPUtil.find_line(this.localSDP.media[i], 'a=mid:')) { - cobbled += SDPUtil.find_line(this.localSDP.media[i], 'a=mid:') + '\r\n'; - } - cobbled += 'a=inactive\r\n'; - } - this.remoteSDP = new SDP(cobbled); - } - // then add things like ice and dtls from remote candidate - elem.each(function () { - for (var i = 0; i < self.remoteSDP.media.length; i++) { - if (SDPUtil.find_line(self.remoteSDP.media[i], 'a=mid:' + $(this).attr('name')) || - self.remoteSDP.media[i].indexOf('m=' + $(this).attr('name')) === 0) { - if (!SDPUtil.find_line(self.remoteSDP.media[i], 'a=ice-ufrag:')) { - var tmp = $(this).find('transport'); - self.remoteSDP.media[i] += 'a=ice-ufrag:' + tmp.attr('ufrag') + '\r\n'; - self.remoteSDP.media[i] += 'a=ice-pwd:' + tmp.attr('pwd') + '\r\n'; - tmp = $(this).find('transport>fingerprint'); - if (tmp.length) { - self.remoteSDP.media[i] += 'a=fingerprint:' + tmp.attr('hash') + ' ' + tmp.text() + '\r\n'; - } else { - console.log('no dtls fingerprint (webrtc issue #1718?)'); - self.remoteSDP.media[i] += 'a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:BAADBAADBAADBAADBAADBAADBAADBAADBAADBAAD\r\n'; - } - break; - } - } - } - }); - this.remoteSDP.raw = this.remoteSDP.session + this.remoteSDP.media.join(''); - - // we need a complete SDP with ice-ufrag/ice-pwd in all parts - // this makes the assumption that the PRANSWER is constructed such that the ice-ufrag is in all mediaparts - // but it could be in the session part as well. since the code above constructs this sdp this can't happen however - var iscomplete = this.remoteSDP.media.filter(function (mediapart) { - return SDPUtil.find_line(mediapart, 'a=ice-ufrag:'); - }).length == this.remoteSDP.media.length; - - if (iscomplete) { - console.log('setting pranswer'); - try { - this.peerconnection.setRemoteDescription(new RTCSessionDescription({type: 'pranswer', sdp: this.remoteSDP.raw }), - function() { - }, - function(e) { - console.log('setRemoteDescription pranswer failed', e.toString()); - }); - } catch (e) { - console.error('setting pranswer failed', e); - } - } else { - //console.log('not yet setting pranswer'); - } - } - // operate on each content element - elem.each(function () { - // would love to deactivate this, but firefox still requires it - var idx = -1; - var i; - for (i = 0; i < self.remoteSDP.media.length; i++) { - if (SDPUtil.find_line(self.remoteSDP.media[i], 'a=mid:' + $(this).attr('name')) || - self.remoteSDP.media[i].indexOf('m=' + $(this).attr('name')) === 0) { - idx = i; - break; - } - } - if (idx == -1) { // fall back to localdescription - for (i = 0; i < self.localSDP.media.length; i++) { - if (SDPUtil.find_line(self.localSDP.media[i], 'a=mid:' + $(this).attr('name')) || - self.localSDP.media[i].indexOf('m=' + $(this).attr('name')) === 0) { - idx = i; - break; - } - } - } - var name = $(this).attr('name'); - // TODO: check ice-pwd and ice-ufrag? - $(this).find('transport>candidate').each(function () { - var line, candidate; - var protocol = this.getAttribute('protocol'); - protocol = - (typeof protocol === 'string') ? protocol.toLowerCase() : ''; - if ((config.webrtcIceTcpDisable && protocol == 'tcp') || - (config.webrtcIceUdpDisable && protocol == 'udp')) { - return; - } - - line = SDPUtil.candidateFromJingle(this); - candidate = new RTCIceCandidate({sdpMLineIndex: idx, - sdpMid: name, - candidate: line}); - try { - self.peerconnection.addIceCandidate(candidate); - } catch (e) { - console.error('addIceCandidate failed', e.toString(), line); - self.eventEmitter.emit(RTCEvents.ADD_ICE_CANDIDATE_FAILED, err, self.peerconnection); - } - }); - }); -}; - -JingleSessionPC.prototype.sendAnswer = function (provisional) { - //console.log('createAnswer', provisional); - var self = this; - this.peerconnection.createAnswer( - function (sdp) { - self.createdAnswer(sdp, provisional); - }, - function (e) { - console.error('createAnswer failed', e); - self.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED); - }, - this.media_constraints - ); -}; - -JingleSessionPC.prototype.createdAnswer = function (sdp, provisional) { - //console.log('createAnswer callback'); - var self = this; - this.localSDP = new SDP(sdp.sdp); - //this.localSDP.mangle(); - this.usepranswer = provisional === true; - if (this.usetrickle) { - if (this.usepranswer) { - sdp.type = 'pranswer'; - for (var i = 0; i < this.localSDP.media.length; i++) { - this.localSDP.media[i] = this.localSDP.media[i].replace('a=sendrecv\r\n', 'a=inactive\r\n'); - } - this.localSDP.raw = this.localSDP.session + '\r\n' + this.localSDP.media.join(''); - } - } - var sendJingle = function (ssrcs) { - // FIXME why do we generate session-accept in 3 different places ? - var accept = $iq({to: self.peerjid, - type: 'set'}) - .c('jingle', {xmlns: 'urn:xmpp:jingle:1', - action: 'session-accept', - initiator: self.initiator, - responder: self.responder, - sid: self.sid }); - if (config.webrtcIceTcpDisable) { - self.localSDP.removeTcpCandidates = true; - } - if (config.webrtcIceUdpDisable) { - self.localSDP.removeUdpCandidates = true; - } - self.localSDP.toJingle( - accept, - self.initiator == self.me ? 'initiator' : 'responder', - ssrcs); - - SSRCReplacement.processSessionInit(accept); - - self.connection.sendIQ(accept, - function () { - var ack = {}; - ack.source = 'answer'; - $(document).trigger('ack.jingle', [self.sid, ack]); - }, - function (stanza) { - var error = ($(stanza).find('error').length) ? { - code: $(stanza).find('error').attr('code'), - reason: $(stanza).find('error :first')[0].tagName, - }:{}; - error.source = 'answer'; - JingleSessionPC.onJingleError(self.sid, error); - }, - 10000); - }; - sdp.sdp = this.localSDP.raw; - this.peerconnection.setLocalDescription(sdp, - function () { - - //console.log('setLocalDescription success'); - if (self.usetrickle && !self.usepranswer) { - sendJingle(); - } - self.setLocalDescription(); - }, - function (e) { - console.error('setLocalDescription failed', e); - self.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED); - } - ); - var cands = SDPUtil.find_lines(this.localSDP.raw, 'a=candidate:'); - for (var j = 0; j < cands.length; j++) { - var cand = SDPUtil.parse_icecandidate(cands[j]); - if (cand.type == 'srflx') { - this.hadstuncandidate = true; - } else if (cand.type == 'relay') { - this.hadturncandidate = true; - } - } -}; - -JingleSessionPC.prototype.sendTerminate = function (reason, text) { - var self = this, - term = $iq({to: this.peerjid, - type: 'set'}) - .c('jingle', {xmlns: 'urn:xmpp:jingle:1', - action: 'session-terminate', - initiator: this.initiator, - sid: this.sid}) - .c('reason') - .c(reason || 'success'); - - if (text) { - term.up().c('text').t(text); - } - - this.connection.sendIQ(term, - function () { - self.peerconnection.close(); - self.peerconnection = null; - self.terminate(); - var ack = {}; - ack.source = 'terminate'; - $(document).trigger('ack.jingle', [self.sid, ack]); - }, - function (stanza) { - var error = ($(stanza).find('error').length) ? { - code: $(stanza).find('error').attr('code'), - reason: $(stanza).find('error :first')[0].tagName, - }:{}; - $(document).trigger('ack.jingle', [self.sid, error]); - }, - 10000); - if (this.statsinterval !== null) { - window.clearInterval(this.statsinterval); - this.statsinterval = null; - } -}; - -/** - * Handles a Jingle source-add message for this Jingle session. - * @param elem An array of Jingle "content" elements. - */ -JingleSessionPC.prototype.addSource = function (elem) { - - var self = this; - // FIXME: dirty waiting - if (!this.peerconnection.localDescription) { - console.warn("addSource - localDescription not ready yet"); - setTimeout(function() - { - self.addSource(elem); - }, - 200 - ); - return; - } - - console.log('addssrc', new Date().getTime()); - console.log('ice', this.peerconnection.iceConnectionState); - - this.readSsrcInfo(elem); - - var sdp = new SDP(this.peerconnection.remoteDescription.sdp); - var mySdp = new SDP(this.peerconnection.localDescription.sdp); - - $(elem).each(function (idx, content) { - var name = $(content).attr('name'); - var lines = ''; - $(content).find('ssrc-group[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]').each(function() { - var semantics = this.getAttribute('semantics'); - var ssrcs = $(this).find('>source').map(function () { - return this.getAttribute('ssrc'); - }).get(); - - if (ssrcs.length) { - lines += 'a=ssrc-group:' + semantics + ' ' + ssrcs.join(' ') + '\r\n'; - } - }); - var tmp = $(content).find('source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]'); // can handle both >source and >description>source - tmp.each(function () { - var ssrc = $(this).attr('ssrc'); - if(mySdp.containsSSRC(ssrc)){ - /** - * This happens when multiple participants change their streams at the same time and - * ColibriFocus.modifySources have to wait for stable state. In the meantime multiple - * addssrc are scheduled for update IQ. See - */ - console.warn("Got add stream request for my own ssrc: "+ssrc); - return; - } - if (sdp.containsSSRC(ssrc)) { - console.warn("Source-add request for existing SSRC: " + ssrc); - return; - } - $(this).find('>parameter').each(function () { - lines += 'a=ssrc:' + ssrc + ' ' + $(this).attr('name'); - if ($(this).attr('value') && $(this).attr('value').length) - lines += ':' + $(this).attr('value'); - lines += '\r\n'; - }); - }); - sdp.media.forEach(function(media, idx) { - if (!SDPUtil.find_line(media, 'a=mid:' + name)) - return; - sdp.media[idx] += lines; - if (!self.addssrc[idx]) self.addssrc[idx] = ''; - self.addssrc[idx] += lines; - }); - sdp.raw = sdp.session + sdp.media.join(''); - }); - - this.modifySourcesQueue.push(function() { - // When a source is added and if this is FF, a new channel is allocated - // for receiving the added source. We need to diffuse the SSRC of this - // new recvonly channel to the rest of the peers. - console.log('modify sources done'); - - var newSdp = new SDP(self.peerconnection.localDescription.sdp); - console.log("SDPs", mySdp, newSdp); - self.notifyMySSRCUpdate(mySdp, newSdp); - }); -}; - -/** - * Handles a Jingle source-remove message for this Jingle session. - * @param elem An array of Jingle "content" elements. - */ -JingleSessionPC.prototype.removeSource = function (elem) { - - var self = this; - // FIXME: dirty waiting - if (!this.peerconnection.localDescription) { - console.warn("removeSource - localDescription not ready yet"); - setTimeout(function() { - self.removeSource(elem); - }, - 200 - ); - return; - } - - console.log('removessrc', new Date().getTime()); - console.log('ice', this.peerconnection.iceConnectionState); - var sdp = new SDP(this.peerconnection.remoteDescription.sdp); - var mySdp = new SDP(this.peerconnection.localDescription.sdp); - - $(elem).each(function (idx, content) { - var name = $(content).attr('name'); - var lines = ''; - $(content).find('ssrc-group[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]').each(function() { - var semantics = this.getAttribute('semantics'); - var ssrcs = $(this).find('>source').map(function () { - return this.getAttribute('ssrc'); - }).get(); - - if (ssrcs.length) { - lines += 'a=ssrc-group:' + semantics + ' ' + ssrcs.join(' ') + '\r\n'; - } - }); - var tmp = $(content).find('source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]'); // can handle both >source and >description>source - tmp.each(function () { - var ssrc = $(this).attr('ssrc'); - // This should never happen, but can be useful for bug detection - if(mySdp.containsSSRC(ssrc)){ - console.error("Got remove stream request for my own ssrc: "+ssrc); - return; - } - $(this).find('>parameter').each(function () { - lines += 'a=ssrc:' + ssrc + ' ' + $(this).attr('name'); - if ($(this).attr('value') && $(this).attr('value').length) - lines += ':' + $(this).attr('value'); - lines += '\r\n'; - }); - }); - sdp.media.forEach(function(media, idx) { - if (!SDPUtil.find_line(media, 'a=mid:' + name)) - return; - sdp.media[idx] += lines; - if (!self.removessrc[idx]) self.removessrc[idx] = ''; - self.removessrc[idx] += lines; - }); - sdp.raw = sdp.session + sdp.media.join(''); - }); - - this.modifySourcesQueue.push(function() { - // When a source is removed and if this is FF, the recvonly channel that - // receives the remote stream is deactivated . We need to diffuse the - // recvonly SSRC removal to the rest of the peers. - console.log('modify sources done'); - - var newSdp = new SDP(self.peerconnection.localDescription.sdp); - console.log("SDPs", mySdp, newSdp); - self.notifyMySSRCUpdate(mySdp, newSdp); - }); -}; - -JingleSessionPC.prototype._modifySources = function (successCallback, queueCallback) { - var self = this; - - if (this.peerconnection.signalingState == 'closed') return; - if (!(this.addssrc.length || this.removessrc.length || this.pendingop !== null || this.switchstreams)){ - // There is nothing to do since scheduled job might have been executed by another succeeding call - this.setLocalDescription(); - if(successCallback){ - successCallback(); - } - queueCallback(); - return; - } - - // Reset switch streams flag - this.switchstreams = false; - - var sdp = new SDP(this.peerconnection.remoteDescription.sdp); - - // add sources - this.addssrc.forEach(function(lines, idx) { - sdp.media[idx] += lines; - }); - this.addssrc = []; - - // remove sources - this.removessrc.forEach(function(lines, idx) { - lines = lines.split('\r\n'); - lines.pop(); // remove empty last element; - lines.forEach(function(line) { - sdp.media[idx] = sdp.media[idx].replace(line + '\r\n', ''); - }); - }); - this.removessrc = []; - - sdp.raw = sdp.session + sdp.media.join(''); - this.peerconnection.setRemoteDescription(new RTCSessionDescription({type: 'offer', sdp: sdp.raw}), - function() { - - if(self.signalingState == 'closed') { - console.error("createAnswer attempt on closed state"); - queueCallback("createAnswer attempt on closed state"); - return; - } - - self.peerconnection.createAnswer( - function(modifiedAnswer) { - // change video direction, see https://github.com/jitsi/jitmeet/issues/41 - if (self.pendingop !== null) { - var sdp = new SDP(modifiedAnswer.sdp); - if (sdp.media.length > 1) { - switch(self.pendingop) { - case 'mute': - sdp.media[1] = sdp.media[1].replace('a=sendrecv', 'a=recvonly'); - break; - case 'unmute': - sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv'); - break; - } - sdp.raw = sdp.session + sdp.media.join(''); - modifiedAnswer.sdp = sdp.raw; - } - self.pendingop = null; - } - - // FIXME: pushing down an answer while ice connection state - // is still checking is bad... - //console.log(self.peerconnection.iceConnectionState); - - // trying to work around another chrome bug - //modifiedAnswer.sdp = modifiedAnswer.sdp.replace(/a=setup:active/g, 'a=setup:actpass'); - self.peerconnection.setLocalDescription(modifiedAnswer, - function() { - //console.log('modified setLocalDescription ok'); - self.setLocalDescription(); - if(successCallback){ - successCallback(); - } - queueCallback(); - }, - function(error) { - console.error('modified setLocalDescription failed', error); - queueCallback(error); - } - ); - }, - function(error) { - console.error('modified answer failed', error); - queueCallback(error); - } - ); - }, - function(error) { - console.error('modify failed', error); - queueCallback(error); - } - ); -}; - - -/** - * Switches video streams. - * @param newStream new stream that will be used as video of this session. - * @param oldStream old video stream of this session. - * @param successCallback callback executed after successful stream switch. - * @param isAudio whether the streams are audio (if true) or video (if false). - */ -JingleSessionPC.prototype.switchStreams = - function (newStream, oldStream, successCallback, isAudio) { - var self = this; - var sender, newTrack; - var senderKind = isAudio ? 'audio' : 'video'; - // Remember SDP to figure out added/removed SSRCs - var oldSdp = null; - - if (self.peerconnection) { - if (self.peerconnection.localDescription) { - oldSdp = new SDP(self.peerconnection.localDescription.sdp); - } - if (RTCBrowserType.getBrowserType() === - RTCBrowserType.RTC_BROWSER_FIREFOX) { - // On Firefox we don't replace MediaStreams as this messes up the - // m-lines (which can't be removed in Plan Unified) and brings a lot - // of complications. Instead, we use the RTPSender and replace just - // the track. - - // Find the right sender (for audio or video) - self.peerconnection.peerconnection.getSenders().some(function (s) { - if (s.track && s.track.kind === senderKind) { - sender = s; - return true; - } - }); - - if (sender) { - // We assume that our streams have a single track, either audio - // or video. - newTrack = isAudio ? newStream.getAudioTracks()[0] : - newStream.getVideoTracks()[0]; - sender.replaceTrack(newTrack) - .then(function() { - console.log("Replaced a track, isAudio=" + isAudio); - }) - .catch(function(err) { - console.log("Failed to replace a track: " + err); - }); - } else { - console.log("Cannot switch tracks: no RTPSender."); - } - } else { - self.peerconnection.removeStream(oldStream, true); - if (newStream) { - self.peerconnection.addStream(newStream); - } - } - } - - // Conference is not active - if (!oldSdp) { - successCallback(); - return; - } - - self.switchstreams = true; - self.modifySourcesQueue.push(function() { - console.log('modify sources done'); - - successCallback(); - - var newSdp = new SDP(self.peerconnection.localDescription.sdp); - console.log("SDPs", oldSdp, newSdp); - self.notifyMySSRCUpdate(oldSdp, newSdp); - }); -}; - -/** - * Figures out added/removed ssrcs and send update IQs. - * @param old_sdp SDP object for old description. - * @param new_sdp SDP object for new description. - */ -JingleSessionPC.prototype.notifyMySSRCUpdate = function (old_sdp, new_sdp) { - - if (!(this.peerconnection.signalingState == 'stable' && - this.peerconnection.iceConnectionState == 'connected')){ - console.log("Too early to send updates"); - return; - } - - // send source-remove IQ. - sdpDiffer = new SDPDiffer(new_sdp, old_sdp); - var remove = $iq({to: this.peerjid, type: 'set'}) - .c('jingle', { - xmlns: 'urn:xmpp:jingle:1', - action: 'source-remove', - initiator: this.initiator, - sid: this.sid - } - ); - var removed = sdpDiffer.toJingle(remove); - - // Let 'source-remove' IQ through the hack and see if we're allowed to send - // it in the current form - if (removed) - remove = SSRCReplacement.processSourceRemove(remove); - - if (removed && remove) { - console.info("Sending source-remove", remove); - this.connection.sendIQ(remove, - function (res) { - console.info('got remove result', res); - }, - function (err) { - console.error('got remove error', err); - } - ); - } else { - console.log('removal not necessary'); - } - - // send source-add IQ. - var sdpDiffer = new SDPDiffer(old_sdp, new_sdp); - var add = $iq({to: this.peerjid, type: 'set'}) - .c('jingle', { - xmlns: 'urn:xmpp:jingle:1', - action: 'source-add', - initiator: this.initiator, - sid: this.sid - } - ); - var added = sdpDiffer.toJingle(add); - - // Let 'source-add' IQ through the hack and see if we're allowed to send - // it in the current form - if (added) - add = SSRCReplacement.processSourceAdd(add); - - if (added && add) { - console.info("Sending source-add", add); - this.connection.sendIQ(add, - function (res) { - console.info('got add result', res); - }, - function (err) { - console.error('got add error', err); - } - ); - } else { - console.log('addition not necessary'); - } -}; - -/** - * Mutes/unmutes the (local) video i.e. enables/disables all video tracks. - * - * @param mute true to mute the (local) video i.e. to disable all video - * tracks; otherwise, false - * @param callback a function to be invoked with mute after all video - * tracks have been enabled/disabled. The function may, optionally, return - * another function which is to be invoked after the whole mute/unmute operation - * has completed successfully. - * @param options an object which specifies optional arguments such as the - * boolean key byUser with default value true which - * specifies whether the method was initiated in response to a user command (in - * contrast to an automatic decision made by the application logic) - */ -JingleSessionPC.prototype.setVideoMute = function (mute, callback, options) { - var byUser; - - if (options) { - byUser = options.byUser; - if (typeof byUser === 'undefined') { - byUser = true; - } - } else { - byUser = true; - } - // The user's command to mute the (local) video takes precedence over any - // automatic decision made by the application logic. - if (byUser) { - this.videoMuteByUser = mute; - } else if (this.videoMuteByUser) { - return; - } - - this.hardMuteVideo(mute); - - var self = this; - var oldSdp = null; - if(self.peerconnection) { - if(self.peerconnection.localDescription) { - oldSdp = new SDP(self.peerconnection.localDescription.sdp); - } - } - - this.modifySourcesQueue.push(function() { - console.log('modify sources done'); - - callback(mute); - - var newSdp = new SDP(self.peerconnection.localDescription.sdp); - console.log("SDPs", oldSdp, newSdp); - self.notifyMySSRCUpdate(oldSdp, newSdp); - }); -}; - -JingleSessionPC.prototype.hardMuteVideo = function (muted) { - this.pendingop = muted ? 'mute' : 'unmute'; -}; - -JingleSessionPC.prototype.sendMute = function (muted, content) { - var info = $iq({to: this.peerjid, - type: 'set'}) - .c('jingle', {xmlns: 'urn:xmpp:jingle:1', - action: 'session-info', - initiator: this.initiator, - sid: this.sid }); - info.c(muted ? 'mute' : 'unmute', {xmlns: 'urn:xmpp:jingle:apps:rtp:info:1'}); - info.attrs({'creator': this.me == this.initiator ? 'creator' : 'responder'}); - if (content) { - info.attrs({'name': content}); - } - this.connection.send(info); -}; - -JingleSessionPC.prototype.sendRinging = function () { - var info = $iq({to: this.peerjid, - type: 'set'}) - .c('jingle', {xmlns: 'urn:xmpp:jingle:1', - action: 'session-info', - initiator: this.initiator, - sid: this.sid }); - info.c('ringing', {xmlns: 'urn:xmpp:jingle:apps:rtp:info:1'}); - this.connection.send(info); -}; - -JingleSessionPC.prototype.getStats = function (interval) { - var self = this; - var recv = {audio: 0, video: 0}; - var lost = {audio: 0, video: 0}; - var lastrecv = {audio: 0, video: 0}; - var lastlost = {audio: 0, video: 0}; - var loss = {audio: 0, video: 0}; - var delta = {audio: 0, video: 0}; - this.statsinterval = window.setInterval(function () { - if (self && self.peerconnection && self.peerconnection.getStats) { - self.peerconnection.getStats(function (stats) { - var results = stats.result(); - // TODO: there are so much statistics you can get from this.. - for (var i = 0; i < results.length; ++i) { - if (results[i].type == 'ssrc') { - var packetsrecv = results[i].stat('packetsReceived'); - var packetslost = results[i].stat('packetsLost'); - if (packetsrecv && packetslost) { - packetsrecv = parseInt(packetsrecv, 10); - packetslost = parseInt(packetslost, 10); - - if (results[i].stat('googFrameRateReceived')) { - lastlost.video = lost.video; - lastrecv.video = recv.video; - recv.video = packetsrecv; - lost.video = packetslost; - } else { - lastlost.audio = lost.audio; - lastrecv.audio = recv.audio; - recv.audio = packetsrecv; - lost.audio = packetslost; - } - } - } - } - delta.audio = recv.audio - lastrecv.audio; - delta.video = recv.video - lastrecv.video; - loss.audio = (delta.audio > 0) ? Math.ceil(100 * (lost.audio - lastlost.audio) / delta.audio) : 0; - loss.video = (delta.video > 0) ? Math.ceil(100 * (lost.video - lastlost.video) / delta.video) : 0; - $(document).trigger('packetloss.jingle', [self.sid, loss]); - }); - } - }, interval || 3000); - return this.statsinterval; -}; - -JingleSessionPC.onJingleError = function (session, error) -{ - console.error("Jingle error", error); -}; - -JingleSessionPC.onJingleFatalError = function (session, error) -{ - this.service.sessionTerminated = true; - this.connection.emuc.doLeave(); - this.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED); - this.eventEmitter.emit(XMPPEvents.JINGLE_FATAL_ERROR, session, error); -}; - -JingleSessionPC.prototype.setLocalDescription = function () { - var self = this; - var newssrcs = []; - var session = transform.parse(this.peerconnection.localDescription.sdp); - var i; - session.media.forEach(function (media) { - - if (media.ssrcs && media.ssrcs.length > 0) { - // TODO(gp) maybe exclude FID streams? - media.ssrcs.forEach(function (ssrc) { - if (ssrc.attribute !== 'cname') { - return; - } - newssrcs.push({ - 'ssrc': ssrc.id, - 'type': media.type - }); - }); - } - else if(self.localStreamsSSRC && self.localStreamsSSRC[media.type]) - { - newssrcs.push({ - 'ssrc': self.localStreamsSSRC[media.type], - 'type': media.type - }); - } - - }); - - console.log('new ssrcs', newssrcs); - - // Bind us as local SSRCs owner - if (newssrcs.length > 0) { - - if (config.advertiseSSRCsInPresence) { - // This is only for backward compatibility with clients which - // don't support getting sources from Jingle (i.e. jirecon). - this.connection.emuc.clearPresenceMedia(); - } - - for (i = 0; i < newssrcs.length; i++) { - var ssrc = newssrcs[i].ssrc; - var myJid = self.connection.emuc.myroomjid; - self.ssrcOwners[ssrc] = myJid; - - if (config.advertiseSSRCsInPresence) { - // This is only for backward compatibility with clients which - // don't support getting sources from Jingle (i.e. jirecon). - this.connection.emuc.addMediaToPresence( - i+1, newssrcs[i].type, ssrc, newssrcs[i].direction); - } - } - - if (config.advertiseSSRCsInPresence) { - this.connection.emuc.sendPresence(); - } - } -}; - -/** - * Handles 'onaddstream' events from the RTCPeerConnection. - * @param event the 'onaddstream' event. - */ -JingleSessionPC.prototype.remoteStreamAdded = function (event) { - var self = this; - var ssrc; - var ssrclines; - var streamId = APP.RTC.getStreamID(event.stream); - - // look up an associated JID for a stream id - if (!streamId) { - console.error("No stream ID for", event.stream); - } else if (streamId.indexOf('mixedmslabel') === -1) { - // look only at a=ssrc: and _not_ at a=ssrc-group: lines - - ssrclines = SDPUtil.find_lines( - this.peerconnection.remoteDescription.sdp, - 'a=ssrc:'); - ssrclines = ssrclines.filter(function (line) { - // NOTE(gp) previously we filtered on the mslabel, but that property - // is not always present. - // return line.indexOf('mslabel:' + event.stream.label) !== -1; - - if (RTCBrowserType.isTemasysPluginUsed()) { - return ((line.indexOf('mslabel:' + streamId) !== -1)); - } else { - return ((line.indexOf('msid:' + streamId) !== -1)); - } - }); - if (ssrclines.length) { - ssrc = ssrclines[0].substring(7).split(' ')[0]; - - if (!self.ssrcOwners[ssrc]) { - console.error("No SSRC owner known for: " + ssrc); - return; - } - event.peerjid = self.ssrcOwners[ssrc]; - console.log('Adding remote stream, SSRC ' + ssrc + - ', associated jid ' + event.peerjid); - } else { - console.error("No SSRC lines for ", streamId); - } - } - - APP.RTC.createRemoteStream(event, ssrc); -}; - -module.exports = JingleSessionPC; diff --git a/modules/xmpp/LocalSSRCReplacement.js b/modules/xmpp/LocalSSRCReplacement.js deleted file mode 100644 index 0b8e65cff..000000000 --- a/modules/xmpp/LocalSSRCReplacement.js +++ /dev/null @@ -1,268 +0,0 @@ -/* global $ */ - -/* - Here we do modifications of local video SSRCs. There are 2 situations we have - to handle: - - 1. We generate SSRC for local recvonly video stream. This is the case when we - have no local camera and it is not generated automatically, but SSRC=1 is - used implicitly. If that happens RTCP packets will be dropped by the JVB - and we won't be able to request video key frames correctly. - - 2. A hack to re-use SSRC of the first video stream for any new stream created - in future. It turned out that Chrome may keep on using the SSRC of removed - video stream in RTCP even though a new one has been created. So we just - want to avoid that by re-using it. Jingle 'source-remove'/'source-add' - notifications are blocked once first video SSRC is advertised to the focus. - - What this hack does: - - 1. Stores the SSRC of the first video stream created by - a) scanning Jingle session-accept/session-invite for existing video SSRC - b) watching for 'source-add' for new video stream if it has not been - created in step a) - 2. Exposes method 'mungeLocalVideoSSRC' which replaces any new video SSRC with - the stored one. It is called by 'TracablePeerConnection' before local SDP is - returned to the other parts of the application. - 3. Scans 'source-remove'/'source-add' notifications for stored video SSRC and - blocks those notifications. This makes Jicofo and all participants think - that it exists all the time even if the video stream has been removed or - replaced locally. Thanks to that there is no additional signaling activity - on video mute or when switching to the desktop stream. - */ - -var SDP = require('./SDP'); -var RTCBrowserType = require('../RTC/RTCBrowserType'); - -/** - * The hack is enabled on all browsers except FF by default - * FIXME finish the hack once removeStream method is implemented in FF - * @type {boolean} - */ -var isEnabled = !RTCBrowserType.isFirefox(); - -/** - * Stored SSRC of local video stream. - */ -var localVideoSSRC; - -/** - * SSRC used for recvonly video stream when we have no local camera. - * This is in order to tell Chrome what SSRC should be used in RTCP requests - * instead of 1. - */ -var localRecvOnlySSRC; - -/** - * cname for localRecvOnlySSRC - */ -var localRecvOnlyCName; - -/** - * Method removes element which describes localVideoSSRC - * from given Jingle IQ. - * @param modifyIq 'source-add' or 'source-remove' Jingle IQ. - * @param actionName display name of the action which will be printed in log - * messages. - * @returns {*} modified Jingle IQ, so that it does not contain element - * corresponding to localVideoSSRC or null if no - * other SSRCs left to be signaled after removing it. - */ -var filterOutSource = function (modifyIq, actionName) { - var modifyIqTree = $(modifyIq.tree()); - - if (!localVideoSSRC) - return modifyIqTree[0]; - - var videoSSRC = modifyIqTree.find( - '>jingle>content[name="video"]' + - '>description>source[ssrc="' + localVideoSSRC + '"]'); - - if (!videoSSRC.length) { - return modifyIqTree[0]; - } - - console.info( - 'Blocking ' + actionName + ' for local video SSRC: ' + localVideoSSRC); - - videoSSRC.remove(); - - // Check if any sources still left to be added/removed - if (modifyIqTree.find('>jingle>content>description>source').length) { - return modifyIqTree[0]; - } else { - return null; - } -}; - -/** - * Scans given Jingle IQ for video SSRC and stores it. - * @param jingleIq the Jingle IQ to be scanned for video SSRC. - */ -var storeLocalVideoSSRC = function (jingleIq) { - var videoSSRCs = - $(jingleIq.tree()) - .find('>jingle>content[name="video"]>description>source'); - - videoSSRCs.each(function (idx, ssrcElem) { - if (localVideoSSRC) - return; - // We consider SSRC real only if it has msid attribute - // recvonly streams in FF do not have it as well as local SSRCs - // we generate for recvonly streams in Chrome - var ssrSel = $(ssrcElem); - var msid = ssrSel.find('>parameter[name="msid"]'); - if (msid.length) { - var ssrcVal = ssrSel.attr('ssrc'); - if (ssrcVal) { - localVideoSSRC = ssrcVal; - console.info('Stored local video SSRC' + - ' for future re-use: ' + localVideoSSRC); - } - } - }); -}; - -/** - * Generates new SSRC for local video recvonly stream. - * FIXME what about eventual SSRC collision ? - */ -function generateRecvonlySSRC() { - // - localRecvOnlySSRC = - Math.random().toString(10).substring(2, 11); - localRecvOnlyCName = - Math.random().toString(36).substring(2); - console.info( - "Generated local recvonly SSRC: " + localRecvOnlySSRC + - ", cname: " + localRecvOnlyCName); -} - -var LocalSSRCReplacement = { - /** - * Method must be called before 'session-initiate' or 'session-invite' is - * sent. Scans the IQ for local video SSRC and stores it if detected. - * - * @param sessionInit our 'session-initiate' or 'session-accept' Jingle IQ - * which will be scanned for local video SSRC. - */ - processSessionInit: function (sessionInit) { - if (!isEnabled) - return; - - if (localVideoSSRC) { - console.error("Local SSRC stored already: " + localVideoSSRC); - return; - } - storeLocalVideoSSRC(sessionInit); - }, - /** - * If we have local video SSRC stored searched given - * localDescription for video SSRC and makes sure it is replaced - * with the stored one. - * @param localDescription local description object that will have local - * video SSRC replaced with the stored one - * @returns modified localDescription object. - */ - mungeLocalVideoSSRC: function (localDescription) { - if (!isEnabled) - return localDescription; - - if (!localDescription) { - console.warn("localDescription is null or undefined"); - return localDescription; - } - - // IF we have local video SSRC stored make sure it is replaced - // with old SSRC - if (localVideoSSRC) { - var newSdp = new SDP(localDescription.sdp); - if (newSdp.media[1].indexOf("a=ssrc:") !== -1 && - !newSdp.containsSSRC(localVideoSSRC)) { - // Get new video SSRC - var map = newSdp.getMediaSsrcMap(); - var videoPart = map[1]; - var videoSSRCs = videoPart.ssrcs; - var newSSRC = Object.keys(videoSSRCs)[0]; - - console.info( - "Replacing new video SSRC: " + newSSRC + - " with " + localVideoSSRC); - - localDescription.sdp = - newSdp.raw.replace( - new RegExp('a=ssrc:' + newSSRC, 'g'), - 'a=ssrc:' + localVideoSSRC); - } - } else { - // Make sure we have any SSRC for recvonly video stream - var sdp = new SDP(localDescription.sdp); - - if (sdp.media[1] && sdp.media[1].indexOf('a=ssrc:') === -1 && - sdp.media[1].indexOf('a=recvonly') !== -1) { - - if (!localRecvOnlySSRC) { - generateRecvonlySSRC(); - } - - console.info('No SSRC in video recvonly stream' + - ' - adding SSRC: ' + localRecvOnlySSRC); - - sdp.media[1] += 'a=ssrc:' + localRecvOnlySSRC + - ' cname:' + localRecvOnlyCName + '\r\n'; - - localDescription.sdp = sdp.session + sdp.media.join(''); - } - } - return localDescription; - }, - /** - * Method must be called before 'source-add' notification is sent. In case - * we have local video SSRC advertised already it will be removed from the - * notification. If no other SSRCs are described by given IQ null will be - * returned which means that there is no point in sending the notification. - * @param sourceAdd 'source-add' Jingle IQ to be processed - * @returns modified 'source-add' IQ which can be sent to the focus or - * null if no notification shall be sent. It is no longer - * a Strophe IQ Builder instance, but DOM element tree. - */ - processSourceAdd: function (sourceAdd) { - if (!isEnabled) - return sourceAdd; - - if (!localVideoSSRC) { - // Store local SSRC if available - storeLocalVideoSSRC(sourceAdd); - return sourceAdd; - } else { - return filterOutSource(sourceAdd, 'source-add'); - } - }, - /** - * Method must be called before 'source-remove' notification is sent. - * Removes local video SSRC from the notification. If there are no other - * SSRCs described in the given IQ null will be returned which - * means that there is no point in sending the notification. - * @param sourceRemove 'source-remove' Jingle IQ to be processed - * @returns modified 'source-remove' IQ which can be sent to the focus or - * null if no notification shall be sent. It is no longer - * a Strophe IQ Builder instance, but DOM element tree. - */ - processSourceRemove: function (sourceRemove) { - if (!isEnabled) - return sourceRemove; - - return filterOutSource(sourceRemove, 'source-remove'); - }, - - /** - * Turns the hack on or off - * @param enabled true to enable the hack or false - * to disable it - */ - setEnabled: function (enabled) { - isEnabled = enabled; - } -}; - -module.exports = LocalSSRCReplacement; diff --git a/modules/xmpp/SDP.js b/modules/xmpp/SDP.js deleted file mode 100644 index bacbe77ed..000000000 --- a/modules/xmpp/SDP.js +++ /dev/null @@ -1,645 +0,0 @@ -/* jshint -W101 */ -/* jshint -W117 */ -var SDPUtil = require("./SDPUtil"); - -// SDP STUFF -function SDP(sdp) { - /** - * Whether or not to remove TCP ice candidates when translating from/to jingle. - * @type {boolean} - */ - this.removeTcpCandidates = false; - - /** - * Whether or not to remove UDP ice candidates when translating from/to jingle. - * @type {boolean} - */ - this.removeUdpCandidates = false; - - this.media = sdp.split('\r\nm='); - for (var i = 1; i < this.media.length; i++) { - this.media[i] = 'm=' + this.media[i]; - if (i != this.media.length - 1) { - this.media[i] += '\r\n'; - } - } - this.session = this.media.shift() + '\r\n'; - this.raw = this.session + this.media.join(''); -} - -/** - * Returns map of MediaChannel mapped per channel idx. - */ -SDP.prototype.getMediaSsrcMap = function() { - var self = this; - var media_ssrcs = {}; - var tmp; - for (var mediaindex = 0; mediaindex < self.media.length; mediaindex++) { - tmp = SDPUtil.find_lines(self.media[mediaindex], 'a=ssrc:'); - var mid = SDPUtil.parse_mid(SDPUtil.find_line(self.media[mediaindex], 'a=mid:')); - var media = { - mediaindex: mediaindex, - mid: mid, - ssrcs: {}, - ssrcGroups: [] - }; - media_ssrcs[mediaindex] = media; - tmp.forEach(function (line) { - var linessrc = line.substring(7).split(' ')[0]; - // allocate new ChannelSsrc - if(!media.ssrcs[linessrc]) { - media.ssrcs[linessrc] = { - ssrc: linessrc, - lines: [] - }; - } - media.ssrcs[linessrc].lines.push(line); - }); - tmp = SDPUtil.find_lines(self.media[mediaindex], 'a=ssrc-group:'); - tmp.forEach(function(line){ - var idx = line.indexOf(' '); - var semantics = line.substr(0, idx).substr(13); - var ssrcs = line.substr(14 + semantics.length).split(' '); - if (ssrcs.length) { - media.ssrcGroups.push({ - semantics: semantics, - ssrcs: ssrcs - }); - } - }); - } - return media_ssrcs; -}; -/** - * Returns true if this SDP contains given SSRC. - * @param ssrc the ssrc to check. - * @returns {boolean} true if this SDP contains given SSRC. - */ -SDP.prototype.containsSSRC = function(ssrc) { - var medias = this.getMediaSsrcMap(); - Object.keys(medias).forEach(function(mediaindex){ - var media = medias[mediaindex]; - //console.log("Check", channel, ssrc); - if(Object.keys(media.ssrcs).indexOf(ssrc) != -1){ - return true; - } - }); - return false; -}; - -// remove iSAC and CN from SDP -SDP.prototype.mangle = function () { - var i, j, mline, lines, rtpmap, newdesc; - for (i = 0; i < this.media.length; i++) { - lines = this.media[i].split('\r\n'); - lines.pop(); // remove empty last element - mline = SDPUtil.parse_mline(lines.shift()); - if (mline.media != 'audio') - continue; - newdesc = ''; - mline.fmt.length = 0; - for (j = 0; j < lines.length; j++) { - if (lines[j].substr(0, 9) == 'a=rtpmap:') { - rtpmap = SDPUtil.parse_rtpmap(lines[j]); - if (rtpmap.name == 'CN' || rtpmap.name == 'ISAC') - continue; - mline.fmt.push(rtpmap.id); - newdesc += lines[j] + '\r\n'; - } else { - newdesc += lines[j] + '\r\n'; - } - } - this.media[i] = SDPUtil.build_mline(mline) + '\r\n'; - this.media[i] += newdesc; - } - this.raw = this.session + this.media.join(''); -}; - -// remove lines matching prefix from session section -SDP.prototype.removeSessionLines = function(prefix) { - var self = this; - var lines = SDPUtil.find_lines(this.session, prefix); - lines.forEach(function(line) { - self.session = self.session.replace(line + '\r\n', ''); - }); - this.raw = this.session + this.media.join(''); - return lines; -}; - -// remove lines matching prefix from a media section specified by mediaindex -// TODO: non-numeric mediaindex could match mid -SDP.prototype.removeMediaLines = function(mediaindex, prefix) { - var self = this; - var lines = SDPUtil.find_lines(this.media[mediaindex], prefix); - lines.forEach(function(line) { - self.media[mediaindex] = self.media[mediaindex].replace(line + '\r\n', ''); - }); - this.raw = this.session + this.media.join(''); - return lines; -}; - -// add content's to a jingle element -SDP.prototype.toJingle = function (elem, thecreator, ssrcs) { -// console.log("SSRC" + ssrcs["audio"] + " - " + ssrcs["video"]); - var i, j, k, mline, ssrc, rtpmap, tmp, lines; - // new bundle plan - if (SDPUtil.find_line(this.session, 'a=group:')) { - lines = SDPUtil.find_lines(this.session, 'a=group:'); - for (i = 0; i < lines.length; i++) { - tmp = lines[i].split(' '); - var semantics = tmp.shift().substr(8); - elem.c('group', {xmlns: 'urn:xmpp:jingle:apps:grouping:0', semantics:semantics}); - for (j = 0; j < tmp.length; j++) { - elem.c('content', {name: tmp[j]}).up(); - } - elem.up(); - } - } - for (i = 0; i < this.media.length; i++) { - mline = SDPUtil.parse_mline(this.media[i].split('\r\n')[0]); - if (!(mline.media === 'audio' || - mline.media === 'video' || - mline.media === 'application')) - { - continue; - } - if (SDPUtil.find_line(this.media[i], 'a=ssrc:')) { - ssrc = SDPUtil.find_line(this.media[i], 'a=ssrc:').substring(7).split(' ')[0]; // take the first - } else { - if(ssrcs && ssrcs[mline.media]) { - ssrc = ssrcs[mline.media]; - } else { - ssrc = false; - } - } - - elem.c('content', {creator: thecreator, name: mline.media}); - if (SDPUtil.find_line(this.media[i], 'a=mid:')) { - // prefer identifier from a=mid if present - var mid = SDPUtil.parse_mid(SDPUtil.find_line(this.media[i], 'a=mid:')); - elem.attrs({ name: mid }); - } - - if (SDPUtil.find_line(this.media[i], 'a=rtpmap:').length) { - elem.c('description', - {xmlns: 'urn:xmpp:jingle:apps:rtp:1', - media: mline.media }); - if (ssrc) { - elem.attrs({ssrc: ssrc}); - } - for (j = 0; j < mline.fmt.length; j++) { - rtpmap = SDPUtil.find_line(this.media[i], 'a=rtpmap:' + mline.fmt[j]); - elem.c('payload-type', SDPUtil.parse_rtpmap(rtpmap)); - // put any 'a=fmtp:' + mline.fmt[j] lines into - if (SDPUtil.find_line(this.media[i], 'a=fmtp:' + mline.fmt[j])) { - tmp = SDPUtil.parse_fmtp(SDPUtil.find_line(this.media[i], 'a=fmtp:' + mline.fmt[j])); - for (k = 0; k < tmp.length; k++) { - elem.c('parameter', tmp[k]).up(); - } - } - this.rtcpFbToJingle(i, elem, mline.fmt[j]); // XEP-0293 -- map a=rtcp-fb - - elem.up(); - } - if (SDPUtil.find_line(this.media[i], 'a=crypto:', this.session)) { - elem.c('encryption', {required: 1}); - var crypto = SDPUtil.find_lines(this.media[i], 'a=crypto:', this.session); - crypto.forEach(function(line) { - elem.c('crypto', SDPUtil.parse_crypto(line)).up(); - }); - elem.up(); // end of encryption - } - - if (ssrc) { - // new style mapping - elem.c('source', { ssrc: ssrc, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' }); - // FIXME: group by ssrc and support multiple different ssrcs - var ssrclines = SDPUtil.find_lines(this.media[i], 'a=ssrc:'); - if(ssrclines.length > 0) { - ssrclines.forEach(function (line) { - var idx = line.indexOf(' '); - var linessrc = line.substr(0, idx).substr(7); - if (linessrc != ssrc) { - elem.up(); - ssrc = linessrc; - elem.c('source', { ssrc: ssrc, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' }); - } - var kv = line.substr(idx + 1); - elem.c('parameter'); - if (kv.indexOf(':') == -1) { - elem.attrs({ name: kv }); - } else { - var k = kv.split(':', 2)[0]; - elem.attrs({ name: k }); - - var v = kv.split(':', 2)[1]; - v = SDPUtil.filter_special_chars(v); - elem.attrs({ value: v }); - } - elem.up(); - }); - } else { - elem.up(); - elem.c('source', { ssrc: ssrc, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' }); - elem.c('parameter'); - elem.attrs({name: "cname", value:Math.random().toString(36).substring(7)}); - elem.up(); - var msid = null; - if(mline.media == "audio") { - msid = APP.RTC.localAudio.getId(); - } else { - msid = APP.RTC.localVideo.getId(); - } - if(msid !== null) { - msid = SDPUtil.filter_special_chars(msid); - elem.c('parameter'); - elem.attrs({name: "msid", value:msid}); - elem.up(); - elem.c('parameter'); - elem.attrs({name: "mslabel", value:msid}); - elem.up(); - elem.c('parameter'); - elem.attrs({name: "label", value:msid}); - elem.up(); - } - } - elem.up(); - - // XEP-0339 handle ssrc-group attributes - var ssrc_group_lines = SDPUtil.find_lines(this.media[i], 'a=ssrc-group:'); - ssrc_group_lines.forEach(function(line) { - var idx = line.indexOf(' '); - var semantics = line.substr(0, idx).substr(13); - var ssrcs = line.substr(14 + semantics.length).split(' '); - if (ssrcs.length) { - elem.c('ssrc-group', { semantics: semantics, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' }); - ssrcs.forEach(function(ssrc) { - elem.c('source', { ssrc: ssrc }) - .up(); - }); - elem.up(); - } - }); - } - - if (SDPUtil.find_line(this.media[i], 'a=rtcp-mux')) { - elem.c('rtcp-mux').up(); - } - - // XEP-0293 -- map a=rtcp-fb:* - this.rtcpFbToJingle(i, elem, '*'); - - // XEP-0294 - if (SDPUtil.find_line(this.media[i], 'a=extmap:')) { - lines = SDPUtil.find_lines(this.media[i], 'a=extmap:'); - for (j = 0; j < lines.length; j++) { - tmp = SDPUtil.parse_extmap(lines[j]); - elem.c('rtp-hdrext', { xmlns: 'urn:xmpp:jingle:apps:rtp:rtp-hdrext:0', - uri: tmp.uri, - id: tmp.value }); - if (tmp.hasOwnProperty('direction')) { - switch (tmp.direction) { - case 'sendonly': - elem.attrs({senders: 'responder'}); - break; - case 'recvonly': - elem.attrs({senders: 'initiator'}); - break; - case 'sendrecv': - elem.attrs({senders: 'both'}); - break; - case 'inactive': - elem.attrs({senders: 'none'}); - break; - } - } - // TODO: handle params - elem.up(); - } - } - elem.up(); // end of description - } - - // map ice-ufrag/pwd, dtls fingerprint, candidates - this.transportToJingle(i, elem); - - if (SDPUtil.find_line(this.media[i], 'a=sendrecv', this.session)) { - elem.attrs({senders: 'both'}); - } else if (SDPUtil.find_line(this.media[i], 'a=sendonly', this.session)) { - elem.attrs({senders: 'initiator'}); - } else if (SDPUtil.find_line(this.media[i], 'a=recvonly', this.session)) { - elem.attrs({senders: 'responder'}); - } else if (SDPUtil.find_line(this.media[i], 'a=inactive', this.session)) { - elem.attrs({senders: 'none'}); - } - if (mline.port == '0') { - // estos hack to reject an m-line - elem.attrs({senders: 'rejected'}); - } - elem.up(); // end of content - } - elem.up(); - return elem; -}; - -SDP.prototype.transportToJingle = function (mediaindex, elem) { - var tmp, sctpmap, sctpAttrs, fingerprints; - var self = this; - elem.c('transport'); - - // XEP-0343 DTLS/SCTP - if (SDPUtil.find_line(this.media[mediaindex], 'a=sctpmap:').length) - { - sctpmap = SDPUtil.find_line( - this.media[mediaindex], 'a=sctpmap:', self.session); - if (sctpmap) - { - sctpAttrs = SDPUtil.parse_sctpmap(sctpmap); - elem.c('sctpmap', - { - xmlns: 'urn:xmpp:jingle:transports:dtls-sctp:1', - number: sctpAttrs[0], /* SCTP port */ - protocol: sctpAttrs[1] /* protocol */ - }); - // Optional stream count attribute - if (sctpAttrs.length > 2) - elem.attrs({ streams: sctpAttrs[2]}); - elem.up(); - } - } - // XEP-0320 - fingerprints = SDPUtil.find_lines(this.media[mediaindex], 'a=fingerprint:', this.session); - fingerprints.forEach(function(line) { - tmp = SDPUtil.parse_fingerprint(line); - tmp.xmlns = 'urn:xmpp:jingle:apps:dtls:0'; - elem.c('fingerprint').t(tmp.fingerprint); - delete tmp.fingerprint; - line = SDPUtil.find_line(self.media[mediaindex], 'a=setup:', self.session); - if (line) { - tmp.setup = line.substr(8); - } - elem.attrs(tmp); - elem.up(); // end of fingerprint - }); - tmp = SDPUtil.iceparams(this.media[mediaindex], this.session); - if (tmp) { - tmp.xmlns = 'urn:xmpp:jingle:transports:ice-udp:1'; - elem.attrs(tmp); - // XEP-0176 - if (SDPUtil.find_line(this.media[mediaindex], 'a=candidate:', this.session)) { // add any a=candidate lines - var lines = SDPUtil.find_lines(this.media[mediaindex], 'a=candidate:', this.session); - lines.forEach(function (line) { - var candidate = SDPUtil.candidateToJingle(line); - var protocol = (candidate && - typeof candidate.protocol === 'string') - ? candidate.protocol.toLowerCase() : ''; - if ((self.removeTcpCandidates && protocol === 'tcp') || - (self.removeUdpCandidates && protocol === 'udp')) { - return; - } - elem.c('candidate', candidate).up(); - }); - } - } - elem.up(); // end of transport -}; - -SDP.prototype.rtcpFbToJingle = function (mediaindex, elem, payloadtype) { // XEP-0293 - var lines = SDPUtil.find_lines(this.media[mediaindex], 'a=rtcp-fb:' + payloadtype); - lines.forEach(function (line) { - var tmp = SDPUtil.parse_rtcpfb(line); - if (tmp.type == 'trr-int') { - elem.c('rtcp-fb-trr-int', {xmlns: 'urn:xmpp:jingle:apps:rtp:rtcp-fb:0', value: tmp.params[0]}); - elem.up(); - } else { - elem.c('rtcp-fb', {xmlns: 'urn:xmpp:jingle:apps:rtp:rtcp-fb:0', type: tmp.type}); - if (tmp.params.length > 0) { - elem.attrs({'subtype': tmp.params[0]}); - } - elem.up(); - } - }); -}; - -SDP.prototype.rtcpFbFromJingle = function (elem, payloadtype) { // XEP-0293 - var media = ''; - var tmp = elem.find('>rtcp-fb-trr-int[xmlns="urn:xmpp:jingle:apps:rtp:rtcp-fb:0"]'); - if (tmp.length) { - media += 'a=rtcp-fb:' + '*' + ' ' + 'trr-int' + ' '; - if (tmp.attr('value')) { - media += tmp.attr('value'); - } else { - media += '0'; - } - media += '\r\n'; - } - tmp = elem.find('>rtcp-fb[xmlns="urn:xmpp:jingle:apps:rtp:rtcp-fb:0"]'); - tmp.each(function () { - media += 'a=rtcp-fb:' + payloadtype + ' ' + $(this).attr('type'); - if ($(this).attr('subtype')) { - media += ' ' + $(this).attr('subtype'); - } - media += '\r\n'; - }); - return media; -}; - -// construct an SDP from a jingle stanza -SDP.prototype.fromJingle = function (jingle) { - var self = this; - this.raw = 'v=0\r\n' + - 'o=- 1923518516 2 IN IP4 0.0.0.0\r\n' +// FIXME - 's=-\r\n' + - 't=0 0\r\n'; - // http://tools.ietf.org/html/draft-ietf-mmusic-sdp-bundle-negotiation-04#section-8 - if ($(jingle).find('>group[xmlns="urn:xmpp:jingle:apps:grouping:0"]').length) { - $(jingle).find('>group[xmlns="urn:xmpp:jingle:apps:grouping:0"]').each(function (idx, group) { - var contents = $(group).find('>content').map(function (idx, content) { - return content.getAttribute('name'); - }).get(); - if (contents.length > 0) { - self.raw += 'a=group:' + (group.getAttribute('semantics') || group.getAttribute('type')) + ' ' + contents.join(' ') + '\r\n'; - } - }); - } - - this.session = this.raw; - jingle.find('>content').each(function () { - var m = self.jingle2media($(this)); - self.media.push(m); - }); - - // reconstruct msid-semantic -- apparently not necessary - /* - var msid = SDPUtil.parse_ssrc(this.raw); - if (msid.hasOwnProperty('mslabel')) { - this.session += "a=msid-semantic: WMS " + msid.mslabel + "\r\n"; - } - */ - - this.raw = this.session + this.media.join(''); -}; - -// translate a jingle content element into an an SDP media part -SDP.prototype.jingle2media = function (content) { - var media = '', - desc = content.find('description'), - ssrc = desc.attr('ssrc'), - self = this, - tmp; - var sctp = content.find( - '>transport>sctpmap[xmlns="urn:xmpp:jingle:transports:dtls-sctp:1"]'); - - tmp = { media: desc.attr('media') }; - tmp.port = '1'; - if (content.attr('senders') == 'rejected') { - // estos hack to reject an m-line. - tmp.port = '0'; - } - if (content.find('>transport>fingerprint').length || desc.find('encryption').length) { - if (sctp.length) - tmp.proto = 'DTLS/SCTP'; - else - tmp.proto = 'RTP/SAVPF'; - } else { - tmp.proto = 'RTP/AVPF'; - } - if (!sctp.length) { - tmp.fmt = desc.find('payload-type').map( - function () { return this.getAttribute('id'); }).get(); - media += SDPUtil.build_mline(tmp) + '\r\n'; - } else { - media += 'm=application 1 DTLS/SCTP ' + sctp.attr('number') + '\r\n'; - media += 'a=sctpmap:' + sctp.attr('number') + - ' ' + sctp.attr('protocol'); - - var streamCount = sctp.attr('streams'); - if (streamCount) - media += ' ' + streamCount + '\r\n'; - else - media += '\r\n'; - } - - media += 'c=IN IP4 0.0.0.0\r\n'; - if (!sctp.length) - media += 'a=rtcp:1 IN IP4 0.0.0.0\r\n'; - tmp = content.find('>transport[xmlns="urn:xmpp:jingle:transports:ice-udp:1"]'); - if (tmp.length) { - if (tmp.attr('ufrag')) { - media += SDPUtil.build_iceufrag(tmp.attr('ufrag')) + '\r\n'; - } - if (tmp.attr('pwd')) { - media += SDPUtil.build_icepwd(tmp.attr('pwd')) + '\r\n'; - } - tmp.find('>fingerprint').each(function () { - // FIXME: check namespace at some point - media += 'a=fingerprint:' + this.getAttribute('hash'); - media += ' ' + $(this).text(); - media += '\r\n'; - if (this.getAttribute('setup')) { - media += 'a=setup:' + this.getAttribute('setup') + '\r\n'; - } - }); - } - switch (content.attr('senders')) { - case 'initiator': - media += 'a=sendonly\r\n'; - break; - case 'responder': - media += 'a=recvonly\r\n'; - break; - case 'none': - media += 'a=inactive\r\n'; - break; - case 'both': - media += 'a=sendrecv\r\n'; - break; - } - media += 'a=mid:' + content.attr('name') + '\r\n'; - - // - // see http://code.google.com/p/libjingle/issues/detail?id=309 -- no spec though - // and http://mail.jabber.org/pipermail/jingle/2011-December/001761.html - if (desc.find('rtcp-mux').length) { - media += 'a=rtcp-mux\r\n'; - } - - if (desc.find('encryption').length) { - desc.find('encryption>crypto').each(function () { - media += 'a=crypto:' + this.getAttribute('tag'); - media += ' ' + this.getAttribute('crypto-suite'); - media += ' ' + this.getAttribute('key-params'); - if (this.getAttribute('session-params')) { - media += ' ' + this.getAttribute('session-params'); - } - media += '\r\n'; - }); - } - desc.find('payload-type').each(function () { - media += SDPUtil.build_rtpmap(this) + '\r\n'; - if ($(this).find('>parameter').length) { - media += 'a=fmtp:' + this.getAttribute('id') + ' '; - media += $(this).find('parameter').map(function () { - return (this.getAttribute('name') - ? (this.getAttribute('name') + '=') : '') + - this.getAttribute('value'); - }).get().join('; '); - media += '\r\n'; - } - // xep-0293 - media += self.rtcpFbFromJingle($(this), this.getAttribute('id')); - }); - - // xep-0293 - media += self.rtcpFbFromJingle(desc, '*'); - - // xep-0294 - tmp = desc.find('>rtp-hdrext[xmlns="urn:xmpp:jingle:apps:rtp:rtp-hdrext:0"]'); - tmp.each(function () { - media += 'a=extmap:' + this.getAttribute('id') + ' ' + this.getAttribute('uri') + '\r\n'; - }); - - content.find('>transport[xmlns="urn:xmpp:jingle:transports:ice-udp:1"]>candidate').each(function () { - var protocol = this.getAttribute('protocol'); - protocol = (typeof protocol === 'string') ? protocol.toLowerCase(): ''; - - if ((self.removeTcpCandidates && protocol === 'tcp') || - (self.removeUdpCandidates && protocol === 'udp')) { - return; - } - - media += SDPUtil.candidateFromJingle(this); - }); - - // XEP-0339 handle ssrc-group attributes - content.find('description>ssrc-group[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]').each(function() { - var semantics = this.getAttribute('semantics'); - var ssrcs = $(this).find('>source').map(function() { - return this.getAttribute('ssrc'); - }).get(); - - if (ssrcs.length) { - media += 'a=ssrc-group:' + semantics + ' ' + ssrcs.join(' ') + '\r\n'; - } - }); - - tmp = content.find('description>source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]'); - tmp.each(function () { - var ssrc = this.getAttribute('ssrc'); - $(this).find('>parameter').each(function () { - var name = this.getAttribute('name'); - var value = this.getAttribute('value'); - value = SDPUtil.filter_special_chars(value); - media += 'a=ssrc:' + ssrc + ' ' + name; - if (value && value.length) - media += ':' + value; - media += '\r\n'; - }); - }); - - return media; -}; - - -module.exports = SDP; - diff --git a/modules/xmpp/SDPDiffer.js b/modules/xmpp/SDPDiffer.js deleted file mode 100644 index 38d52150a..000000000 --- a/modules/xmpp/SDPDiffer.js +++ /dev/null @@ -1,168 +0,0 @@ -var SDPUtil = require("./SDPUtil"); - -function SDPDiffer(mySDP, otherSDP) -{ - this.mySDP = mySDP; - this.otherSDP = otherSDP; -} - -/** - * Returns map of MediaChannel that contains media contained in - * 'mySDP', but not contained in 'otherSdp'. Mapped by channel idx. - */ -SDPDiffer.prototype.getNewMedia = function() { - - // this could be useful in Array.prototype. - function arrayEquals(array) { - // if the other array is a falsy value, return - if (!array) - return false; - - // compare lengths - can save a lot of time - if (this.length != array.length) - return false; - - for (var i = 0, l=this.length; i < l; i++) { - // Check if we have nested arrays - if (this[i] instanceof Array && array[i] instanceof Array) { - // recurse into the nested arrays - if (!this[i].equals(array[i])) - return false; - } - else if (this[i] != array[i]) { - // Warning - two different object instances will never be - // equal: {x:20} != {x:20} - return false; - } - } - return true; - } - - var myMedias = this.mySDP.getMediaSsrcMap(); - var othersMedias = this.otherSDP.getMediaSsrcMap(); - var newMedia = {}; - Object.keys(othersMedias).forEach(function(othersMediaIdx) { - var myMedia = myMedias[othersMediaIdx]; - var othersMedia = othersMedias[othersMediaIdx]; - if(!myMedia && othersMedia) { - // Add whole channel - newMedia[othersMediaIdx] = othersMedia; - return; - } - // Look for new ssrcs across the channel - Object.keys(othersMedia.ssrcs).forEach(function(ssrc) { - if(Object.keys(myMedia.ssrcs).indexOf(ssrc) === -1) { - // Allocate channel if we've found ssrc that doesn't exist in - // our channel - if(!newMedia[othersMediaIdx]){ - newMedia[othersMediaIdx] = { - mediaindex: othersMedia.mediaindex, - mid: othersMedia.mid, - ssrcs: {}, - ssrcGroups: [] - }; - } - newMedia[othersMediaIdx].ssrcs[ssrc] = othersMedia.ssrcs[ssrc]; - } - }); - - // Look for new ssrc groups across the channels - othersMedia.ssrcGroups.forEach(function(otherSsrcGroup){ - - // try to match the other ssrc-group with an ssrc-group of ours - var matched = false; - for (var i = 0; i < myMedia.ssrcGroups.length; i++) { - var mySsrcGroup = myMedia.ssrcGroups[i]; - if (otherSsrcGroup.semantics == mySsrcGroup.semantics && - arrayEquals.apply(otherSsrcGroup.ssrcs, - [mySsrcGroup.ssrcs])) { - - matched = true; - break; - } - } - - if (!matched) { - // Allocate channel if we've found an ssrc-group that doesn't - // exist in our channel - - if(!newMedia[othersMediaIdx]){ - newMedia[othersMediaIdx] = { - mediaindex: othersMedia.mediaindex, - mid: othersMedia.mid, - ssrcs: {}, - ssrcGroups: [] - }; - } - newMedia[othersMediaIdx].ssrcGroups.push(otherSsrcGroup); - } - }); - }); - return newMedia; -}; - -/** - * TODO: document! - */ -SDPDiffer.prototype.toJingle = function(modify) { - var sdpMediaSsrcs = this.getNewMedia(); - - var modified = false; - Object.keys(sdpMediaSsrcs).forEach(function(mediaindex){ - modified = true; - var media = sdpMediaSsrcs[mediaindex]; - modify.c('content', {name: media.mid}); - - modify.c('description', - {xmlns:'urn:xmpp:jingle:apps:rtp:1', media: media.mid}); - // FIXME: not completely sure this operates on blocks and / or handles - // different ssrcs correctly - // generate sources from lines - Object.keys(media.ssrcs).forEach(function(ssrcNum) { - var mediaSsrc = media.ssrcs[ssrcNum]; - modify.c('source', { xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' }); - modify.attrs({ssrc: mediaSsrc.ssrc}); - // iterate over ssrc lines - mediaSsrc.lines.forEach(function (line) { - var idx = line.indexOf(' '); - var kv = line.substr(idx + 1); - modify.c('parameter'); - if (kv.indexOf(':') == -1) { - modify.attrs({ name: kv }); - } else { - var nv = kv.split(':', 2); - var name = nv[0]; - var value = SDPUtil.filter_special_chars(nv[1]); - modify.attrs({ name: name }); - modify.attrs({ value: value }); - } - modify.up(); // end of parameter - }); - modify.up(); // end of source - }); - - // generate source groups from lines - media.ssrcGroups.forEach(function(ssrcGroup) { - if (ssrcGroup.ssrcs.length) { - - modify.c('ssrc-group', { - semantics: ssrcGroup.semantics, - xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' - }); - - ssrcGroup.ssrcs.forEach(function (ssrc) { - modify.c('source', { ssrc: ssrc }) - .up(); // end of source - }); - modify.up(); // end of ssrc-group - } - }); - - modify.up(); // end of description - modify.up(); // end of content - }); - - return modified; -}; - -module.exports = SDPDiffer; \ No newline at end of file diff --git a/modules/xmpp/SDPUtil.js b/modules/xmpp/SDPUtil.js deleted file mode 100644 index 65cdf3ecf..000000000 --- a/modules/xmpp/SDPUtil.js +++ /dev/null @@ -1,361 +0,0 @@ -/* jshint -W101 */ -var RTCBrowserType = require('../RTC/RTCBrowserType'); - -var SDPUtil = { - filter_special_chars: function (text) { - return text.replace(/[\\\/\{,\}\+]/g, ""); - }, - iceparams: function (mediadesc, sessiondesc) { - var data = null; - if (SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc) && - SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc)) { - data = { - ufrag: SDPUtil.parse_iceufrag(SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc)), - pwd: SDPUtil.parse_icepwd(SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc)) - }; - } - return data; - }, - parse_iceufrag: function (line) { - return line.substring(12); - }, - build_iceufrag: function (frag) { - return 'a=ice-ufrag:' + frag; - }, - parse_icepwd: function (line) { - return line.substring(10); - }, - build_icepwd: function (pwd) { - return 'a=ice-pwd:' + pwd; - }, - parse_mid: function (line) { - return line.substring(6); - }, - parse_mline: function (line) { - var parts = line.substring(2).split(' '), - data = {}; - data.media = parts.shift(); - data.port = parts.shift(); - data.proto = parts.shift(); - if (parts[parts.length - 1] === '') { // trailing whitespace - parts.pop(); - } - data.fmt = parts; - return data; - }, - build_mline: function (mline) { - return 'm=' + mline.media + ' ' + mline.port + ' ' + mline.proto + ' ' + mline.fmt.join(' '); - }, - parse_rtpmap: function (line) { - var parts = line.substring(9).split(' '), - data = {}; - data.id = parts.shift(); - parts = parts[0].split('/'); - data.name = parts.shift(); - data.clockrate = parts.shift(); - data.channels = parts.length ? parts.shift() : '1'; - return data; - }, - /** - * Parses SDP line "a=sctpmap:..." and extracts SCTP port from it. - * @param line eg. "a=sctpmap:5000 webrtc-datachannel" - * @returns [SCTP port number, protocol, streams] - */ - parse_sctpmap: function (line) - { - var parts = line.substring(10).split(' '); - var sctpPort = parts[0]; - var protocol = parts[1]; - // Stream count is optional - var streamCount = parts.length > 2 ? parts[2] : null; - return [sctpPort, protocol, streamCount];// SCTP port - }, - build_rtpmap: function (el) { - var line = 'a=rtpmap:' + el.getAttribute('id') + ' ' + el.getAttribute('name') + '/' + el.getAttribute('clockrate'); - if (el.getAttribute('channels') && el.getAttribute('channels') != '1') { - line += '/' + el.getAttribute('channels'); - } - return line; - }, - parse_crypto: function (line) { - var parts = line.substring(9).split(' '), - data = {}; - data.tag = parts.shift(); - data['crypto-suite'] = parts.shift(); - data['key-params'] = parts.shift(); - if (parts.length) { - data['session-params'] = parts.join(' '); - } - return data; - }, - parse_fingerprint: function (line) { // RFC 4572 - var parts = line.substring(14).split(' '), - data = {}; - data.hash = parts.shift(); - data.fingerprint = parts.shift(); - // TODO assert that fingerprint satisfies 2UHEX *(":" 2UHEX) ? - return data; - }, - parse_fmtp: function (line) { - var parts = line.split(' '), - i, key, value, - data = []; - parts.shift(); - parts = parts.join(' ').split(';'); - for (i = 0; i < parts.length; i++) { - key = parts[i].split('=')[0]; - while (key.length && key[0] == ' ') { - key = key.substring(1); - } - value = parts[i].split('=')[1]; - if (key && value) { - data.push({name: key, value: value}); - } else if (key) { - // rfc 4733 (DTMF) style stuff - data.push({name: '', value: key}); - } - } - return data; - }, - parse_icecandidate: function (line) { - var candidate = {}, - elems = line.split(' '); - candidate.foundation = elems[0].substring(12); - candidate.component = elems[1]; - candidate.protocol = elems[2].toLowerCase(); - candidate.priority = elems[3]; - candidate.ip = elems[4]; - candidate.port = elems[5]; - // elems[6] => "typ" - candidate.type = elems[7]; - candidate.generation = 0; // default value, may be overwritten below - for (var i = 8; i < elems.length; i += 2) { - switch (elems[i]) { - case 'raddr': - candidate['rel-addr'] = elems[i + 1]; - break; - case 'rport': - candidate['rel-port'] = elems[i + 1]; - break; - case 'generation': - candidate.generation = elems[i + 1]; - break; - case 'tcptype': - candidate.tcptype = elems[i + 1]; - break; - default: // TODO - console.log('parse_icecandidate not translating "' + elems[i] + '" = "' + elems[i + 1] + '"'); - } - } - candidate.network = '1'; - candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random - return candidate; - }, - build_icecandidate: function (cand) { - var line = ['a=candidate:' + cand.foundation, cand.component, cand.protocol, cand.priority, cand.ip, cand.port, 'typ', cand.type].join(' '); - line += ' '; - switch (cand.type) { - case 'srflx': - case 'prflx': - case 'relay': - if (cand.hasOwnAttribute('rel-addr') && cand.hasOwnAttribute('rel-port')) { - line += 'raddr'; - line += ' '; - line += cand['rel-addr']; - line += ' '; - line += 'rport'; - line += ' '; - line += cand['rel-port']; - line += ' '; - } - break; - } - if (cand.hasOwnAttribute('tcptype')) { - line += 'tcptype'; - line += ' '; - line += cand.tcptype; - line += ' '; - } - line += 'generation'; - line += ' '; - line += cand.hasOwnAttribute('generation') ? cand.generation : '0'; - return line; - }, - parse_ssrc: function (desc) { - // proprietary mapping of a=ssrc lines - // TODO: see "Jingle RTP Source Description" by Juberti and P. Thatcher on google docs - // and parse according to that - var lines = desc.split('\r\n'), - data = {}; - for (var i = 0; i < lines.length; i++) { - if (lines[i].substring(0, 7) == 'a=ssrc:') { - var idx = lines[i].indexOf(' '); - data[lines[i].substr(idx + 1).split(':', 2)[0]] = lines[i].substr(idx + 1).split(':', 2)[1]; - } - } - return data; - }, - parse_rtcpfb: function (line) { - var parts = line.substr(10).split(' '); - var data = {}; - data.pt = parts.shift(); - data.type = parts.shift(); - data.params = parts; - return data; - }, - parse_extmap: function (line) { - var parts = line.substr(9).split(' '); - var data = {}; - data.value = parts.shift(); - if (data.value.indexOf('/') != -1) { - data.direction = data.value.substr(data.value.indexOf('/') + 1); - data.value = data.value.substr(0, data.value.indexOf('/')); - } else { - data.direction = 'both'; - } - data.uri = parts.shift(); - data.params = parts; - return data; - }, - find_line: function (haystack, needle, sessionpart) { - var lines = haystack.split('\r\n'); - for (var i = 0; i < lines.length; i++) { - if (lines[i].substring(0, needle.length) == needle) { - return lines[i]; - } - } - if (!sessionpart) { - return false; - } - // search session part - lines = sessionpart.split('\r\n'); - for (var j = 0; j < lines.length; j++) { - if (lines[j].substring(0, needle.length) == needle) { - return lines[j]; - } - } - return false; - }, - find_lines: function (haystack, needle, sessionpart) { - var lines = haystack.split('\r\n'), - needles = []; - for (var i = 0; i < lines.length; i++) { - if (lines[i].substring(0, needle.length) == needle) - needles.push(lines[i]); - } - if (needles.length || !sessionpart) { - return needles; - } - // search session part - lines = sessionpart.split('\r\n'); - for (var j = 0; j < lines.length; j++) { - if (lines[j].substring(0, needle.length) == needle) { - needles.push(lines[j]); - } - } - return needles; - }, - candidateToJingle: function (line) { - // a=candidate:2979166662 1 udp 2113937151 192.168.2.100 57698 typ host generation 0 - // - if (line.indexOf('candidate:') === 0) { - line = 'a=' + line; - } else if (line.substring(0, 12) != 'a=candidate:') { - console.log('parseCandidate called with a line that is not a candidate line'); - console.log(line); - return null; - } - if (line.substring(line.length - 2) == '\r\n') // chomp it - line = line.substring(0, line.length - 2); - var candidate = {}, - elems = line.split(' '), - i; - if (elems[6] != 'typ') { - console.log('did not find typ in the right place'); - console.log(line); - return null; - } - candidate.foundation = elems[0].substring(12); - candidate.component = elems[1]; - candidate.protocol = elems[2].toLowerCase(); - candidate.priority = elems[3]; - candidate.ip = elems[4]; - candidate.port = elems[5]; - // elems[6] => "typ" - candidate.type = elems[7]; - - candidate.generation = '0'; // default, may be overwritten below - for (i = 8; i < elems.length; i += 2) { - switch (elems[i]) { - case 'raddr': - candidate['rel-addr'] = elems[i + 1]; - break; - case 'rport': - candidate['rel-port'] = elems[i + 1]; - break; - case 'generation': - candidate.generation = elems[i + 1]; - break; - case 'tcptype': - candidate.tcptype = elems[i + 1]; - break; - default: // TODO - console.log('not translating "' + elems[i] + '" = "' + elems[i + 1] + '"'); - } - } - candidate.network = '1'; - candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random - return candidate; - }, - candidateFromJingle: function (cand) { - var line = 'a=candidate:'; - line += cand.getAttribute('foundation'); - line += ' '; - line += cand.getAttribute('component'); - line += ' '; - - var protocol = cand.getAttribute('protocol'); - // use tcp candidates for FF - if (RTCBrowserType.isFirefox() && protocol.toLowerCase() == 'ssltcp') { - protocol = 'tcp'; - } - - line += ' '; - line += cand.getAttribute('priority'); - line += ' '; - line += cand.getAttribute('ip'); - line += ' '; - line += cand.getAttribute('port'); - line += ' '; - line += 'typ'; - line += ' ' + cand.getAttribute('type'); - line += ' '; - switch (cand.getAttribute('type')) { - case 'srflx': - case 'prflx': - case 'relay': - if (cand.getAttribute('rel-addr') && cand.getAttribute('rel-port')) { - line += 'raddr'; - line += ' '; - line += cand.getAttribute('rel-addr'); - line += ' '; - line += 'rport'; - line += ' '; - line += cand.getAttribute('rel-port'); - line += ' '; - } - break; - } - if (protocol.toLowerCase() == 'tcp') { - line += 'tcptype'; - line += ' '; - line += cand.getAttribute('tcptype'); - line += ' '; - } - line += 'generation'; - line += ' '; - line += cand.getAttribute('generation') || '0'; - return line + '\r\n'; - } -}; -module.exports = SDPUtil; \ No newline at end of file diff --git a/modules/xmpp/TraceablePeerConnection.js b/modules/xmpp/TraceablePeerConnection.js deleted file mode 100644 index 7df5bd8a5..000000000 --- a/modules/xmpp/TraceablePeerConnection.js +++ /dev/null @@ -1,449 +0,0 @@ -/* global $, config, mozRTCPeerConnection, RTCPeerConnection, - webkitRTCPeerConnection, RTCSessionDescription */ -/* jshint -W101 */ -var RTC = require('../RTC/RTC'); -var RTCBrowserType = require("../RTC/RTCBrowserType"); -var RTCEvents = require("../../service/RTC/RTCEvents"); -var SSRCReplacement = require("./LocalSSRCReplacement"); - -function TraceablePeerConnection(ice_config, constraints, session) { - var self = this; - var RTCPeerConnectionType = null; - if (RTCBrowserType.isFirefox()) { - RTCPeerConnectionType = mozRTCPeerConnection; - } else if (RTCBrowserType.isTemasysPluginUsed()) { - RTCPeerConnectionType = RTCPeerConnection; - } else { - RTCPeerConnectionType = webkitRTCPeerConnection; - } - self.eventEmitter = session.eventEmitter; - this.peerconnection = new RTCPeerConnectionType(ice_config, constraints); - this.updateLog = []; - this.stats = {}; - this.statsinterval = null; - this.maxstats = 0; // limit to 300 values, i.e. 5 minutes; set to 0 to disable - var Interop = require('sdp-interop').Interop; - this.interop = new Interop(); - var Simulcast = require('sdp-simulcast'); - this.simulcast = new Simulcast({numOfLayers: 3, explodeRemoteSimulcast: false}); - - // override as desired - this.trace = function (what, info) { - /*console.warn('WTRACE', what, info); - if (info && RTCBrowserType.isIExplorer()) { - if (info.length > 1024) { - console.warn('WTRACE', what, info.substr(1024)); - } - if (info.length > 2048) { - console.warn('WTRACE', what, info.substr(2048)); - } - }*/ - self.updateLog.push({ - time: new Date(), - type: what, - value: info || "" - }); - }; - this.onicecandidate = null; - this.peerconnection.onicecandidate = function (event) { - // FIXME: this causes stack overflow with Temasys Plugin - if (!RTCBrowserType.isTemasysPluginUsed()) - self.trace('onicecandidate', JSON.stringify(event.candidate, null, ' ')); - if (self.onicecandidate !== null) { - self.onicecandidate(event); - } - }; - this.onaddstream = null; - this.peerconnection.onaddstream = function (event) { - self.trace('onaddstream', event.stream.id); - if (self.onaddstream !== null) { - self.onaddstream(event); - } - }; - this.onremovestream = null; - this.peerconnection.onremovestream = function (event) { - self.trace('onremovestream', event.stream.id); - if (self.onremovestream !== null) { - self.onremovestream(event); - } - }; - this.onsignalingstatechange = null; - this.peerconnection.onsignalingstatechange = function (event) { - self.trace('onsignalingstatechange', self.signalingState); - if (self.onsignalingstatechange !== null) { - self.onsignalingstatechange(event); - } - }; - this.oniceconnectionstatechange = null; - this.peerconnection.oniceconnectionstatechange = function (event) { - self.trace('oniceconnectionstatechange', self.iceConnectionState); - if (self.oniceconnectionstatechange !== null) { - self.oniceconnectionstatechange(event); - } - }; - this.onnegotiationneeded = null; - this.peerconnection.onnegotiationneeded = function (event) { - self.trace('onnegotiationneeded'); - if (self.onnegotiationneeded !== null) { - self.onnegotiationneeded(event); - } - }; - self.ondatachannel = null; - this.peerconnection.ondatachannel = function (event) { - self.trace('ondatachannel', event); - if (self.ondatachannel !== null) { - self.ondatachannel(event); - } - }; - // XXX: do all non-firefox browsers which we support also support this? - if (!RTCBrowserType.isFirefox() && this.maxstats) { - this.statsinterval = window.setInterval(function() { - self.peerconnection.getStats(function(stats) { - var results = stats.result(); - var now = new Date(); - for (var i = 0; i < results.length; ++i) { - results[i].names().forEach(function (name) { - var id = results[i].id + '-' + name; - if (!self.stats[id]) { - self.stats[id] = { - startTime: now, - endTime: now, - values: [], - times: [] - }; - } - self.stats[id].values.push(results[i].stat(name)); - self.stats[id].times.push(now.getTime()); - if (self.stats[id].values.length > self.maxstats) { - self.stats[id].values.shift(); - self.stats[id].times.shift(); - } - self.stats[id].endTime = now; - }); - } - }); - - }, 1000); - } -} - -/** - * Returns a string representation of a SessionDescription object. - */ -var dumpSDP = function(description) { - if (typeof description === 'undefined' || description === null) { - return ''; - } - - return 'type: ' + description.type + '\r\n' + description.sdp; -}; - -/** - * Takes a SessionDescription object and returns a "normalized" version. - * Currently it only takes care of ordering the a=ssrc lines. - */ -var normalizePlanB = function(desc) { - if (typeof desc !== 'object' || desc === null || - typeof desc.sdp !== 'string') { - console.warn('An empty description was passed as an argument.'); - return desc; - } - - var transform = require('sdp-transform'); - var session = transform.parse(desc.sdp); - - if (typeof session !== 'undefined' && typeof session.media !== 'undefined' && - Array.isArray(session.media)) { - session.media.forEach(function (mLine) { - - // Chrome appears to be picky about the order in which a=ssrc lines - // are listed in an m-line when rtx is enabled (and thus there are - // a=ssrc-group lines with FID semantics). Specifically if we have - // "a=ssrc-group:FID S1 S2" and the "a=ssrc:S2" lines appear before - // the "a=ssrc:S1" lines, SRD fails. - // So, put SSRC which appear as the first SSRC in an FID ssrc-group - // first. - var firstSsrcs = []; - var newSsrcLines = []; - - if (typeof mLine.ssrcGroups !== 'undefined' && Array.isArray(mLine.ssrcGroups)) { - mLine.ssrcGroups.forEach(function (group) { - if (typeof group.semantics !== 'undefined' && - group.semantics === 'FID') { - if (typeof group.ssrcs !== 'undefined') { - firstSsrcs.push(Number(group.ssrcs.split(' ')[0])); - } - } - }); - } - - if (typeof mLine.ssrcs !== 'undefined' && Array.isArray(mLine.ssrcs)) { - var i; - for (i = 0; i Plan B - // transformation. - desc = SSRCReplacement.mungeLocalVideoSSRC(desc); - - this.trace('getLocalDescription::preTransform', dumpSDP(desc)); - - // if we're running on FF, transform to Plan B first. - if (RTCBrowserType.usesUnifiedPlan()) { - desc = this.interop.toPlanB(desc); - this.trace('getLocalDescription::postTransform (Plan B)', dumpSDP(desc)); - } - return desc; - }); - TraceablePeerConnection.prototype.__defineGetter__( - 'remoteDescription', - function() { - var desc = this.peerconnection.remoteDescription; - this.trace('getRemoteDescription::preTransform', dumpSDP(desc)); - - // if we're running on FF, transform to Plan B first. - if (RTCBrowserType.usesUnifiedPlan()) { - desc = this.interop.toPlanB(desc); - this.trace('getRemoteDescription::postTransform (Plan B)', dumpSDP(desc)); - } - return desc; - }); -} - -TraceablePeerConnection.prototype.addStream = function (stream) { - this.trace('addStream', stream.id); - try - { - this.peerconnection.addStream(stream); - } - catch (e) - { - console.error(e); - } -}; - -TraceablePeerConnection.prototype.removeStream = function (stream, stopStreams) { - this.trace('removeStream', stream.id); - if(stopStreams) { - RTC.stopMediaStream(stream); - } - - try { - // FF doesn't support this yet. - if (this.peerconnection.removeStream) - this.peerconnection.removeStream(stream); - } catch (e) { - console.error(e); - } -}; - -TraceablePeerConnection.prototype.createDataChannel = function (label, opts) { - this.trace('createDataChannel', label, opts); - return this.peerconnection.createDataChannel(label, opts); -}; - -TraceablePeerConnection.prototype.setLocalDescription - = function (description, successCallback, failureCallback) { - this.trace('setLocalDescription::preTransform', dumpSDP(description)); - // if we're running on FF, transform to Plan A first. - if (RTCBrowserType.usesUnifiedPlan()) { - description = this.interop.toUnifiedPlan(description); - this.trace('setLocalDescription::postTransform (Plan A)', dumpSDP(description)); - } - - var self = this; - this.peerconnection.setLocalDescription(description, - function () { - self.trace('setLocalDescriptionOnSuccess'); - successCallback(); - }, - function (err) { - self.trace('setLocalDescriptionOnFailure', err); - self.eventEmitter.emit(RTCEvents.SET_LOCAL_DESCRIPTION_FAILED, err, self.peerconnection); - failureCallback(err); - } - ); - /* - if (this.statsinterval === null && this.maxstats > 0) { - // start gathering stats - } - */ -}; - -TraceablePeerConnection.prototype.setRemoteDescription - = function (description, successCallback, failureCallback) { - this.trace('setRemoteDescription::preTransform', dumpSDP(description)); - // TODO the focus should squeze or explode the remote simulcast - description = this.simulcast.mungeRemoteDescription(description); - this.trace('setRemoteDescription::postTransform (simulcast)', dumpSDP(description)); - - // if we're running on FF, transform to Plan A first. - if (RTCBrowserType.usesUnifiedPlan()) { - description = this.interop.toUnifiedPlan(description); - this.trace('setRemoteDescription::postTransform (Plan A)', dumpSDP(description)); - } - - if (RTCBrowserType.usesPlanB()) { - description = normalizePlanB(description); - } - - var self = this; - this.peerconnection.setRemoteDescription(description, - function () { - self.trace('setRemoteDescriptionOnSuccess'); - successCallback(); - }, - function (err) { - self.trace('setRemoteDescriptionOnFailure', err); - self.eventEmitter.emit(RTCEvents.SET_REMOTE_DESCRIPTION_FAILED, err, self.peerconnection); - failureCallback(err); - } - ); - /* - if (this.statsinterval === null && this.maxstats > 0) { - // start gathering stats - } - */ -}; - -TraceablePeerConnection.prototype.close = function () { - this.trace('stop'); - if (this.statsinterval !== null) { - window.clearInterval(this.statsinterval); - this.statsinterval = null; - } - this.peerconnection.close(); -}; - -TraceablePeerConnection.prototype.createOffer - = function (successCallback, failureCallback, constraints) { - var self = this; - this.trace('createOffer', JSON.stringify(constraints, null, ' ')); - this.peerconnection.createOffer( - function (offer) { - self.trace('createOfferOnSuccess::preTransform', dumpSDP(offer)); - // NOTE this is not tested because in meet the focus generates the - // offer. - - // if we're running on FF, transform to Plan B first. - if (RTCBrowserType.usesUnifiedPlan()) { - offer = self.interop.toPlanB(offer); - self.trace('createOfferOnSuccess::postTransform (Plan B)', dumpSDP(offer)); - } - - offer = SSRCReplacement.mungeLocalVideoSSRC(offer); - - if (config.enableSimulcast && self.simulcast.isSupported()) { - offer = self.simulcast.mungeLocalDescription(offer); - self.trace('createOfferOnSuccess::postTransform (simulcast)', dumpSDP(offer)); - } - successCallback(offer); - }, - function(err) { - self.trace('createOfferOnFailure', err); - self.eventEmitter.emit(RTCEvents.CREATE_OFFER_FAILED, err, self.peerconnection); - failureCallback(err); - }, - constraints - ); -}; - -TraceablePeerConnection.prototype.createAnswer - = function (successCallback, failureCallback, constraints) { - var self = this; - this.trace('createAnswer', JSON.stringify(constraints, null, ' ')); - this.peerconnection.createAnswer( - function (answer) { - self.trace('createAnswerOnSuccess::preTransform', dumpSDP(answer)); - // if we're running on FF, transform to Plan A first. - if (RTCBrowserType.usesUnifiedPlan()) { - answer = self.interop.toPlanB(answer); - self.trace('createAnswerOnSuccess::postTransform (Plan B)', dumpSDP(answer)); - } - - // munge local video SSRC - answer = SSRCReplacement.mungeLocalVideoSSRC(answer); - - if (config.enableSimulcast && self.simulcast.isSupported()) { - answer = self.simulcast.mungeLocalDescription(answer); - self.trace('createAnswerOnSuccess::postTransform (simulcast)', dumpSDP(answer)); - } - successCallback(answer); - }, - function(err) { - self.trace('createAnswerOnFailure', err); - self.eventEmitter.emit(RTCEvents.CREATE_ANSWER_FAILED, err, self.peerconnection); - failureCallback(err); - }, - constraints - ); -}; - -TraceablePeerConnection.prototype.addIceCandidate - = function (candidate, successCallback, failureCallback) { - //var self = this; - this.trace('addIceCandidate', JSON.stringify(candidate, null, ' ')); - this.peerconnection.addIceCandidate(candidate); - /* maybe later - this.peerconnection.addIceCandidate(candidate, - function () { - self.trace('addIceCandidateOnSuccess'); - successCallback(); - }, - function (err) { - self.trace('addIceCandidateOnFailure', err); - failureCallback(err); - } - ); - */ -}; - -TraceablePeerConnection.prototype.getStats = function(callback, errback) { - // TODO: Is this the correct way to handle Opera, Temasys? - if (RTCBrowserType.isFirefox()) { - // ignore for now... - if(!errback) - errback = function () {}; - this.peerconnection.getStats(null, callback, errback); - } else { - this.peerconnection.getStats(callback); - } -}; - -module.exports = TraceablePeerConnection; diff --git a/modules/xmpp/moderator.js b/modules/xmpp/moderator.js deleted file mode 100644 index 0c32057dc..000000000 --- a/modules/xmpp/moderator.js +++ /dev/null @@ -1,434 +0,0 @@ -/* global $, $iq, APP, config, messageHandler, - roomName, sessionTerminated, Strophe, Util */ -var XMPPEvents = require("../../service/xmpp/XMPPEvents"); -var Settings = require("../settings/Settings"); - -var AuthenticationEvents - = require("../../service/authentication/AuthenticationEvents"); - -/** - * Contains logic responsible for enabling/disabling functionality available - * only to moderator users. - */ -var connection = null; -var focusUserJid; - -function createExpBackoffTimer(step) { - var count = 1; - return function (reset) { - // Reset call - if (reset) { - count = 1; - return; - } - // Calculate next timeout - var timeout = Math.pow(2, count - 1); - count += 1; - return timeout * step; - }; -} - -var getNextTimeout = createExpBackoffTimer(1000); -var getNextErrorTimeout = createExpBackoffTimer(1000); -// External authentication stuff -var externalAuthEnabled = false; -// Sip gateway can be enabled by configuring Jigasi host in config.js or -// it will be enabled automatically if focus detects the component through -// service discovery. -var sipGatewayEnabled; - -var eventEmitter = null; - -var Moderator = { - isModerator: function () { - return connection && connection.emuc.isModerator(); - }, - - isPeerModerator: function (peerJid) { - return connection && - connection.emuc.getMemberRole(peerJid) === 'moderator'; - }, - - isExternalAuthEnabled: function () { - return externalAuthEnabled; - }, - - isSipGatewayEnabled: function () { - return sipGatewayEnabled; - }, - - setConnection: function (con) { - connection = con; - }, - - init: function (xmpp, emitter) { - this.xmppService = xmpp; - eventEmitter = emitter; - - sipGatewayEnabled = - config.hosts && config.hosts.call_control !== undefined; - - // Message listener that talks to POPUP window - function listener(event) { - if (event.data && event.data.sessionId) { - if (event.origin !== window.location.origin) { - console.warn("Ignoring sessionId from different origin: " + - event.origin); - return; - } - localStorage.setItem('sessionId', event.data.sessionId); - // After popup is closed we will authenticate - } - } - // Register - if (window.addEventListener) { - window.addEventListener("message", listener, false); - } else { - window.attachEvent("onmessage", listener); - } - }, - - onMucMemberLeft: function (jid) { - console.info("Someone left is it focus ? " + jid); - var resource = Strophe.getResourceFromJid(jid); - if (resource === 'focus' && !this.xmppService.sessionTerminated) { - console.info( - "Focus has left the room - leaving conference"); - //hangUp(); - // We'd rather reload to have everything re-initialized - // FIXME: show some message before reload - location.reload(); - } - }, - - setFocusUserJid: function (focusJid) { - if (!focusUserJid) { - focusUserJid = focusJid; - console.info("Focus jid set to: " + focusUserJid); - } - }, - - getFocusUserJid: function () { - return focusUserJid; - }, - - getFocusComponent: function () { - // Get focus component address - var focusComponent = config.hosts.focus; - // If not specified use default: 'focus.domain' - if (!focusComponent) { - focusComponent = 'focus.' + config.hosts.domain; - } - return focusComponent; - }, - - createConferenceIq: function (roomName) { - // Generate create conference IQ - var elem = $iq({to: Moderator.getFocusComponent(), type: 'set'}); - - // Session Id used for authentication - var sessionId = localStorage.getItem('sessionId'); - var machineUID = Settings.getSettings().uid; - - console.info( - "Session ID: " + sessionId + " machine UID: " + machineUID); - - elem.c('conference', { - xmlns: 'http://jitsi.org/protocol/focus', - room: roomName, - 'machine-uid': machineUID - }); - - if (sessionId) { - elem.attrs({ 'session-id': sessionId}); - } - - if (config.hosts.bridge !== undefined) { - elem.c( - 'property', - { name: 'bridge', value: config.hosts.bridge}) - .up(); - } - // Tell the focus we have Jigasi configured - if (config.hosts.call_control !== undefined) { - elem.c( - 'property', - { name: 'call_control', value: config.hosts.call_control}) - .up(); - } - if (config.channelLastN !== undefined) { - elem.c( - 'property', - { name: 'channelLastN', value: config.channelLastN}) - .up(); - } - if (config.adaptiveLastN !== undefined) { - elem.c( - 'property', - { name: 'adaptiveLastN', value: config.adaptiveLastN}) - .up(); - } - if (config.adaptiveSimulcast !== undefined) { - elem.c( - 'property', - { name: 'adaptiveSimulcast', value: config.adaptiveSimulcast}) - .up(); - } - if (config.openSctp !== undefined) { - elem.c( - 'property', - { name: 'openSctp', value: config.openSctp}) - .up(); - } - if(config.startAudioMuted !== undefined) - { - elem.c( - 'property', - { name: 'startAudioMuted', value: config.startAudioMuted}) - .up(); - } - if(config.startVideoMuted !== undefined) - { - elem.c( - 'property', - { name: 'startVideoMuted', value: config.startVideoMuted}) - .up(); - } - elem.c( - 'property', - { name: 'simulcastMode', value: 'rewriting'}) - .up(); - elem.up(); - return elem; - }, - - parseSessionId: function (resultIq) { - var sessionId = $(resultIq).find('conference').attr('session-id'); - if (sessionId) { - console.info('Received sessionId: ' + sessionId); - localStorage.setItem('sessionId', sessionId); - } - }, - - parseConfigOptions: function (resultIq) { - - Moderator.setFocusUserJid( - $(resultIq).find('conference').attr('focusjid')); - - var authenticationEnabled - = $(resultIq).find( - '>conference>property' + - '[name=\'authentication\'][value=\'true\']').length > 0; - - console.info("Authentication enabled: " + authenticationEnabled); - - externalAuthEnabled = $(resultIq).find( - '>conference>property' + - '[name=\'externalAuth\'][value=\'true\']').length > 0; - - console.info('External authentication enabled: ' + externalAuthEnabled); - - if (!externalAuthEnabled) { - // We expect to receive sessionId in 'internal' authentication mode - Moderator.parseSessionId(resultIq); - } - - var authIdentity = $(resultIq).find('>conference').attr('identity'); - - eventEmitter.emit(AuthenticationEvents.IDENTITY_UPDATED, - authenticationEnabled, authIdentity); - - // Check if focus has auto-detected Jigasi component(this will be also - // included if we have passed our host from the config) - if ($(resultIq).find( - '>conference>property' + - '[name=\'sipGatewayEnabled\'][value=\'true\']').length) { - sipGatewayEnabled = true; - } - - console.info("Sip gateway enabled: " + sipGatewayEnabled); - }, - - // FIXME: we need to show the fact that we're waiting for the focus - // to the user(or that focus is not available) - allocateConferenceFocus: function (roomName, callback) { - // Try to use focus user JID from the config - Moderator.setFocusUserJid(config.focusUserJid); - // Send create conference IQ - var iq = Moderator.createConferenceIq(roomName); - var self = this; - connection.sendIQ( - iq, - function (result) { - - // Setup config options - Moderator.parseConfigOptions(result); - - if ('true' === $(result).find('conference').attr('ready')) { - // Reset both timers - getNextTimeout(true); - getNextErrorTimeout(true); - // Exec callback - callback(); - } else { - var waitMs = getNextTimeout(); - console.info("Waiting for the focus... " + waitMs); - // Reset error timeout - getNextErrorTimeout(true); - window.setTimeout( - function () { - Moderator.allocateConferenceFocus( - roomName, callback); - }, waitMs); - } - }, - function (error) { - // Invalid session ? remove and try again - // without session ID to get a new one - var invalidSession - = $(error).find('>error>session-invalid').length; - if (invalidSession) { - console.info("Session expired! - removing"); - localStorage.removeItem("sessionId"); - } - if ($(error).find('>error>graceful-shutdown').length) { - eventEmitter.emit(XMPPEvents.GRACEFUL_SHUTDOWN); - return; - } - // Check for error returned by the reservation system - var reservationErr = $(error).find('>error>reservation-error'); - if (reservationErr.length) { - // Trigger error event - var errorCode = reservationErr.attr('error-code'); - var errorMsg; - if ($(error).find('>error>text')) { - errorMsg = $(error).find('>error>text').text(); - } - eventEmitter.emit( - XMPPEvents.RESERVATION_ERROR, errorCode, errorMsg); - return; - } - // Not authorized to create new room - if ($(error).find('>error>not-authorized').length) { - console.warn("Unauthorized to start the conference", error); - var toDomain - = Strophe.getDomainFromJid(error.getAttribute('to')); - if (toDomain !== config.hosts.anonymousdomain) { - // FIXME: "is external" should come either from - // the focus or config.js - externalAuthEnabled = true; - } - eventEmitter.emit( - XMPPEvents.AUTHENTICATION_REQUIRED, - function () { - Moderator.allocateConferenceFocus( - roomName, callback); - }); - return; - } - var waitMs = getNextErrorTimeout(); - console.error("Focus error, retry after " + waitMs, error); - // Show message - var focusComponent = Moderator.getFocusComponent(); - var retrySec = waitMs / 1000; - // FIXME: message is duplicated ? - // Do not show in case of session invalid - // which means just a retry - if (!invalidSession) { - eventEmitter.emit(XMPPEvents.FOCUS_DISCONNECTED, - focusComponent, retrySec); - } - // Reset response timeout - getNextTimeout(true); - window.setTimeout( - function () { - Moderator.allocateConferenceFocus(roomName, callback); - }, waitMs); - } - ); - }, - - getLoginUrl: function (roomName, urlCallback) { - var iq = $iq({to: Moderator.getFocusComponent(), type: 'get'}); - iq.c('login-url', { - xmlns: 'http://jitsi.org/protocol/focus', - room: roomName, - 'machine-uid': Settings.getSettings().uid - }); - connection.sendIQ( - iq, - function (result) { - var url = $(result).find('login-url').attr('url'); - url = url = decodeURIComponent(url); - if (url) { - console.info("Got auth url: " + url); - urlCallback(url); - } else { - console.error( - "Failed to get auth url from the focus", result); - } - }, - function (error) { - console.error("Get auth url error", error); - } - ); - }, - getPopupLoginUrl: function (roomName, urlCallback) { - var iq = $iq({to: Moderator.getFocusComponent(), type: 'get'}); - iq.c('login-url', { - xmlns: 'http://jitsi.org/protocol/focus', - room: roomName, - 'machine-uid': Settings.getSettings().uid, - popup: true - }); - connection.sendIQ( - iq, - function (result) { - var url = $(result).find('login-url').attr('url'); - url = url = decodeURIComponent(url); - if (url) { - console.info("Got POPUP auth url: " + url); - urlCallback(url); - } else { - console.error( - "Failed to get POPUP auth url from the focus", result); - } - }, - function (error) { - console.error('Get POPUP auth url error', error); - } - ); - }, - logout: function (callback) { - var iq = $iq({to: Moderator.getFocusComponent(), type: 'set'}); - var sessionId = localStorage.getItem('sessionId'); - if (!sessionId) { - callback(); - return; - } - iq.c('logout', { - xmlns: 'http://jitsi.org/protocol/focus', - 'session-id': sessionId - }); - connection.sendIQ( - iq, - function (result) { - var logoutUrl = $(result).find('logout').attr('logout-url'); - if (logoutUrl) { - logoutUrl = decodeURIComponent(logoutUrl); - } - console.info("Log out OK, url: " + logoutUrl, result); - localStorage.removeItem('sessionId'); - callback(logoutUrl); - }, - function (error) { - console.error("Logout error", error); - } - ); - } -}; - -module.exports = Moderator; - - - diff --git a/modules/xmpp/recording.js b/modules/xmpp/recording.js deleted file mode 100644 index 1350d13ae..000000000 --- a/modules/xmpp/recording.js +++ /dev/null @@ -1,178 +0,0 @@ -/* global $, $iq, config, connection, focusMucJid, messageHandler, - Toolbar, Util */ -var Moderator = require("./moderator"); - - -var recordingToken = null; -var recordingEnabled; - -/** - * Whether to use a jirecon component for recording, or use the videobridge - * through COLIBRI. - */ -var useJirecon; - -/** - * The ID of the jirecon recording session. Jirecon generates it when we - * initially start recording, and it needs to be used in subsequent requests - * to jirecon. - */ -var jireconRid = null; - -/** - * The callback to update the recording button. Currently used from colibri - * after receiving a pending status. - */ -var recordingStateChangeCallback = null; - -function setRecordingToken(token) { - recordingToken = token; -} - -function setRecordingJirecon(state, token, callback, connection) { - if (state == recordingEnabled){ - return; - } - - var iq = $iq({to: config.hosts.jirecon, type: 'set'}) - .c('recording', {xmlns: 'http://jitsi.org/protocol/jirecon', - action: (state === 'on') ? 'start' : 'stop', - mucjid: connection.emuc.roomjid}); - if (state === 'off'){ - iq.attrs({rid: jireconRid}); - } - - console.log('Start recording'); - - connection.sendIQ( - iq, - function (result) { - // TODO wait for an IQ with the real status, since this is - // provisional? - jireconRid = $(result).find('recording').attr('rid'); - console.log('Recording ' + - ((state === 'on') ? 'started' : 'stopped') + - '(jirecon)' + result); - recordingEnabled = state; - if (state === 'off'){ - jireconRid = null; - } - - callback(state); - }, - function (error) { - console.log('Failed to start recording, error: ', error); - callback(recordingEnabled); - }); -} - -// Sends a COLIBRI message which enables or disables (according to 'state') -// the recording on the bridge. Waits for the result IQ and calls 'callback' -// with the new recording state, according to the IQ. -function setRecordingColibri(state, token, callback, connection) { - var elem = $iq({to: connection.emuc.focusMucJid, type: 'set'}); - elem.c('conference', { - xmlns: 'http://jitsi.org/protocol/colibri' - }); - elem.c('recording', {state: state, token: token}); - - connection.sendIQ(elem, - function (result) { - console.log('Set recording "', state, '". Result:', result); - var recordingElem = $(result).find('>conference>recording'); - var newState = recordingElem.attr('state'); - - recordingEnabled = newState; - callback(newState); - - if (newState === 'pending' && !recordingStateChangeCallback) { - recordingStateChangeCallback = callback; - connection.addHandler(function(iq){ - var state = $(iq).find('recording').attr('state'); - if (state) - recordingStateChangeCallback(state); - }, 'http://jitsi.org/protocol/colibri', 'iq', null, null, null); - } - }, - function (error) { - console.warn(error); - callback(recordingEnabled); - } - ); -} - -function setRecording(state, token, callback, connection) { - if (useJirecon){ - setRecordingJirecon(state, token, callback, connection); - } else { - setRecordingColibri(state, token, callback, connection); - } -} - -var Recording = { - init: function () { - useJirecon = config.hosts && - (typeof config.hosts.jirecon != "undefined"); - }, - toggleRecording: function (tokenEmptyCallback, - recordingStateChangeCallback, - connection) { - if (!Moderator.isModerator()) { - console.log( - 'non-focus, or conference not yet organized:' + - ' not enabling recording'); - return; - } - - var self = this; - // Jirecon does not (currently) support a token. - if (!recordingToken && !useJirecon) { - tokenEmptyCallback(function (value) { - setRecordingToken(value); - self.toggleRecording(tokenEmptyCallback, - recordingStateChangeCallback, - connection); - }); - - return; - } - - var oldState = recordingEnabled; - var newState = (oldState === 'off' || !oldState) ? 'on' : 'off'; - - setRecording(newState, - recordingToken, - function (state) { - console.log("New recording state: ", state); - if (state === oldState) { - // FIXME: new focus: - // this will not work when moderator changes - // during active session. Then it will assume that - // recording status has changed to true, but it might have - // been already true(and we only received actual status from - // the focus). - // - // SO we start with status null, so that it is initialized - // here and will fail only after second click, so if invalid - // token was used we have to press the button twice before - // current status will be fetched and token will be reset. - // - // Reliable way would be to return authentication error. - // Or status update when moderator connects. - // Or we have to stop recording session when current - // moderator leaves the room. - - // Failed to change, reset the token because it might - // have been wrong - setRecordingToken(null); - } - recordingStateChangeCallback(state); - - }, - connection - ); - } - -}; - -module.exports = Recording; \ No newline at end of file diff --git a/modules/xmpp/strophe.emuc.js b/modules/xmpp/strophe.emuc.js deleted file mode 100644 index ef6be3585..000000000 --- a/modules/xmpp/strophe.emuc.js +++ /dev/null @@ -1,706 +0,0 @@ -/* jshint -W117 */ -/* a simple MUC connection plugin - * can only handle a single MUC room - */ -var XMPPEvents = require("../../service/xmpp/XMPPEvents"); -var Moderator = require("./moderator"); - -module.exports = function(XMPP, eventEmitter) { - Strophe.addConnectionPlugin('emuc', { - connection: null, - roomjid: null, - myroomjid: null, - members: {}, - list_members: [], // so we can elect a new focus - presMap: {}, - preziMap: {}, - lastPresenceMap: {}, - joined: false, - isOwner: false, - role: null, - focusMucJid: null, - bridgeIsDown: false, - init: function (conn) { - this.connection = conn; - }, - initPresenceMap: function (myroomjid) { - this.presMap['to'] = myroomjid; - this.presMap['xns'] = 'http://jabber.org/protocol/muc'; - if (APP.RTC.localAudio && APP.RTC.localAudio.isMuted()) { - this.addAudioInfoToPresence(true); - } - if (APP.RTC.localVideo && APP.RTC.localVideo.isMuted()) { - this.addVideoInfoToPresence(true); - } - }, - doJoin: function (jid, password) { - this.myroomjid = jid; - - console.info("Joined MUC as " + this.myroomjid); - - this.initPresenceMap(this.myroomjid); - - if (!this.roomjid) { - this.roomjid = Strophe.getBareJidFromJid(jid); - // add handlers (just once) - this.connection.addHandler(this.onPresence.bind(this), null, 'presence', null, null, this.roomjid, {matchBare: true}); - this.connection.addHandler(this.onPresenceUnavailable.bind(this), null, 'presence', 'unavailable', null, this.roomjid, {matchBare: true}); - this.connection.addHandler(this.onPresenceError.bind(this), null, 'presence', 'error', null, this.roomjid, {matchBare: true}); - this.connection.addHandler(this.onMessage.bind(this), null, 'message', null, null, this.roomjid, {matchBare: true}); - } - if (password !== undefined) { - this.presMap['password'] = password; - } - this.sendPresence(); - }, - doLeave: function () { - console.log("do leave", this.myroomjid); - var pres = $pres({to: this.myroomjid, type: 'unavailable' }); - this.presMap.length = 0; - this.connection.send(pres); - }, - createNonAnonymousRoom: function () { - // http://xmpp.org/extensions/xep-0045.html#createroom-reserved - - var getForm = $iq({type: 'get', to: this.roomjid}) - .c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'}) - .c('x', {xmlns: 'jabber:x:data', type: 'submit'}); - - var self = this; - - this.connection.sendIQ(getForm, function (form) { - - if (!$(form).find( - '>query>x[xmlns="jabber:x:data"]' + - '>field[var="muc#roomconfig_whois"]').length) { - - console.error('non-anonymous rooms not supported'); - return; - } - - var formSubmit = $iq({to: this.roomjid, type: 'set'}) - .c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'}); - - formSubmit.c('x', {xmlns: 'jabber:x:data', type: 'submit'}); - - formSubmit.c('field', {'var': 'FORM_TYPE'}) - .c('value') - .t('http://jabber.org/protocol/muc#roomconfig').up().up(); - - formSubmit.c('field', {'var': 'muc#roomconfig_whois'}) - .c('value').t('anyone').up().up(); - - self.connection.sendIQ(formSubmit); - - }, function (error) { - console.error("Error getting room configuration form"); - }); - }, - onPresence: function (pres) { - var from = pres.getAttribute('from'); - - // What is this for? A workaround for something? - if (pres.getAttribute('type')) { - return true; - } - - // Parse etherpad tag. - var etherpad = $(pres).find('>etherpad'); - if (etherpad.length) { - if (config.etherpad_base) { - eventEmitter.emit(XMPPEvents.ETHERPAD, etherpad.text()); - } - } - - var url; - // Parse prezi tag. - var presentation = $(pres).find('>prezi'); - if (presentation.length) { - url = presentation.attr('url'); - var current = presentation.find('>current').text(); - - console.log('presentation info received from', from, url); - - if (this.preziMap[from] == null) { - this.preziMap[from] = url; - - $(document).trigger('presentationadded.muc', [from, url, current]); - } - else { - $(document).trigger('gotoslide.muc', [from, url, current]); - } - } - else if (this.preziMap[from] != null) { - url = this.preziMap[from]; - delete this.preziMap[from]; - $(document).trigger('presentationremoved.muc', [from, url]); - } - - // store the last presence for participant - this.lastPresenceMap[from] = {}; - - // Parse audio info tag. - var audioMuted = $(pres).find('>audiomuted'); - if (audioMuted.length) { - eventEmitter.emit(XMPPEvents.PARTICIPANT_AUDIO_MUTED, - from, (audioMuted.text() === "true")); - } - - // Parse video info tag. - var videoMuted = $(pres).find('>videomuted'); - if (videoMuted.length) { - var value = (videoMuted.text() === "true"); - this.lastPresenceMap[from].videoMuted = value; - eventEmitter.emit(XMPPEvents.PARTICIPANT_VIDEO_MUTED, from, value); - } - - var startMuted = $(pres).find('>startmuted'); - if (startMuted.length && Moderator.isPeerModerator(from)) { - eventEmitter.emit(XMPPEvents.START_MUTED_SETTING_CHANGED, - startMuted.attr("audio") === "true", - startMuted.attr("video") === "true"); - } - - var devices = $(pres).find('>devices'); - if(devices.length) - { - var audio = devices.find('>audio'); - var video = devices.find('>video'); - var devicesValues = {audio: false, video: false}; - if(audio.length && audio.text() === "true") - { - devicesValues.audio = true; - } - - if(video.length && video.text() === "true") - { - devicesValues.video = true; - } - eventEmitter.emit(XMPPEvents.DEVICE_AVAILABLE, - Strophe.getResourceFromJid(from), devicesValues); - } - - var videoType = $(pres).find('>videoType'); - if (videoType.length) - { - if (videoType.text().length) - { - eventEmitter.emit(XMPPEvents.PARTICIPANT_VIDEO_TYPE_CHANGED, - Strophe.getResourceFromJid(from), videoType.text()); - } - } - - var stats = $(pres).find('>stats'); - if (stats.length) { - var statsObj = {}; - Strophe.forEachChild(stats[0], "stat", function (el) { - statsObj[el.getAttribute("name")] = el.getAttribute("value"); - }); - eventEmitter.emit(XMPPEvents.REMOTE_STATS, from, statsObj); - } - - // Parse status. - if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="201"]').length) { - this.isOwner = true; - this.createNonAnonymousRoom(); - } - - // Parse roles. - var member = {}; - member.show = $(pres).find('>show').text(); - member.status = $(pres).find('>status').text(); - var tmp = $(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>item'); - member.affiliation = tmp.attr('affiliation'); - member.role = tmp.attr('role'); - - // Focus recognition - member.jid = tmp.attr('jid'); - member.isFocus = false; - if (member.jid - && member.jid.indexOf(Moderator.getFocusUserJid() + "/") == 0) { - member.isFocus = true; - } - - var nicktag = $(pres).find('>nick[xmlns="http://jabber.org/protocol/nick"]'); - member.displayName = (nicktag.length > 0 ? nicktag.text() : null); - - if (from == this.myroomjid) { - if (member.affiliation == 'owner') this.isOwner = true; - if (this.role !== member.role) { - this.role = member.role; - - eventEmitter.emit(XMPPEvents.LOCAL_ROLE_CHANGED, - from, member, pres, Moderator.isModerator()); - } - if (!this.joined) { - this.joined = true; - console.log("(TIME) MUC joined:\t", - window.performance.now()); - eventEmitter.emit(XMPPEvents.MUC_JOINED, from, member); - this.list_members.push(from); - } - } else if (this.members[from] === undefined) { - // new participant - this.members[from] = member; - this.list_members.push(from); - console.log('entered', from, member); - if (member.isFocus) { - this.focusMucJid = from; - console.info("Ignore focus: " + from + ", real JID: " + member.jid); - } - else { - var id = $(pres).find('>userId').text(); - var email = $(pres).find('>email'); - if (email.length > 0) { - id = email.text(); - } - eventEmitter.emit(XMPPEvents.MUC_MEMBER_JOINED, from, id, member.displayName); - } - } else { - // Presence update for existing participant - // Watch role change: - if (this.members[from].role != member.role) { - this.members[from].role = member.role; - eventEmitter.emit(XMPPEvents.MUC_ROLE_CHANGED, - member.role, member.displayName); - } - - // store the new - if(member.displayName) - this.members[from].displayName = member.displayName; - } - - // Always trigger presence to update bindings - this.parsePresence(from, member, pres); - - // Trigger status message update - if (member.status) { - eventEmitter.emit(XMPPEvents.PRESENCE_STATUS, from, member); - } - - return true; - }, - onPresenceUnavailable: function (pres) { - var from = pres.getAttribute('from'); - // room destroyed ? - if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]' + - '>destroy').length) { - var reason; - var reasonSelect = $(pres).find( - '>x[xmlns="http://jabber.org/protocol/muc#user"]' + - '>destroy>reason'); - if (reasonSelect.length) { - reason = reasonSelect.text(); - } - XMPP.disposeConference(false); - eventEmitter.emit(XMPPEvents.MUC_DESTROYED, reason); - return true; - } - - // Status code 110 indicates that this notification is "self-presence". - if (!$(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="110"]').length) { - delete this.members[from]; - this.list_members.splice(this.list_members.indexOf(from), 1); - this.onParticipantLeft(from); - } - // If the status code is 110 this means we're leaving and we would like - // to remove everyone else from our view, so we trigger the event. - else if (this.list_members.length > 1) { - for (var i = 0; i < this.list_members.length; i++) { - var member = this.list_members[i]; - delete this.members[i]; - this.list_members.splice(i, 1); - this.onParticipantLeft(member); - } - } - if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="307"]').length) { - $(document).trigger('kicked.muc', [from]); - if (this.myroomjid === from) { - XMPP.disposeConference(false); - eventEmitter.emit(XMPPEvents.KICKED); - } - } - - if (this.lastPresenceMap[from] != null) { - delete this.lastPresenceMap[from]; - } - - return true; - }, - onPresenceError: function (pres) { - var from = pres.getAttribute('from'); - if ($(pres).find('>error[type="auth"]>not-authorized[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) { - console.log('on password required', from); - var self = this; - eventEmitter.emit(XMPPEvents.PASSWORD_REQUIRED, function (value) { - self.doJoin(from, value); - }); - } else if ($(pres).find( - '>error[type="cancel"]>not-allowed[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) { - var toDomain = Strophe.getDomainFromJid(pres.getAttribute('to')); - if (toDomain === config.hosts.anonymousdomain) { - // enter the room by replying with 'not-authorized'. This would - // result in reconnection from authorized domain. - // We're either missing Jicofo/Prosody config for anonymous - // domains or something is wrong. -// XMPP.promptLogin(); - eventEmitter.emit(XMPPEvents.ROOM_JOIN_ERROR, pres); - - } else { - console.warn('onPresError ', pres); - eventEmitter.emit(XMPPEvents.ROOM_CONNECT_ERROR, pres); - } - } else { - console.warn('onPresError ', pres); - eventEmitter.emit(XMPPEvents.ROOM_CONNECT_ERROR, pres); - } - return true; - }, - sendMessage: function (body, nickname) { - var msg = $msg({to: this.roomjid, type: 'groupchat'}); - msg.c('body', body).up(); - if (nickname) { - msg.c('nick', {xmlns: 'http://jabber.org/protocol/nick'}).t(nickname).up().up(); - } - this.connection.send(msg); - eventEmitter.emit(XMPPEvents.SENDING_CHAT_MESSAGE, body); - }, - setSubject: function (subject) { - var msg = $msg({to: this.roomjid, type: 'groupchat'}); - msg.c('subject', subject); - this.connection.send(msg); - console.log("topic changed to " + subject); - }, - onMessage: function (msg) { - // FIXME: this is a hack. but jingle on muc makes nickchanges hard - var from = msg.getAttribute('from'); - var nick = - $(msg).find('>nick[xmlns="http://jabber.org/protocol/nick"]') - .text() || - Strophe.getResourceFromJid(from); - - var txt = $(msg).find('>body').text(); - var type = msg.getAttribute("type"); - if (type == "error") { - eventEmitter.emit(XMPPEvents.CHAT_ERROR_RECEIVED, - $(msg).find('>text').text(), txt); - return true; - } - - var subject = $(msg).find('>subject'); - if (subject.length) { - var subjectText = subject.text(); - if (subjectText || subjectText == "") { - eventEmitter.emit(XMPPEvents.SUBJECT_CHANGED, subjectText); - console.log("Subject is changed to " + subjectText); - } - } - - // xep-0203 delay - var stamp = $(msg).find('>delay').attr('stamp'); - - if (!stamp) { - // or xep-0091 delay, UTC timestamp - stamp = $(msg).find('>[xmlns="jabber:x:delay"]').attr('stamp'); - - if (stamp) { - // the format is CCYYMMDDThh:mm:ss - var dateParts = stamp.match(/(\d{4})(\d{2})(\d{2}T\d{2}:\d{2}:\d{2})/); - stamp = dateParts[1] + "-" + dateParts[2] + "-" + dateParts[3] + "Z"; - } - } - - if (txt) { - console.log('chat', nick, txt); - eventEmitter.emit(XMPPEvents.MESSAGE_RECEIVED, - from, nick, txt, this.myroomjid, stamp); - } - return true; - }, - lockRoom: function (key, onSuccess, onError, onNotSupported) { - //http://xmpp.org/extensions/xep-0045.html#roomconfig - var ob = this; - this.connection.sendIQ($iq({to: this.roomjid, type: 'get'}).c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'}), - function (res) { - if ($(res).find('>query>x[xmlns="jabber:x:data"]>field[var="muc#roomconfig_roomsecret"]').length) { - var formsubmit = $iq({to: ob.roomjid, type: 'set'}).c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'}); - formsubmit.c('x', {xmlns: 'jabber:x:data', type: 'submit'}); - formsubmit.c('field', {'var': 'FORM_TYPE'}).c('value').t('http://jabber.org/protocol/muc#roomconfig').up().up(); - formsubmit.c('field', {'var': 'muc#roomconfig_roomsecret'}).c('value').t(key).up().up(); - // Fixes a bug in prosody 0.9.+ https://code.google.com/p/lxmppd/issues/detail?id=373 - formsubmit.c('field', {'var': 'muc#roomconfig_whois'}).c('value').t('anyone').up().up(); - // FIXME: is muc#roomconfig_passwordprotectedroom required? - ob.connection.sendIQ(formsubmit, - onSuccess, - onError); - } else { - onNotSupported(); - } - }, onError); - }, - kick: function (jid) { - var kickIQ = $iq({to: this.roomjid, type: 'set'}) - .c('query', {xmlns: 'http://jabber.org/protocol/muc#admin'}) - .c('item', {nick: Strophe.getResourceFromJid(jid), role: 'none'}) - .c('reason').t('You have been kicked.').up().up().up(); - - this.connection.sendIQ( - kickIQ, - function (result) { - console.log('Kick participant with jid: ', jid, result); - }, - function (error) { - console.log('Kick participant error: ', error); - }); - }, - sendPresence: function () { - if (!this.presMap['to']) { - // Too early to send presence - not initialized - return; - } - var pres = $pres({to: this.presMap['to'] }); - pres.c('x', {xmlns: this.presMap['xns']}); - - if (this.presMap['password']) { - pres.c('password').t(this.presMap['password']).up(); - } - - pres.up(); - - // Send XEP-0115 'c' stanza that contains our capabilities info - if (this.connection.caps) { - this.connection.caps.node = config.clientNode; - pres.c('c', this.connection.caps.generateCapsAttrs()).up(); - } - - pres.c('user-agent', {xmlns: 'http://jitsi.org/jitmeet/user-agent'}) - .t(navigator.userAgent).up(); - - if (this.presMap['bridgeIsDown']) { - pres.c('bridgeIsDown').up(); - } - - if (this.presMap['email']) { - pres.c('email').t(this.presMap['email']).up(); - } - - if (this.presMap['userId']) { - pres.c('userId').t(this.presMap['userId']).up(); - } - - if (this.presMap['displayName']) { - // XEP-0172 - pres.c('nick', {xmlns: 'http://jabber.org/protocol/nick'}) - .t(this.presMap['displayName']).up(); - } - - if(this.presMap["devices"]) - { - pres.c('devices').c('audio').t(this.presMap['devices'].audio).up() - .c('video').t(this.presMap['devices'].video).up().up(); - } - if (this.presMap['audions']) { - pres.c('audiomuted', {xmlns: this.presMap['audions']}) - .t(this.presMap['audiomuted']).up(); - } - - if (this.presMap['videons']) { - pres.c('videomuted', {xmlns: this.presMap['videons']}) - .t(this.presMap['videomuted']).up(); - } - - if (this.presMap['videoTypeNs']) { - pres.c('videoType', { xmlns: this.presMap['videoTypeNs'] }) - .t(this.presMap['videoType']).up(); - } - - if (this.presMap['statsns']) { - var stats = pres.c('stats', {xmlns: this.presMap['statsns']}); - for (var stat in this.presMap["stats"]) - if (this.presMap["stats"][stat] != null) - stats.c("stat", {name: stat, value: this.presMap["stats"][stat]}).up(); - pres.up(); - } - - if (this.presMap['prezins']) { - pres.c('prezi', - {xmlns: this.presMap['prezins'], - 'url': this.presMap['preziurl']}) - .c('current').t(this.presMap['prezicurrent']).up().up(); - } - - // This is only for backward compatibility with clients which - // don't support getting sources from Jingle (i.e. jirecon). - if (this.presMap['medians']) { - pres.c('media', {xmlns: this.presMap['medians']}); - var sourceNumber = 0; - Object.keys(this.presMap).forEach(function (key) { - if (key.indexOf('source') >= 0) { - sourceNumber++; - } - }); - if (sourceNumber > 0) { - for (var i = 1; i <= sourceNumber / 3; i++) { - pres.c('source', - { - type: this.presMap['source' + i + '_type'], - ssrc: this.presMap['source' + i + '_ssrc'], - direction: this.presMap['source' + i + '_direction'] - || 'sendrecv' - } - ).up(); - } - } - pres.up(); - } - - if(this.presMap["startMuted"] !== undefined) - { - pres.c("startmuted", {audio: this.presMap["startMuted"].audio, - video: this.presMap["startMuted"].video, - xmlns: "http://jitsi.org/jitmeet/start-muted"}); - delete this.presMap["startMuted"]; - } - - if (config.token) { - pres.c('token', { xmlns: 'http://jitsi.org/jitmeet/auth-token'}).t(config.token).up(); - } - - pres.up(); - this.connection.send(pres); - }, - addDisplayNameToPresence: function (displayName) { - this.presMap['displayName'] = displayName; - }, - // This is only for backward compatibility with clients which - // don't support getting sources from Jingle (i.e. jirecon). - addMediaToPresence: function (sourceNumber, mtype, ssrcs, direction) { - if (!this.presMap['medians']) - this.presMap['medians'] = 'http://estos.de/ns/mjs'; - - this.presMap['source' + sourceNumber + '_type'] = mtype; - this.presMap['source' + sourceNumber + '_ssrc'] = ssrcs; - this.presMap['source' + sourceNumber + '_direction'] = direction; - }, - // This is only for backward compatibility with clients which - // don't support getting sources from Jingle (i.e. jirecon). - clearPresenceMedia: function () { - var self = this; - Object.keys(this.presMap).forEach(function (key) { - if (key.indexOf('source') != -1) { - delete self.presMap[key]; - } - }); - }, - addDevicesToPresence: function (devices) { - this.presMap['devices'] = devices; - }, - /** - * Adds the info about the type of our video stream. - * @param videoType 'camera' or 'screen' - */ - addVideoTypeToPresence: function (videoType) { - this.presMap['videoTypeNs'] = 'http://jitsi.org/jitmeet/video'; - this.presMap['videoType'] = videoType; - }, - addPreziToPresence: function (url, currentSlide) { - this.presMap['prezins'] = 'http://jitsi.org/jitmeet/prezi'; - this.presMap['preziurl'] = url; - this.presMap['prezicurrent'] = currentSlide; - }, - removePreziFromPresence: function () { - delete this.presMap['prezins']; - delete this.presMap['preziurl']; - delete this.presMap['prezicurrent']; - }, - addCurrentSlideToPresence: function (currentSlide) { - this.presMap['prezicurrent'] = currentSlide; - }, - getPrezi: function (roomjid) { - return this.preziMap[roomjid]; - }, - addAudioInfoToPresence: function (isMuted) { - this.presMap['audions'] = 'http://jitsi.org/jitmeet/audio'; - this.presMap['audiomuted'] = isMuted.toString(); - }, - addVideoInfoToPresence: function (isMuted) { - this.presMap['videons'] = 'http://jitsi.org/jitmeet/video'; - this.presMap['videomuted'] = isMuted.toString(); - }, - addConnectionInfoToPresence: function (stats) { - this.presMap['statsns'] = 'http://jitsi.org/jitmeet/stats'; - this.presMap['stats'] = stats; - }, - findJidFromResource: function (resourceJid) { - if (resourceJid && - resourceJid === Strophe.getResourceFromJid(this.myroomjid)) { - return this.myroomjid; - } - var peerJid = null; - Object.keys(this.members).some(function (jid) { - peerJid = jid; - return Strophe.getResourceFromJid(jid) === resourceJid; - }); - return peerJid; - }, - addBridgeIsDownToPresence: function () { - this.presMap['bridgeIsDown'] = true; - }, - addEmailToPresence: function (email) { - this.presMap['email'] = email; - }, - addUserIdToPresence: function (userId) { - this.presMap['userId'] = userId; - }, - addStartMutedToPresence: function (audio, video) { - this.presMap["startMuted"] = {audio: audio, video: video}; - }, - isModerator: function () { - return this.role === 'moderator'; - }, - getMemberRole: function (peerJid) { - if (this.members[peerJid]) { - return this.members[peerJid].role; - } - return null; - }, - onParticipantLeft: function (jid) { - - eventEmitter.emit(XMPPEvents.MUC_MEMBER_LEFT, jid); - - this.connection.jingle.terminateByJid(jid); - - if (this.getPrezi(jid)) { - $(document).trigger('presentationremoved.muc', - [jid, this.getPrezi(jid)]); - } - - Moderator.onMucMemberLeft(jid); - }, - parsePresence: function (from, member, pres) { - if($(pres).find(">bridgeIsDown").length > 0 && !this.bridgeIsDown) { - this.bridgeIsDown = true; - eventEmitter.emit(XMPPEvents.BRIDGE_DOWN); - } - - if(member.isFocus) - return; - - var displayName = !config.displayJids - ? member.displayName : Strophe.getResourceFromJid(from); - - if (displayName && displayName.length > 0) { - eventEmitter.emit(XMPPEvents.DISPLAY_NAME_CHANGED, from, displayName); - } - - var id = $(pres).find('>userID').text(); - var email = $(pres).find('>email'); - if (email.length > 0) { - id = email.text(); - } - - eventEmitter.emit(XMPPEvents.USER_ID_CHANGED, from, id); - } - }); -}; - diff --git a/modules/xmpp/strophe.jingle.js b/modules/xmpp/strophe.jingle.js deleted file mode 100644 index 7e4a581ef..000000000 --- a/modules/xmpp/strophe.jingle.js +++ /dev/null @@ -1,341 +0,0 @@ -/* jshint -W117 */ -/* jshint -W101 */ - -var JingleSession = require("./JingleSessionPC"); -var XMPPEvents = require("../../service/xmpp/XMPPEvents"); -var RTCBrowserType = require("../RTC/RTCBrowserType"); - - -module.exports = function(XMPP, eventEmitter) { - Strophe.addConnectionPlugin('jingle', { - connection: null, - sessions: {}, - jid2session: {}, - ice_config: {iceServers: []}, - pc_constraints: {}, - activecall: null, - media_constraints: { - mandatory: { - 'OfferToReceiveAudio': true, - 'OfferToReceiveVideo': true - } - // MozDontOfferDataChannel: true when this is firefox - }, - init: function (conn) { - this.connection = conn; - if (this.connection.disco) { - // http://xmpp.org/extensions/xep-0167.html#support - // http://xmpp.org/extensions/xep-0176.html#support - this.connection.disco.addFeature('urn:xmpp:jingle:1'); - this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:1'); - this.connection.disco.addFeature('urn:xmpp:jingle:transports:ice-udp:1'); - this.connection.disco.addFeature('urn:xmpp:jingle:apps:dtls:0'); - this.connection.disco.addFeature('urn:xmpp:jingle:transports:dtls-sctp:1'); - this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:audio'); - this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:video'); - - if (RTCBrowserType.isChrome() || RTCBrowserType.isOpera() || - RTCBrowserType.isTemasysPluginUsed()) { - this.connection.disco.addFeature('urn:ietf:rfc:4588'); - } - - // this is dealt with by SDP O/A so we don't need to announce this - //this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtcp-fb:0'); // XEP-0293 - //this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtp-hdrext:0'); // XEP-0294 - - this.connection.disco.addFeature('urn:ietf:rfc:5761'); // rtcp-mux - this.connection.disco.addFeature('urn:ietf:rfc:5888'); // a=group, e.g. bundle - - //this.connection.disco.addFeature('urn:ietf:rfc:5576'); // a=ssrc - } - this.connection.addHandler(this.onJingle.bind(this), 'urn:xmpp:jingle:1', 'iq', 'set', null, null); - }, - onJingle: function (iq) { - var sid = $(iq).find('jingle').attr('sid'); - var action = $(iq).find('jingle').attr('action'); - var fromJid = iq.getAttribute('from'); - // send ack first - var ack = $iq({type: 'result', - to: fromJid, - id: iq.getAttribute('id') - }); - console.log('on jingle ' + action + ' from ' + fromJid, iq); - var sess = this.sessions[sid]; - if ('session-initiate' != action) { - if (sess === null) { - ack.type = 'error'; - ack.c('error', {type: 'cancel'}) - .c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up() - .c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'}); - this.connection.send(ack); - return true; - } - // local jid is not checked - if (fromJid != sess.peerjid) { - console.warn('jid mismatch for session id', sid, fromJid, sess.peerjid); - ack.type = 'error'; - ack.c('error', {type: 'cancel'}) - .c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up() - .c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'}); - this.connection.send(ack); - return true; - } - } else if (sess !== undefined) { - // existing session with same session id - // this might be out-of-order if the sess.peerjid is the same as from - ack.type = 'error'; - ack.c('error', {type: 'cancel'}) - .c('service-unavailable', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up(); - console.warn('duplicate session id', sid); - this.connection.send(ack); - return true; - } - // FIXME: check for a defined action - this.connection.send(ack); - // see http://xmpp.org/extensions/xep-0166.html#concepts-session - switch (action) { - case 'session-initiate': - console.log("(TIME) received session-initiate:\t", - window.performance.now(), iq); - var startMuted = $(iq).find('jingle>startmuted'); - if (startMuted && startMuted.length > 0) { - var audioMuted = startMuted.attr("audio"); - var videoMuted = startMuted.attr("video"); - eventEmitter.emit(XMPPEvents.START_MUTED_FROM_FOCUS, - audioMuted === "true", videoMuted === "true"); - } - sess = new JingleSession( - $(iq).attr('to'), $(iq).find('jingle').attr('sid'), - this.connection, XMPP, eventEmitter); - // configure session - - sess.media_constraints = this.media_constraints; - sess.pc_constraints = this.pc_constraints; - sess.ice_config = this.ice_config; - - sess.initialize(fromJid, false); - // FIXME: setRemoteDescription should only be done when this call is to be accepted - sess.setOffer($(iq).find('>jingle')); - - this.sessions[sess.sid] = sess; - this.jid2session[sess.peerjid] = sess; - - // the callback should either - // .sendAnswer and .accept - // or .sendTerminate -- not necessarily synchronous - - // TODO: do we check activecall == null? - this.connection.jingle.activecall = sess; - - eventEmitter.emit(XMPPEvents.CALL_INCOMING, sess); - - // TODO: check affiliation and/or role - console.log('emuc data for', sess.peerjid, - this.connection.emuc.members[sess.peerjid]); - sess.sendAnswer(); - sess.accept(); - break; - case 'session-accept': - sess.setAnswer($(iq).find('>jingle')); - sess.accept(); - $(document).trigger('callaccepted.jingle', [sess.sid]); - break; - case 'session-terminate': - if (!sess) { - break; - } - console.log('terminating...', sess.sid); - sess.terminate(); - this.terminate(sess.sid); - if ($(iq).find('>jingle>reason').length) { - $(document).trigger('callterminated.jingle', [ - sess.sid, - sess.peerjid, - $(iq).find('>jingle>reason>:first')[0].tagName, - $(iq).find('>jingle>reason>text').text() - ]); - } else { - $(document).trigger('callterminated.jingle', - [sess.sid, sess.peerjid]); - } - break; - case 'transport-info': - sess.addIceCandidate($(iq).find('>jingle>content')); - break; - case 'session-info': - var affected; - if ($(iq).find('>jingle>ringing[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) { - $(document).trigger('ringing.jingle', [sess.sid]); - } else if ($(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) { - affected = $(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name'); - $(document).trigger('mute.jingle', [sess.sid, affected]); - } else if ($(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) { - affected = $(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name'); - $(document).trigger('unmute.jingle', [sess.sid, affected]); - } - break; - case 'addsource': // FIXME: proprietary, un-jingleish - case 'source-add': // FIXME: proprietary - console.info("source-add", iq); - sess.addSource($(iq).find('>jingle>content')); - break; - case 'removesource': // FIXME: proprietary, un-jingleish - case 'source-remove': // FIXME: proprietary - console.info("source-remove", iq); - sess.removeSource($(iq).find('>jingle>content')); - break; - default: - console.warn('jingle action not implemented', action); - break; - } - return true; - }, - initiate: function (peerjid, myjid) { // initiate a new jinglesession to peerjid - var sess = new JingleSession(myjid || this.connection.jid, - Math.random().toString(36).substr(2, 12), // random string - this.connection, XMPP, eventEmitter); - // configure session - - sess.media_constraints = this.media_constraints; - sess.pc_constraints = this.pc_constraints; - sess.ice_config = this.ice_config; - - sess.initialize(peerjid, true); - this.sessions[sess.sid] = sess; - this.jid2session[sess.peerjid] = sess; - sess.sendOffer(); - return sess; - }, - terminate: function (sid, reason, text) { // terminate by sessionid (or all sessions) - if (sid === null || sid === undefined) { - for (sid in this.sessions) { - if (this.sessions[sid].state != 'ended') { - this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text); - this.sessions[sid].terminate(); - } - delete this.jid2session[this.sessions[sid].peerjid]; - delete this.sessions[sid]; - } - } else if (this.sessions.hasOwnProperty(sid)) { - if (this.sessions[sid].state != 'ended') { - this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text); - this.sessions[sid].terminate(); - } - delete this.jid2session[this.sessions[sid].peerjid]; - delete this.sessions[sid]; - } - }, - // Used to terminate a session when an unavailable presence is received. - terminateByJid: function (jid) { - if (this.jid2session.hasOwnProperty(jid)) { - var sess = this.jid2session[jid]; - if (sess) { - sess.terminate(); - console.log('peer went away silently', jid); - delete this.sessions[sess.sid]; - delete this.jid2session[jid]; - $(document).trigger('callterminated.jingle', - [sess.sid, jid], 'gone'); - } - } - }, - terminateRemoteByJid: function (jid, reason) { - if (this.jid2session.hasOwnProperty(jid)) { - var sess = this.jid2session[jid]; - if (sess) { - sess.sendTerminate(reason || (!sess.active()) ? 'kick' : null); - sess.terminate(); - console.log('terminate peer with jid', sess.sid, jid); - delete this.sessions[sess.sid]; - delete this.jid2session[jid]; - $(document).trigger('callterminated.jingle', - [sess.sid, jid, 'kicked']); - } - } - }, - getStunAndTurnCredentials: function () { - // get stun and turn configuration from server via xep-0215 - // uses time-limited credentials as described in - // http://tools.ietf.org/html/draft-uberti-behave-turn-rest-00 - // - // see https://code.google.com/p/prosody-modules/source/browse/mod_turncredentials/mod_turncredentials.lua - // for a prosody module which implements this - // - // currently, this doesn't work with updateIce and therefore credentials with a long - // validity have to be fetched before creating the peerconnection - // TODO: implement refresh via updateIce as described in - // https://code.google.com/p/webrtc/issues/detail?id=1650 - var self = this; - this.connection.sendIQ( - $iq({type: 'get', to: this.connection.domain}) - .c('services', {xmlns: 'urn:xmpp:extdisco:1'}).c('service', {host: 'turn.' + this.connection.domain}), - function (res) { - var iceservers = []; - $(res).find('>services>service').each(function (idx, el) { - el = $(el); - var dict = {}; - var type = el.attr('type'); - switch (type) { - case 'stun': - dict.url = 'stun:' + el.attr('host'); - if (el.attr('port')) { - dict.url += ':' + el.attr('port'); - } - iceservers.push(dict); - break; - case 'turn': - case 'turns': - dict.url = type + ':'; - if (el.attr('username')) { // https://code.google.com/p/webrtc/issues/detail?id=1508 - if (navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./) && parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10) < 28) { - dict.url += el.attr('username') + '@'; - } else { - dict.username = el.attr('username'); // only works in M28 - } - } - dict.url += el.attr('host'); - if (el.attr('port') && el.attr('port') != '3478') { - dict.url += ':' + el.attr('port'); - } - if (el.attr('transport') && el.attr('transport') != 'udp') { - dict.url += '?transport=' + el.attr('transport'); - } - if (el.attr('password')) { - dict.credential = el.attr('password'); - } - iceservers.push(dict); - break; - } - }); - self.ice_config.iceServers = iceservers; - }, - function (err) { - console.warn('getting turn credentials failed', err); - console.warn('is mod_turncredentials or similar installed?'); - } - ); - // implement push? - }, - - /** - * Returns the data saved in 'updateLog' in a format to be logged. - */ - getLog: function () { - var data = {}; - var self = this; - Object.keys(this.sessions).forEach(function (sid) { - var session = self.sessions[sid]; - if (session.peerconnection && session.peerconnection.updateLog) { - // FIXME: should probably be a .dump call - data["jingle_" + session.sid] = { - updateLog: session.peerconnection.updateLog, - stats: session.peerconnection.stats, - url: window.location.href - }; - } - }); - return data; - } - }); -}; - diff --git a/modules/xmpp/strophe.logger.js b/modules/xmpp/strophe.logger.js deleted file mode 100644 index 4866ff89b..000000000 --- a/modules/xmpp/strophe.logger.js +++ /dev/null @@ -1,20 +0,0 @@ -/* global Strophe */ -module.exports = function () { - - Strophe.addConnectionPlugin('logger', { - // logs raw stanzas and makes them available for download as JSON - connection: null, - log: [], - init: function (conn) { - this.connection = conn; - this.connection.rawInput = this.log_incoming.bind(this); - this.connection.rawOutput = this.log_outgoing.bind(this); - }, - log_incoming: function (stanza) { - this.log.push([new Date().getTime(), 'incoming', stanza]); - }, - log_outgoing: function (stanza) { - this.log.push([new Date().getTime(), 'outgoing', stanza]); - } - }); -}; \ No newline at end of file diff --git a/modules/xmpp/strophe.moderate.js b/modules/xmpp/strophe.moderate.js deleted file mode 100644 index 0372388be..000000000 --- a/modules/xmpp/strophe.moderate.js +++ /dev/null @@ -1,60 +0,0 @@ -/* global $, $iq, config, connection, focusMucJid, forceMuted, Strophe */ -/** - * Moderate connection plugin. - */ -var XMPPEvents = require("../../service/xmpp/XMPPEvents"); - -module.exports = function (XMPP, eventEmitter) { - Strophe.addConnectionPlugin('moderate', { - connection: null, - init: function (conn) { - this.connection = conn; - - this.connection.addHandler(this.onMute.bind(this), - 'http://jitsi.org/jitmeet/audio', - 'iq', - 'set', - null, - null); - }, - setMute: function (jid, mute) { - console.info("set mute", mute); - var iqToFocus = - $iq({to: this.connection.emuc.focusMucJid, type: 'set'}) - .c('mute', { - xmlns: 'http://jitsi.org/jitmeet/audio', - jid: jid - }) - .t(mute.toString()) - .up(); - - this.connection.sendIQ( - iqToFocus, - function (result) { - console.log('set mute', result); - }, - function (error) { - console.log('set mute error', error); - }); - }, - onMute: function (iq) { - var from = iq.getAttribute('from'); - if (from !== this.connection.emuc.focusMucJid) { - console.warn("Ignored mute from non focus peer"); - return false; - } - var mute = $(iq).find('mute'); - if (mute.length) { - var doMuteAudio = mute.text() === "true"; - eventEmitter.emit(XMPPEvents.AUDIO_MUTED_BY_FOCUS, doMuteAudio); - XMPP.forceMuted = doMuteAudio; - } - return true; - }, - eject: function (jid) { - // We're not the focus, so can't terminate - //connection.jingle.terminateRemoteByJid(jid, 'kick'); - this.connection.emuc.kick(jid); - } - }); -}; diff --git a/modules/xmpp/strophe.ping.js b/modules/xmpp/strophe.ping.js deleted file mode 100644 index 1ac534595..000000000 --- a/modules/xmpp/strophe.ping.js +++ /dev/null @@ -1,121 +0,0 @@ -/* global $, $iq, Strophe */ - -var XMPPEvents = require("../../service/xmpp/XMPPEvents"); - -/** - * Ping every 20 sec - */ -var PING_INTERVAL = 20000; - -/** - * Ping timeout error after 15 sec of waiting. - */ -var PING_TIMEOUT = 15000; - -/** - * Will close the connection after 3 consecutive ping errors. - */ -var PING_THRESHOLD = 3; - -/** - * XEP-0199 ping plugin. - * - * Registers "urn:xmpp:ping" namespace under Strophe.NS.PING. - */ -module.exports = function (XMPP, eventEmitter) { - Strophe.addConnectionPlugin('ping', { - - connection: null, - - failedPings: 0, - - /** - * Initializes the plugin. Method called by Strophe. - * @param connection Strophe connection instance. - */ - init: function (connection) { - this.connection = connection; - Strophe.addNamespace('PING', "urn:xmpp:ping"); - }, - - /** - * Sends "ping" to given jid - * @param jid the JID to which ping request will be sent. - * @param success callback called on success. - * @param error callback called on error. - * @param timeout ms how long are we going to wait for the response. On - * timeout error callback is called with undefined error - * argument. - */ - ping: function (jid, success, error, timeout) { - var iq = $iq({type: 'get', to: jid}); - iq.c('ping', {xmlns: Strophe.NS.PING}); - this.connection.sendIQ(iq, success, error, timeout); - }, - - /** - * Checks if given jid has XEP-0199 ping support. - * @param jid the JID to be checked for ping support. - * @param callback function with boolean argument which will be - * true if XEP-0199 ping is supported by given jid - */ - hasPingSupport: function (jid, callback) { - this.connection.disco.info( - jid, null, - function (result) { - var ping = $(result).find('>>feature[var="urn:xmpp:ping"]'); - callback(ping.length > 0); - }, - function (error) { - console.error("Ping feature discovery error", error); - callback(false); - } - ); - }, - - /** - * Starts to send ping in given interval to specified remote JID. - * This plugin supports only one such task and stopInterval - * must be called before starting a new one. - * @param remoteJid remote JID to which ping requests will be sent to. - * @param interval task interval in ms. - */ - startInterval: function (remoteJid, interval) { - if (this.intervalId) { - console.error("Ping task scheduled already"); - return; - } - if (!interval) - interval = PING_INTERVAL; - var self = this; - this.intervalId = window.setInterval(function () { - self.ping(remoteJid, - function (result) { - // Ping OK - self.failedPings = 0; - }, - function (error) { - self.failedPings += 1; - console.error( - "Ping " + (error ? "error" : "timeout"), error); - if (self.failedPings >= PING_THRESHOLD) { - self.connection.disconnect(); - } - }, PING_TIMEOUT); - }, interval); - console.info("XMPP pings will be sent every " + interval + " ms"); - }, - - /** - * Stops current "ping" interval task. - */ - stopInterval: function () { - if (this.intervalId) { - window.clearInterval(this.intervalId); - this.intervalId = null; - this.failedPings = 0; - console.info("Ping interval cleared"); - } - } - }); -}; diff --git a/modules/xmpp/strophe.rayo.js b/modules/xmpp/strophe.rayo.js deleted file mode 100644 index bc9811b64..000000000 --- a/modules/xmpp/strophe.rayo.js +++ /dev/null @@ -1,96 +0,0 @@ -/* jshint -W117 */ -module.exports = function() { - Strophe.addConnectionPlugin('rayo', - { - RAYO_XMLNS: 'urn:xmpp:rayo:1', - connection: null, - init: function (conn) { - this.connection = conn; - if (this.connection.disco) { - this.connection.disco.addFeature('urn:xmpp:rayo:client:1'); - } - - this.connection.addHandler( - this.onRayo.bind(this), this.RAYO_XMLNS, 'iq', 'set', - null, null); - }, - onRayo: function (iq) { - console.info("Rayo IQ", iq); - }, - dial: function (to, from, roomName, roomPass) { - var self = this; - var req = $iq( - { - type: 'set', - to: this.connection.emuc.focusMucJid - } - ); - req.c('dial', - { - xmlns: this.RAYO_XMLNS, - to: to, - from: from - }); - req.c('header', - { - name: 'JvbRoomName', - value: roomName - }).up(); - - if (roomPass !== null && roomPass.length) { - - req.c('header', - { - name: 'JvbRoomPassword', - value: roomPass - }).up(); - } - - this.connection.sendIQ( - req, - function (result) { - console.info('Dial result ', result); - - var resource = $(result).find('ref').attr('uri'); - self.call_resource = resource.substr('xmpp:'.length); - console.info( - "Received call resource: " + self.call_resource); - }, - function (error) { - console.info('Dial error ', error); - } - ); - }, - hang_up: function () { - if (!this.call_resource) { - console.warn("No call in progress"); - return; - } - - var self = this; - var req = $iq( - { - type: 'set', - to: this.call_resource - } - ); - req.c('hangup', - { - xmlns: this.RAYO_XMLNS - }); - - this.connection.sendIQ( - req, - function (result) { - console.info('Hangup result ', result); - self.call_resource = null; - }, - function (error) { - console.info('Hangup error ', error); - self.call_resource = null; - } - ); - } - } - ); -}; diff --git a/modules/xmpp/strophe.util.js b/modules/xmpp/strophe.util.js deleted file mode 100644 index 56a26d54f..000000000 --- a/modules/xmpp/strophe.util.js +++ /dev/null @@ -1,43 +0,0 @@ -/* global Strophe */ -/** - * Strophe logger implementation. Logs from level WARN and above. - */ -module.exports = function () { - - Strophe.log = function (level, msg) { - switch (level) { - case Strophe.LogLevel.WARN: - console.warn("Strophe: " + msg); - break; - case Strophe.LogLevel.ERROR: - case Strophe.LogLevel.FATAL: - console.error("Strophe: " + msg); - break; - } - }; - - Strophe.getStatusString = function (status) { - switch (status) { - case Strophe.Status.ERROR: - return "ERROR"; - case Strophe.Status.CONNECTING: - return "CONNECTING"; - case Strophe.Status.CONNFAIL: - return "CONNFAIL"; - case Strophe.Status.AUTHENTICATING: - return "AUTHENTICATING"; - case Strophe.Status.AUTHFAIL: - return "AUTHFAIL"; - case Strophe.Status.CONNECTED: - return "CONNECTED"; - case Strophe.Status.DISCONNECTED: - return "DISCONNECTED"; - case Strophe.Status.DISCONNECTING: - return "DISCONNECTING"; - case Strophe.Status.ATTACHED: - return "ATTACHED"; - default: - return "unknown"; - } - }; -}; diff --git a/modules/xmpp/xmpp.js b/modules/xmpp/xmpp.js deleted file mode 100644 index 3a980a5a4..000000000 --- a/modules/xmpp/xmpp.js +++ /dev/null @@ -1,616 +0,0 @@ -/* global $, APP, config, Strophe, Base64, $msg */ -/* jshint -W101 */ -var Moderator = require("./moderator"); -var EventEmitter = require("events"); -var Recording = require("./recording"); -var SDP = require("./SDP"); -var SDPUtil = require("./SDPUtil"); -var Settings = require("../settings/Settings"); -var Pako = require("pako"); -var StreamEventTypes = require("../../service/RTC/StreamEventTypes"); -var RTCEvents = require("../../service/RTC/RTCEvents"); -var XMPPEvents = require("../../service/xmpp/XMPPEvents"); -var retry = require('retry'); -var RandomUtil = require("../util/RandomUtil"); - -var eventEmitter = new EventEmitter(); -var connection = null; -var authenticatedUser = false; - -/** - * Utility method that generates user name based on random hex values. - * Eg. 12345678-1234-1234-12345678 - * @returns {string} - */ -function generateUserName() { - return RandomUtil.randomHexString(8) + "-" + RandomUtil.randomHexString(4) - + "-" + RandomUtil.randomHexString(4) + "-" - + RandomUtil.randomHexString(8); -} - -function connect(jid, password) { - - var faultTolerantConnect = retry.operation({ - retries: 3 - }); - - // fault tolerant connect - faultTolerantConnect.attempt(function () { - - connection = XMPP.createConnection(); - Moderator.setConnection(connection); - - connection.jingle.pc_constraints = APP.RTC.getPCConstraints(); - if (config.useIPv6) { - // https://code.google.com/p/webrtc/issues/detail?id=2828 - if (!connection.jingle.pc_constraints.optional) - connection.jingle.pc_constraints.optional = []; - connection.jingle.pc_constraints.optional.push({googIPv6: true}); - } - - // Include user info in MUC presence - var settings = Settings.getSettings(); - if (settings.email) { - connection.emuc.addEmailToPresence(settings.email); - } - if (settings.uid) { - connection.emuc.addUserIdToPresence(settings.uid); - } - if (settings.displayName) { - connection.emuc.addDisplayNameToPresence(settings.displayName); - } - - - // connection.connect() starts the connection process. - // - // As the connection process proceeds, the user supplied callback will - // be triggered multiple times with status updates. The callback should - // take two arguments - the status code and the error condition. - // - // The status code will be one of the values in the Strophe.Status - // constants. The error condition will be one of the conditions defined - // in RFC 3920 or the condition ‘strophe-parsererror’. - // - // The Parameters wait, hold and route are optional and only relevant - // for BOSH connections. Please see XEP 124 for a more detailed - // explanation of the optional parameters. - // - // Connection status constants for use by the connection handler - // callback. - // - // Status.ERROR - An error has occurred (websockets specific) - // Status.CONNECTING - The connection is currently being made - // Status.CONNFAIL - The connection attempt failed - // Status.AUTHENTICATING - The connection is authenticating - // Status.AUTHFAIL - The authentication attempt failed - // Status.CONNECTED - The connection has succeeded - // Status.DISCONNECTED - The connection has been terminated - // Status.DISCONNECTING - The connection is currently being terminated - // Status.ATTACHED - The connection has been attached - - var anonymousConnectionFailed = false; - var connectionFailed = false; - var lastErrorMsg; - connection.connect(jid, password, function (status, msg) { - console.log("(TIME) Strophe " + Strophe.getStatusString(status) + - (msg ? "[" + msg + "]" : "") + - "\t:" + window.performance.now()); - if (status === Strophe.Status.CONNECTED) { - if (config.useStunTurn) { - connection.jingle.getStunAndTurnCredentials(); - } - - console.info("My Jabber ID: " + connection.jid); - - // Schedule ping ? - var pingJid = connection.domain; - connection.ping.hasPingSupport( - pingJid, - function (hasPing) { - if (hasPing) - connection.ping.startInterval(pingJid); - else - console.warn("Ping NOT supported by " + pingJid); - } - ); - - if (password) - authenticatedUser = true; - maybeDoJoin(); - } else if (status === Strophe.Status.CONNFAIL) { - if (msg === 'x-strophe-bad-non-anon-jid') { - anonymousConnectionFailed = true; - } else { - connectionFailed = true; - } - lastErrorMsg = msg; - } else if (status === Strophe.Status.DISCONNECTED) { - // Stop ping interval - connection.ping.stopInterval(); - if (anonymousConnectionFailed) { - // prompt user for username and password - XMPP.promptLogin(); - } else { - - // Strophe already has built-in HTTP/BOSH error handling and - // request retry logic. Requests are resent automatically - // until their error count reaches 5. Strophe.js disconnects - // if the error count is > 5. We are not replicating this - // here. - // - // The "problem" is that failed HTTP/BOSH requests don't - // trigger a callback with a status update, so when a - // callback with status Strophe.Status.DISCONNECTED arrives, - // we can't be sure if it's a graceful disconnect or if it's - // triggered by some HTTP/BOSH error. - // - // But that's a minor issue in Jitsi Meet as we never - // disconnect anyway, not even when the user closes the - // browser window (which is kind of wrong, but the point is - // that we should never ever get disconnected). - // - // On the other hand, failed connections due to XMPP layer - // errors, trigger a callback with status Strophe.Status.CONNFAIL. - // - // Here we implement retry logic for failed connections due - // to XMPP layer errors and we display an error to the user - // if we get disconnected from the XMPP server permanently. - - // If the connection failed, retry. - if (connectionFailed && - faultTolerantConnect.retry("connection-failed")) { - return; - } - - // If we failed to connect to the XMPP server, fire an event - // to let all the interested module now about it. - eventEmitter.emit(XMPPEvents.CONNECTION_FAILED, - msg ? msg : lastErrorMsg); - } - } else if (status === Strophe.Status.AUTHFAIL) { - // wrong password or username, prompt user - XMPP.promptLogin(); - - } - }); - }); -} - - -function maybeDoJoin() { - if (connection && connection.connected && - Strophe.getResourceFromJid(connection.jid) && - (APP.RTC.localAudio || APP.RTC.localVideo)) { - // .connected is true while connecting? - doJoin(); - } -} - -function doJoin() { - eventEmitter.emit(XMPPEvents.READY_TO_JOIN); -} - -function initStrophePlugins() -{ - require("./strophe.emuc")(XMPP, eventEmitter); - require("./strophe.jingle")(XMPP, eventEmitter); - require("./strophe.moderate")(XMPP, eventEmitter); - require("./strophe.util")(); - require("./strophe.ping")(XMPP, eventEmitter); - require("./strophe.rayo")(); - require("./strophe.logger")(); -} - -/** - * If given localStream is video one this method will advertise it's - * video type in MUC presence. - * @param localStream new or modified LocalStream. - */ -function broadcastLocalVideoType(localStream) { - if (localStream.videoType) - XMPP.addToPresence('videoType', localStream.videoType); -} - -function registerListeners() { - APP.RTC.addStreamListener( - function (localStream) { - maybeDoJoin(); - broadcastLocalVideoType(localStream); - }, - StreamEventTypes.EVENT_TYPE_LOCAL_CREATED - ); - APP.RTC.addStreamListener( - broadcastLocalVideoType, - StreamEventTypes.EVENT_TYPE_LOCAL_CHANGED - ); - APP.RTC.addListener(RTCEvents.AVAILABLE_DEVICES_CHANGED, function (devices) { - XMPP.addToPresence("devices", devices); - }); -} - -var unload = (function () { - var unloaded = false; - - return function () { - if (unloaded) { return; } - unloaded = true; - - if (connection && connection.connected) { - // ensure signout - $.ajax({ - type: 'POST', - url: config.bosh, - async: false, - cache: false, - contentType: 'application/xml', - data: "" + - "" + - "", - success: function (data) { - console.log('signed out'); - console.log(data); - }, - error: function (XMLHttpRequest, textStatus, errorThrown) { - console.log('signout error', - textStatus + ' (' + errorThrown + ')'); - } - }); - } - XMPP.disposeConference(true); - }; -})(); - -function setupEvents() { - // In recent versions of FF the 'beforeunload' event is not fired when the - // window or the tab is closed. It is only fired when we leave the page - // (change URL). If this participant doesn't unload properly, then it - // becomes a ghost for the rest of the participants that stay in the - // conference. Thankfully handling the 'unload' event in addition to the - // 'beforeunload' event seems to guarantee the execution of the 'unload' - // method at least once. - // - // The 'unload' method can safely be run multiple times, it will actually do - // something only the first time that it's run, so we're don't have to worry - // about browsers that fire both events. - - $(window).bind('beforeunload', unload); - $(window).bind('unload', unload); -} - -var XMPP = { - getConnection: function(){ return connection; }, - sessionTerminated: false, - - /** - * XMPP connection status - */ - Status: Strophe.Status, - - /** - * Remembers if we were muted by the focus. - * @type {boolean} - */ - forceMuted: false, - start: function () { - setupEvents(); - initStrophePlugins(); - registerListeners(); - Moderator.init(this, eventEmitter); - Recording.init(); - var configDomain = config.hosts.anonymousdomain || config.hosts.domain; - // Force authenticated domain if room is appended with '?login=true' - if (config.hosts.anonymousdomain && - window.location.search.indexOf("login=true") !== -1) { - configDomain = config.hosts.domain; - } - var jid = configDomain || window.location.hostname; - var password = null; - if (config.token) { - password = config.token; - if (config.id) { - jid = config.id + "@" + jid; - } else { - jid = generateUserName() + "@" + jid; - } - } - connect(jid, password); - }, - createConnection: function () { - var bosh = config.bosh || '/http-bind'; - // adds the room name used to the bosh connection - return new Strophe.Connection(bosh + '?room=' + APP.UI.getRoomNode()); - }, - getStatusString: function (status) { - return Strophe.getStatusString(status); - }, - promptLogin: function () { - eventEmitter.emit(XMPPEvents.PROMPT_FOR_LOGIN, connect); - }, - joinRoom: function(roomName, useNicks, nick) { - var roomjid = roomName; - - if (useNicks) { - if (nick) { - roomjid += '/' + nick; - } else { - roomjid += '/' + Strophe.getNodeFromJid(connection.jid); - } - } else { - var tmpJid = Strophe.getNodeFromJid(connection.jid); - - if(!authenticatedUser) - tmpJid = tmpJid.substr(0, 8); - - roomjid += '/' + tmpJid; - } - connection.emuc.doJoin(roomjid); - }, - myJid: function () { - if(!connection) - return null; - return connection.emuc.myroomjid; - }, - myResource: function () { - if(!connection || ! connection.emuc.myroomjid) - return null; - return Strophe.getResourceFromJid(connection.emuc.myroomjid); - }, - getLastPresence: function (from) { - if(!connection) - return null; - return connection.emuc.lastPresenceMap[from]; - }, - disposeConference: function (onUnload) { - var handler = connection.jingle.activecall; - if (handler && handler.peerconnection) { - // FIXME: probably removing streams is not required and close() should - // be enough - if (APP.RTC.localAudio) { - handler.peerconnection.removeStream( - APP.RTC.localAudio.getOriginalStream(), onUnload); - } - if (APP.RTC.localVideo) { - handler.peerconnection.removeStream( - APP.RTC.localVideo.getOriginalStream(), onUnload); - } - handler.peerconnection.close(); - } - eventEmitter.emit(XMPPEvents.DISPOSE_CONFERENCE, onUnload); - connection.jingle.activecall = null; - if (!onUnload) { - this.sessionTerminated = true; - connection.emuc.doLeave(); - } - }, - addListener: function(type, listener) { - eventEmitter.on(type, listener); - }, - removeListener: function (type, listener) { - eventEmitter.removeListener(type, listener); - }, - allocateConferenceFocus: function(roomName, callback) { - Moderator.allocateConferenceFocus(roomName, callback); - }, - getLoginUrl: function (roomName, callback) { - Moderator.getLoginUrl(roomName, callback); - }, - getPopupLoginUrl: function (roomName, callback) { - Moderator.getPopupLoginUrl(roomName, callback); - }, - isModerator: function () { - return Moderator.isModerator(); - }, - isSipGatewayEnabled: function () { - return Moderator.isSipGatewayEnabled(); - }, - isExternalAuthEnabled: function () { - return Moderator.isExternalAuthEnabled(); - }, - isConferenceInProgress: function () { - return connection && connection.jingle.activecall && - connection.jingle.activecall.peerconnection; - }, - switchStreams: function (stream, oldStream, callback, isAudio) { - if (this.isConferenceInProgress()) { - // FIXME: will block switchInProgress on true value in case of exception - connection.jingle.activecall.switchStreams(stream, oldStream, callback, isAudio); - } else { - // We are done immediately - console.warn("No conference handler or conference not started yet"); - callback(); - } - }, - sendVideoInfoPresence: function (mute) { - if(!connection) - return; - connection.emuc.addVideoInfoToPresence(mute); - connection.emuc.sendPresence(); - }, - setVideoMute: function (mute, callback, options) { - if(!connection) - return; - var self = this; - var localCallback = function (mute) { - self.sendVideoInfoPresence(mute); - return callback(mute); - }; - - if(connection.jingle.activecall) - { - connection.jingle.activecall.setVideoMute( - mute, localCallback, options); - } - else { - localCallback(mute); - } - - }, - setAudioMute: function (mute, callback) { - if (!(connection && APP.RTC.localAudio)) { - return false; - } - - if (this.forceMuted && !mute) { - console.info("Asking focus for unmute"); - connection.moderate.setMute(connection.emuc.myroomjid, mute); - // FIXME: wait for result before resetting muted status - this.forceMuted = false; - } - - if (mute == APP.RTC.localAudio.isMuted()) { - // Nothing to do - return true; - } - - APP.RTC.localAudio.setMute(mute); - this.sendAudioInfoPresence(mute, callback); - return true; - }, - sendAudioInfoPresence: function(mute, callback) { - if(connection) { - connection.emuc.addAudioInfoToPresence(mute); - connection.emuc.sendPresence(); - } - callback(); - return true; - }, - toggleRecording: function (tokenEmptyCallback, - recordingStateChangeCallback) { - Recording.toggleRecording(tokenEmptyCallback, - recordingStateChangeCallback, connection); - }, - addToPresence: function (name, value, dontSend) { - switch (name) { - case "displayName": - connection.emuc.addDisplayNameToPresence(value); - break; - case "prezi": - connection.emuc.addPreziToPresence(value, 0); - break; - case "preziSlide": - connection.emuc.addCurrentSlideToPresence(value); - break; - case "connectionQuality": - connection.emuc.addConnectionInfoToPresence(value); - break; - case "email": - connection.emuc.addEmailToPresence(value); - break; - case "devices": - connection.emuc.addDevicesToPresence(value); - break; - case "videoType": - connection.emuc.addVideoTypeToPresence(value); - break; - case "startMuted": - if(!Moderator.isModerator()) - return; - connection.emuc.addStartMutedToPresence(value[0], - value[1]); - break; - default : - console.log("Unknown tag for presence: " + name); - return; - } - if (!dontSend) - connection.emuc.sendPresence(); - }, - /** - * Sends 'data' as a log message to the focus. Returns true iff a message - * was sent. - * @param data - * @returns {boolean} true iff a message was sent. - */ - sendLogs: function (data) { - if(!connection.emuc.focusMucJid) - return false; - - var deflate = true; - - var content = JSON.stringify(data); - if (deflate) { - content = String.fromCharCode.apply(null, Pako.deflateRaw(content)); - } - content = Base64.encode(content); - // XEP-0337-ish - var message = $msg({to: connection.emuc.focusMucJid, type: 'normal'}); - message.c('log', { xmlns: 'urn:xmpp:eventlog', - id: 'PeerConnectionStats'}); - message.c('message').t(content).up(); - if (deflate) { - message.c('tag', {name: "deflated", value: "true"}).up(); - } - message.up(); - - connection.send(message); - return true; - }, - // Gets the logs from strophe.jingle. - getJingleLog: function () { - return connection.jingle ? connection.jingle.getLog() : {}; - }, - // Gets the logs from strophe. - getXmppLog: function () { - return connection.logger ? connection.logger.log : null; - }, - getPrezi: function () { - return connection.emuc.getPrezi(this.myJid()); - }, - removePreziFromPresence: function () { - connection.emuc.removePreziFromPresence(); - connection.emuc.sendPresence(); - }, - sendChatMessage: function (message, nickname) { - connection.emuc.sendMessage(message, nickname); - }, - setSubject: function (topic) { - connection.emuc.setSubject(topic); - }, - lockRoom: function (key, onSuccess, onError, onNotSupported) { - connection.emuc.lockRoom(key, onSuccess, onError, onNotSupported); - }, - dial: function (to, from, roomName,roomPass) { - connection.rayo.dial(to, from, roomName,roomPass); - }, - setMute: function (jid, mute) { - connection.moderate.setMute(jid, mute); - }, - eject: function (jid) { - connection.moderate.eject(jid); - }, - logout: function (callback) { - Moderator.logout(callback); - }, - findJidFromResource: function (resource) { - return connection.emuc.findJidFromResource(resource); - }, - getMembers: function () { - return connection.emuc.members; - }, - getJidFromSSRC: function (ssrc) { - if (!this.isConferenceInProgress()) - return null; - return connection.jingle.activecall.getSsrcOwner(ssrc); - }, - // Returns true iff we have joined the MUC. - isMUCJoined: function () { - return connection === null ? false : connection.emuc.joined; - }, - getSessions: function () { - return connection.jingle.sessions; - }, - removeStream: function (stream) { - if (!this.isConferenceInProgress()) - return; - connection.jingle.activecall.peerconnection.removeStream(stream); - }, - filter_special_chars: function (text) { - return SDPUtil.filter_special_chars(text); - } -}; - -module.exports = XMPP;