diff --git a/JitsiConference.js b/JitsiConference.js index 5f00c0789..dc53a69bd 100644 --- a/JitsiConference.js +++ b/JitsiConference.js @@ -1,3 +1,5 @@ +var RTC = require("./modules/RTC/RTC"); +var XMPPEvents = require("./service/xmpp/XMPPEvents"); /** * Creates a JitsiConference object with the given name and properties. @@ -14,6 +16,10 @@ function JitsiConference(options) { this.connection = this.options.connection; this.xmpp = this.connection.xmpp; this.room = this.xmpp.createRoom(this.options.name, null, null); + this.rtc = new RTC(); + this.xmpp.addListener(XMPPEvents.CALL_INCOMING, + this.rtc.onIncommingCall.bind(this.rtc)); + } /** @@ -41,14 +47,14 @@ JitsiConference.prototype.leave = function () { * or a JitsiConferenceError if rejected. */ JitsiConference.prototype.createLocalTracks = function (options) { - + this.rtc.obtainAudioAndVideoPermissions(); } /** * Returns the local tracks. */ JitsiConference.prototype.getLocalTracks = function () { - + return this.rtc.localStreams; }; @@ -61,7 +67,7 @@ JitsiConference.prototype.getLocalTracks = function () { * Note: consider adding eventing functionality by extending an EventEmitter impl, instead of rolling ourselves */ JitsiConference.prototype.on = function (eventId, handler) { - this.add.addListener(eventId, handler); + this.room.addListener(eventId, handler); } /** @@ -147,7 +153,15 @@ JitsiConference.prototype.setDisplayName = function(name) { * @param id the identifier of the participant */ JitsiConference.prototype.selectParticipant = function(participantId) { + this.rtc.selectedEndpoint(participantId); +} +/** + * + * @param id the identifier of the participant + */ +JitsiConference.prototype.pinParticipant = function(participantId) { + this.rtc.pinEndpoint(participantId); } /** diff --git a/JitsiTrack.js b/JitsiTrack.js index 163bafaeb..9654612cb 100644 --- a/JitsiTrack.js +++ b/JitsiTrack.js @@ -2,9 +2,9 @@ * Represents a single media track (either audio or video). * @constructor */ -function JitsiTrack() +function JitsiTrack(stream) { - + this.stream = stream; } /** @@ -23,34 +23,35 @@ JitsiTrack.AUDIO = "audio"; * Returns the type (audio or video) of this track. */ JitsiTrack.prototype.getType = function() { + return this.stream.type; }; /** * @returns {JitsiParticipant} to which this track belongs, or null if it is a local track. */ -JitsiTrack.prototype.getParitcipant() = function() { +JitsiTrack.prototype.getParitcipant = function() { }; /** * Returns the RTCMediaStream from the browser (?). */ -JitsiTrack.prototype.getOriginalStream() { - +JitsiTrack.prototype.getOriginalStream = function() { + return this.stream.getOriginalStream(); } /** * Mutes the track. */ JitsiTrack.prototype.mute = function () { - + this.stream.setMute(true); } /** * Unmutes the stream. */ JitsiTrack.prototype.unmute = function () { - + this.stream.setMute(false); } /** diff --git a/modules/RTC/RTC.js b/modules/RTC/RTC.js index 1a5b7e6b6..60b4fc6aa 100644 --- a/modules/RTC/RTC.js +++ b/modules/RTC/RTC.js @@ -12,9 +12,7 @@ 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(); - +var desktopsharing = require("../desktopsharing/desktopsharing"); function getMediaStreamUsage() { @@ -43,233 +41,263 @@ function getMediaStreamUsage() return result; } -var RTC = { - rtcUtils: null, - devices: { + +function RTC() +{ + this.rtcUtils = null; + this.devices = { audio: true, video: true - }, - localStreams: [], - 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"; + }; + this.localStreams = []; + this.remoteStreams = {}; + this.localAudio = null; + this.localVideo = null; + this.eventEmitter = new EventEmitter(); + var self = this; + desktopsharing.addListener( + function (stream, isUsingScreenStream, callback) { + self.changeLocalVideo(stream, isUsingScreenStream, callback); + }, DesktopSharingEventTypes.NEW_STREAM_CREATED); - eventEmitter.removeListener(eventType, listener); - }, - createLocalStream: function (stream, type, change, videoType, isMuted, isGUMStream) { + // 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 () { + self.eventEmitter.emit(RTCEvents.RTC_READY, true); + }; - var localStream = new LocalStream(stream, type, eventEmitter, videoType, isGUMStream); - //in firefox we have only one stream object - if(this.localStreams.length == 0 || - this.localStreams[0].getOriginalStream() != stream) - this.localStreams.push(localStream); - if(isMuted === true) + this.rtcUtils = new RTCUtils(this, onReady); + + // Call onReady() if Temasys plugin is not used + if (!RTCBrowserType.isTemasysPluginUsed()) { + onReady(); + } +} + +RTC.prototype.obtainAudioAndVideoPermissions = function () { + this.rtcUtils.obtainAudioAndVideoPermissions( + null, null, getMediaStreamUsage()); +} + +RTC.prototype.onIncommingCall = function(event) { + DataChannels.init(event.peerconnection, self.eventEmitter); +} + +RTC.prototype.selectedEndpoint = function (id) { + DataChannels.handleSelectedEndpointEvent(id); +} + +RTC.prototype.pinEndpoint = function (id) { + DataChannels.handlePinnedEndpointEvent(id); +} + +RTC.prototype.addStreamListener = function (listener, eventType) { + this.eventEmitter.on(eventType, listener); +}; + +RTC.prototype.addListener = function (type, listener) { + this.eventEmitter.on(type, listener); +}; + +RTC.prototype.removeStreamListener = function (listener, eventType) { + if(!(eventType instanceof StreamEventTypes)) + throw "Illegal argument"; + + this.removeListener(eventType, listener); +}; + +RTC.prototype.createLocalStreams = function (streams, change) { + for (var i = 0; i < streams.length; i++) { + var localStream = new LocalStream(streams.stream, + streams.type, this.eventEmitter, streams.videoType, + streams.isGUMStream); + this.localStreams.push(localStream); + if (streams.isMuted === true) localStream.setMute(true); - if(type == "audio") { + if (streams.type == "audio") { this.localAudio = localStream; } else { this.localVideo = localStream; } var eventType = StreamEventTypes.EVENT_TYPE_LOCAL_CREATED; - if(change) + if (change) eventType = StreamEventTypes.EVENT_TYPE_LOCAL_CHANGED; - eventEmitter.emit(eventType, localStream, isMuted); - return localStream; - }, - removeLocalStream: function (stream) { - for(var i = 0; i < this.localStreams.length; i++) { - if(this.localStreams[i].getOriginalStream() === stream) { - delete this.localStreams[i]; - return; - } - } - }, - createRemoteStream: function (data, sid, thessrc) { - var remoteStream = new MediaStream(data, sid, thessrc, - RTCBrowserType.getBrowserType(), eventEmitter); - var jid = data.peerjid || APP.xmpp.myJid(); - if(!this.remoteStreams[jid]) { - this.remoteStreams[jid] = {}; - } - this.remoteStreams[jid][remoteStream.type]= remoteStream; - eventEmitter.emit(StreamEventTypes.EVENT_TYPE_REMOTE_CREATED, remoteStream); - return 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( - function (stream, isUsingScreenStream, callback) { - self.changeLocalVideo(stream, isUsingScreenStream, callback); - }, DesktopSharingEventTypes.NEW_STREAM_CREATED); - 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); + this.eventEmitter.emit(eventType, localStream, streams.isMuted); + } + return this.localStreams; +}; - // 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, 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; - }, - switchVideoStreams: function (new_stream) { - this.localVideo.stream = new_stream; - - this.localStreams = []; - - //in firefox we have only one stream object - if (this.localAudio.getOriginalStream() != new_stream) - this.localStreams.push(this.localAudio); - this.localStreams.push(this.localVideo); - }, - 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 to trigger onended event for old stream - oldStream.stop(); - - this.switchVideoStreams(videoStream, 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, "audio", true); - // Stop the stream to trigger onended event for old stream - oldStream.stop(); - 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) +RTC.prototype.removeLocalStream = function (stream) { + for(var i = 0; i < this.localStreams.length; i++) { + if(this.localStreams[i].getOriginalStream() === stream) { + delete this.localStreams[i]; 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); } }; +RTC.prototype.createRemoteStream = function (data, sid, thessrc) { + var remoteStream = new MediaStream(data, sid, thessrc, + RTCBrowserType.getBrowserType(), this.eventEmitter); + if(data.peerjid) + return; + var jid = data.peerjid; + if(!this.remoteStreams[jid]) { + this.remoteStreams[jid] = {}; + } + this.remoteStreams[jid][remoteStream.type]= remoteStream; + this.eventEmitter.emit(StreamEventTypes.EVENT_TYPE_REMOTE_CREATED, remoteStream); + return remoteStream; +}; + +RTC.prototype.getPCConstraints = function () { + return this.rtcUtils.pc_constraints; +}; + +RTC.prototype.getUserMediaWithConstraints = function(um, success_callback, + failure_callback, resolution, + bandwidth, fps, desktopStream) +{ + return this.rtcUtils.getUserMediaWithConstraints(um, success_callback, + failure_callback, resolution, bandwidth, fps, desktopStream); +}; + +RTC.prototype.attachMediaStream = function (elSelector, stream) { + this.rtcUtils.attachMediaStream(elSelector, stream); +}; + +RTC.prototype.getStreamID = function (stream) { + return this.rtcUtils.getStreamID(stream); +}; + +RTC.prototype.getVideoSrc = function (element) { + return this.rtcUtils.getVideoSrc(element); +}; + +RTC.prototype.setVideoSrc = function (element, src) { + this.rtcUtils.setVideoSrc(element, src); +}; + +RTC.prototype.getVideoElementName = function () { + return RTCBrowserType.isTemasysPluginUsed() ? 'object' : 'video'; +}; + +RTC.prototype.dispose = function() { + if (this.rtcUtils) { + this.rtcUtils = null; + } +}; + +RTC.prototype.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; +}; + +RTC.prototype.switchVideoStreams = function (new_stream) { + this.localVideo.stream = new_stream; + + this.localStreams = []; + + //in firefox we have only one stream object + if (this.localAudio.getOriginalStream() != new_stream) + this.localStreams.push(this.localAudio); + this.localStreams.push(this.localVideo); +}; + +RTC.prototype.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) { + self.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 to trigger onended event for old stream + oldStream.stop(); + + this.switchVideoStreams(videoStream, oldStream); + + APP.xmpp.switchStreams(videoStream, oldStream,localCallback); +}; + +RTC.prototype.changeLocalAudio = function (stream, callback) { + var oldStream = this.localAudio.getOriginalStream(); + var newStream = this.rtcUtils.createStream(stream); + this.localAudio = this.createLocalStream(newStream, "audio", true); + // Stop the stream to trigger onended event for old stream + oldStream.stop(); + APP.xmpp.switchStreams(newStream, oldStream, callback, true); +}; + +RTC.prototype.isVideoMuted = function (jid) { + if (jid === APP.xmpp.myJid()) { + var localVideo = APP.RTC.localVideo; + return (!localVideo || localVideo.isMuted()); + } else { + if (!this.remoteStreams[jid] || + !this.remoteStreams[jid][MediaStreamType.VIDEO_TYPE]) { + return null; + } + return this.remoteStreams[jid][MediaStreamType.VIDEO_TYPE].muted; + } +}; + +RTC.prototype.setVideoMute = function (mute, callback, options) { + if (!this.localVideo) + return; + + if (mute == this.localVideo.isMuted()) + { + APP.xmpp.sendVideoInfoPresence(mute); + if (callback) + callback(mute); + } + else + { + this.localVideo.setMute(mute); + APP.xmpp.setVideoMute( + mute, + callback, + options); + } +}; + +RTC.prototype.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; + this.eventEmitter.emit(RTCEvents.AVAILABLE_DEVICES_CHANGED, this.devices); +}; + module.exports = RTC; diff --git a/modules/RTC/RTCUtils.js b/modules/RTC/RTCUtils.js index a661bfd59..40a89f402 100644 --- a/modules/RTC/RTCUtils.js +++ b/modules/RTC/RTCUtils.js @@ -436,7 +436,7 @@ RTCUtils.prototype.successCallback = function (stream, usageOptions) { if (stream && stream.getAudioTracks && stream.getVideoTracks) console.log('got', stream, stream.getAudioTracks().length, stream.getVideoTracks().length); - this.handleLocalStream(stream, usageOptions); + return this.handleLocalStream(stream, usageOptions); }; RTCUtils.prototype.errorCallback = function (error) { @@ -475,7 +475,6 @@ RTCUtils.prototype.errorCallback = function (error) { 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(); @@ -493,6 +492,7 @@ RTCUtils.prototype.handleLocalStream = function(stream, usageOptions) { videoStream.addTrack(videoTracks[i]); } } + } else if (RTCBrowserType.isFirefox() || RTCBrowserType.isTemasysPluginUsed()) { // Firefox and Temasys plugin @@ -513,12 +513,9 @@ RTCUtils.prototype.handleLocalStream = function(stream, usageOptions) { var audioGUM = (!usageOptions || usageOptions.audio !== false), videoGUM = (!usageOptions || usageOptions.video !== false); - - this.service.createLocalStream(audioStream, "audio", null, null, - audioMuted, audioGUM); - - this.service.createLocalStream(videoStream, "video", null, 'camera', - videoMuted, videoGUM); + return this.service.createLocalStreams( + [{stream: audioStream, type: "audio", isMuted: audioMuted, isGUMStream: audioGUM, videoType: null}, + {stream: videoStream, type: "video", isMuted: videoMuted, isGUMStream: videoGUM, videoType: "camera"}]); }; function DummyMediaStream(id) {