Refactors RTC module to support multiple conferences. Implements JitsiTrack interfaces.
This commit is contained in:
parent
2b5d893ec1
commit
3281fdd523
|
@ -1,5 +1,8 @@
|
|||
var RTC = require("./modules/RTC/RTC");
|
||||
var XMPPEvents = require("./service/xmpp/XMPPEvents");
|
||||
var StreamEventTypes = require("./service/RTC/StreamEventTypes");
|
||||
var EventEmitter = require("events");
|
||||
var JitsiConferenceEvents = require("./JitsiConferenceEvents");
|
||||
|
||||
/**
|
||||
* Creates a JitsiConference object with the given name and properties.
|
||||
|
@ -15,11 +18,10 @@ function JitsiConference(options) {
|
|||
this.options = 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));
|
||||
|
||||
this.eventEmitter = new EventEmitter();
|
||||
this.room = this.xmpp.createRoom(this.options.name, null, null, this.options.config);
|
||||
this.rtc = new RTC(this.room, options);
|
||||
setupListeners(this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -27,7 +29,6 @@ function JitsiConference(options) {
|
|||
* @param password {string} the password
|
||||
*/
|
||||
JitsiConference.prototype.join = function (password) {
|
||||
|
||||
this.room.join(password);
|
||||
}
|
||||
|
||||
|
@ -67,7 +68,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.room.addListener(eventId, handler);
|
||||
this.eventEmitter.on(eventId, handler);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -78,7 +79,7 @@ JitsiConference.prototype.on = function (eventId, handler) {
|
|||
* Note: consider adding eventing functionality by extending an EventEmitter impl, instead of rolling ourselves
|
||||
*/
|
||||
JitsiConference.prototype.off = function (eventId, handler) {
|
||||
this.room.removeListener(eventId, listener);
|
||||
this.eventEmitter.removeListener(eventId, listener);
|
||||
}
|
||||
|
||||
// Common aliases for event emitter
|
||||
|
@ -181,5 +182,21 @@ JitsiConference.prototype.getParticipantById = function(id) {
|
|||
|
||||
}
|
||||
|
||||
function setupListeners(conference) {
|
||||
conference.xmpp.addListener(XMPPEvents.CALL_INCOMING,
|
||||
conference.rtc.onIncommingCall.bind(conference.rtc));
|
||||
conference.room.addListener(XMPPEvents.REMOTE_STREAM_RECEIVED,
|
||||
conference.rtc.createRemoteStream.bind(conference.rtc));
|
||||
conference.rtc.addListener(StreamEventTypes.EVENT_TYPE_REMOTE_CREATED, function (stream) {
|
||||
conference.eventEmitter.emit(JitsiConferenceEvents.TRACK_ADDED, stream);
|
||||
});
|
||||
conference.rtc.addListener(StreamEventTypes.EVENT_TYPE_REMOTE_CREATED, function (stream) {
|
||||
conference.eventEmitter.emit(JitsiConferenceEvents.TRACK_REMOVED, stream);
|
||||
})
|
||||
conference.room.addListener(XMPPEvents.MUC_JOINED, function () {
|
||||
conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_JOINED);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
module.exports = JitsiConference;
|
||||
|
|
|
@ -17,6 +17,9 @@ var LibJitsiMeet = {
|
|||
errors: {
|
||||
conference: JitsiConferenceErrors,
|
||||
connection: JitsiConnectionErrors
|
||||
},
|
||||
init: function (options) {
|
||||
require("./modules/RTC/RTC").init(options || {});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
3222
lib-jitsi-meet.js
3222
lib-jitsi-meet.js
File diff suppressed because it is too large
Load Diff
|
@ -4,155 +4,148 @@
|
|||
// https://code.google.com/p/chromium/issues/detail?id=405545
|
||||
var RTCEvents = require("../../service/RTC/RTCEvents");
|
||||
|
||||
var _dataChannels = [];
|
||||
var eventEmitter = null;
|
||||
|
||||
/**
|
||||
* Binds "ondatachannel" event listener to given PeerConnection instance.
|
||||
* @param peerConnection WebRTC peer connection instance.
|
||||
*/
|
||||
function DataChannels(peerConnection, emitter) {
|
||||
peerConnection.ondatachannel = this.onDataChannel;
|
||||
this.eventEmitter = emitter;
|
||||
|
||||
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;
|
||||
this._dataChannels = [];
|
||||
|
||||
dataChannel.onopen = function () {
|
||||
console.info("Data channel opened by the Videobridge!", dataChannel);
|
||||
// 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);
|
||||
|
||||
// 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 = new Boolean(oldValue).valueOf();
|
||||
}
|
||||
}
|
||||
if ((type = typeof newValue) !== 'boolean') {
|
||||
if (type === 'string') {
|
||||
newValue = (newValue == "true");
|
||||
} else {
|
||||
newValue = new 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.log(
|
||||
"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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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: onSelectedEndpointChanged,
|
||||
handlePinnedEndpointEvent: onPinnedEndpointChanged
|
||||
// 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);
|
||||
};*/
|
||||
};
|
||||
|
||||
function onSelectedEndpointChanged(userResource) {
|
||||
|
||||
/**
|
||||
* Callback triggered by PeerConnection when new data channel is opened
|
||||
* on the bridge.
|
||||
* @param event the event info object.
|
||||
*/
|
||||
DataChannels.prototype.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));
|
||||
|
||||
this.eventEmitter.emit(RTCEvents.DATA_CHANNEL_OPEN);
|
||||
}.bind(this);
|
||||
|
||||
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);
|
||||
this.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 = new Boolean(oldValue).valueOf();
|
||||
}
|
||||
}
|
||||
if ((type = typeof newValue) !== 'boolean') {
|
||||
if (type === 'string') {
|
||||
newValue = (newValue == "true");
|
||||
} else {
|
||||
newValue = new Boolean(newValue).valueOf();
|
||||
}
|
||||
}
|
||||
|
||||
this.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.log(
|
||||
"Data channel new last-n event: ",
|
||||
lastNEndpoints, endpointsEnteringLastN, obj);
|
||||
this.eventEmitter.emit(RTCEvents.LASTN_ENDPOINT_CHANGED,
|
||||
lastNEndpoints, endpointsEnteringLastN, obj);
|
||||
}
|
||||
else {
|
||||
console.debug("Data channel JSON-formatted message: ", obj);
|
||||
}
|
||||
}
|
||||
}.bind(this);
|
||||
|
||||
dataChannel.onclose = function () {
|
||||
console.info("The Data Channel closed", dataChannel);
|
||||
var idx = this._dataChannels.indexOf(dataChannel);
|
||||
if (idx > -1)
|
||||
this._dataChannels = this._dataChannels.splice(idx, 1);
|
||||
}.bind(this);
|
||||
this._dataChannels.push(dataChannel);
|
||||
};
|
||||
|
||||
DataChannels.prototype.handleSelectedEndpointEvent = function (userResource) {
|
||||
console.log('selected endpoint changed: ', userResource);
|
||||
if (_dataChannels && _dataChannels.length != 0) {
|
||||
_dataChannels.some(function (dataChannel) {
|
||||
if (this._dataChannels && this._dataChannels.length != 0) {
|
||||
this._dataChannels.some(function (dataChannel) {
|
||||
if (dataChannel.readyState == 'open') {
|
||||
console.log('sending selected endpoint changed ' +
|
||||
'notification to the bridge: ', userResource);
|
||||
|
@ -169,10 +162,10 @@ function onSelectedEndpointChanged(userResource) {
|
|||
}
|
||||
}
|
||||
|
||||
function onPinnedEndpointChanged(userResource) {
|
||||
DataChannels.prototype.handlePinnedEndpointEvent = function (userResource) {
|
||||
console.log('pinned endpoint changed: ', userResource);
|
||||
if (_dataChannels && _dataChannels.length != 0) {
|
||||
_dataChannels.some(function (dataChannel) {
|
||||
if (this._dataChannels && this._dataChannels.length != 0) {
|
||||
this._dataChannels.some(function (dataChannel) {
|
||||
if (dataChannel.readyState == 'open') {
|
||||
dataChannel.send(JSON.stringify({
|
||||
'colibriClass': 'PinnedEndpointChangedEvent',
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
var JitsiTrack = require("./JitsiTrack");
|
||||
var StreamEventTypes = require("../../service/RTC/StreamEventTypes");
|
||||
var RTCEvents = require("../../service/RTC/RTCEvents");
|
||||
var RTCBrowserType = require("./RTCBrowserType");
|
||||
|
||||
/**
|
||||
* 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(stream) {
|
||||
var originalStop = stream.stop;
|
||||
stream.stop = function () {
|
||||
originalStop.apply(stream);
|
||||
if (!stream.ended) {
|
||||
stream.ended = true;
|
||||
stream.onended();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a single media track (either audio or video).
|
||||
* @constructor
|
||||
*/
|
||||
function JitsiLocalTrack(RTC, stream, eventEmitter, videoType, isGUMStream)
|
||||
{
|
||||
JitsiTrack.call(this, RTC, stream);
|
||||
this.eventEmitter = eventEmitter;
|
||||
this.videoType = videoType;
|
||||
this.isGUMStream = true;
|
||||
if(isGUMStream === false)
|
||||
this.isGUMStream = isGUMStream;
|
||||
this.stream.onended = function () {
|
||||
this.eventEmitter.emit(StreamEventTypes.EVENT_TYPE_LOCAL_ENDED, this);
|
||||
}.bind(this);
|
||||
if (RTCBrowserType.isFirefox()) {
|
||||
implementOnEndedHandling(this.stream);
|
||||
}
|
||||
}
|
||||
|
||||
JitsiLocalTrack.prototype = Object.create(JitsiTrack.prototype);
|
||||
JitsiLocalTrack.prototype.constructor = JitsiLocalTrack;
|
||||
|
||||
/**
|
||||
* Mutes / unmutes the track.
|
||||
* @param mute {boolean} if true the track will be muted. Otherwise the track will be unmuted.
|
||||
*/
|
||||
JitsiLocalTrack.prototype._setMute = function (mute) {
|
||||
var isAudio = this.type === JitsiTrack.AUDIO;
|
||||
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;
|
||||
}
|
||||
if(isAudio)
|
||||
this.rtc.room.setAudioMute(mute);
|
||||
else
|
||||
this.rtc.room.setVideoMute(mute);
|
||||
this.eventEmitter.emit(eventType, mute);
|
||||
} else {
|
||||
if (mute) {
|
||||
this.rtc.room.removeStream(this.stream);
|
||||
this.stream.stop();
|
||||
if(isAudio)
|
||||
this.rtc.room.setAudioMute(mute);
|
||||
else
|
||||
this.rtc.room.setVideoMute(mute);
|
||||
this.eventEmitter.emit(eventType, true);
|
||||
} else {
|
||||
var self = this;
|
||||
this.rtc.obtainAudioAndVideoPermissions(
|
||||
{devices: (this.isAudioStream() ? ["audio"] : ["video"])})
|
||||
.then(function (stream) {
|
||||
if (isAudio) {
|
||||
self.rtc.changeLocalAudio(stream,
|
||||
function () {
|
||||
this.rtc.room.setAudioMute(mute);
|
||||
self.eventEmitter.emit(eventType, false);
|
||||
});
|
||||
} else {
|
||||
self.rtc.changeLocalVideo(stream, false,
|
||||
function () {
|
||||
this.rtc.room.setVideoMute(mute);
|
||||
self.eventEmitter.emit(eventType, false);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops sending the media track. And removes it from the HTML.
|
||||
* NOTE: Works for local tracks only.
|
||||
*/
|
||||
JitsiLocalTrack.prototype.stop = function () {
|
||||
this.rtc.room.removeStream(this.stream);
|
||||
this.stream.stop();
|
||||
this.detach();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Starts sending the track.
|
||||
* NOTE: Works for local tracks only.
|
||||
*/
|
||||
JitsiLocalTrack.prototype.start = function() {
|
||||
this.rtc.room.addStream(this.stream, function () {});
|
||||
}
|
||||
|
||||
module.exports = JitsiLocalTrack;
|
|
@ -0,0 +1,31 @@
|
|||
var JitsiTrack = require("./JitsiTrack");
|
||||
|
||||
/**
|
||||
* Represents a single media track (either audio or video).
|
||||
* @constructor
|
||||
*/
|
||||
function JitsiRemoteTrack(RTC, data, sid, ssrc, browser, eventEmitter) {
|
||||
JitsiTrack.call(this, RTC, data.stream);
|
||||
this.rtc = RTC;
|
||||
this.sid = sid;
|
||||
this.stream = data.stream;
|
||||
this.peerjid = data.peerjid;
|
||||
this.videoType = data.videoType;
|
||||
this.ssrc = ssrc;
|
||||
this.muted = false;
|
||||
this.eventEmitter = eventEmitter;
|
||||
}
|
||||
|
||||
JitsiRemoteTrack.prototype = Object.create(JitsiTrack.prototype);
|
||||
JitsiRemoteTrack.prototype.constructor = JitsiRemoteTrack;
|
||||
|
||||
JitsiRemoteTrack.prototype._setMute = function (value) {
|
||||
this.stream.muted = value;
|
||||
this.muted = value;
|
||||
};
|
||||
|
||||
delete JitsiRemoteTrack.prototype.stop;
|
||||
|
||||
delete JitsiRemoteTrack.prototype.start;
|
||||
|
||||
module.exports = JitsiRemoteTrack;
|
|
@ -1,10 +1,24 @@
|
|||
var RTC = require("./RTCUtils");
|
||||
|
||||
/**
|
||||
* Represents a single media track (either audio or video).
|
||||
* @constructor
|
||||
*/
|
||||
function JitsiTrack(stream)
|
||||
function JitsiTrack(RTC, stream)
|
||||
{
|
||||
this.rtc = RTC;
|
||||
this.stream = stream;
|
||||
this.type = (this.stream.getVideoTracks().length > 0)?
|
||||
JitsiTrack.VIDEO : JitsiTrack.AUDIO;
|
||||
if(this.type == "audio") {
|
||||
this._getTracks = function () {
|
||||
return this.stream.getAudioTracks();
|
||||
}.bind(this);
|
||||
} else {
|
||||
this._getTracks = function () {
|
||||
return this.stream.getVideoTracks();
|
||||
}.bind(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -23,7 +37,7 @@ JitsiTrack.AUDIO = "audio";
|
|||
* Returns the type (audio or video) of this track.
|
||||
*/
|
||||
JitsiTrack.prototype.getType = function() {
|
||||
return this.stream.type;
|
||||
return this.type;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -37,21 +51,21 @@ JitsiTrack.prototype.getParitcipant = function() {
|
|||
* Returns the RTCMediaStream from the browser (?).
|
||||
*/
|
||||
JitsiTrack.prototype.getOriginalStream = function() {
|
||||
return this.stream.getOriginalStream();
|
||||
return this.stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mutes the track.
|
||||
*/
|
||||
JitsiTrack.prototype.mute = function () {
|
||||
this.stream.setMute(true);
|
||||
this._setMute(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmutes the stream.
|
||||
*/
|
||||
JitsiTrack.prototype.unmute = function () {
|
||||
this.stream.setMute(false);
|
||||
this._setMute(false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -59,7 +73,7 @@ JitsiTrack.prototype.unmute = function () {
|
|||
* @param container the HTML container
|
||||
*/
|
||||
JitsiTrack.prototype.attach = function (container) {
|
||||
|
||||
RTC.attachMediaStream(container, this.stream);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -67,7 +81,7 @@ JitsiTrack.prototype.attach = function (container) {
|
|||
* @param container the HTML container
|
||||
*/
|
||||
JitsiTrack.prototype.detach = function (container) {
|
||||
|
||||
$(container).find(">video").remove();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -76,6 +90,7 @@ JitsiTrack.prototype.detach = function (container) {
|
|||
*/
|
||||
JitsiTrack.prototype.stop = function () {
|
||||
|
||||
this.detach();
|
||||
}
|
||||
|
||||
|
||||
|
@ -84,6 +99,7 @@ JitsiTrack.prototype.stop = function () {
|
|||
* NOTE: Works for local tracks only.
|
||||
*/
|
||||
JitsiTrack.prototype.start = function() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -91,6 +107,18 @@ JitsiTrack.prototype.start = function() {
|
|||
* screen capture as opposed to a camera.
|
||||
*/
|
||||
JitsiTrack.prototype.isScreenSharing = function(){
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns id of the track.
|
||||
* @returns {string} id of the track or null if this is fake track.
|
||||
*/
|
||||
JitsiTrack.prototype.getId = function () {
|
||||
var tracks = this.stream.getTracks();
|
||||
if(!tracks || tracks.length === 0)
|
||||
return null;
|
||||
return tracks[0].id;
|
||||
};
|
||||
|
||||
module.exports = JitsiTrack;
|
|
@ -2,9 +2,9 @@
|
|||
var EventEmitter = require("events");
|
||||
var RTCBrowserType = require("./RTCBrowserType");
|
||||
var RTCUtils = require("./RTCUtils.js");
|
||||
var LocalStream = require("./LocalStream.js");
|
||||
var JitsiLocalTrack = require("./JitsiLocalTrack.js");
|
||||
var DataChannels = require("./DataChannels");
|
||||
var MediaStream = require("./MediaStream.js");
|
||||
var JitsiRemoteTrack = require("./JitsiRemoteTrack.js");
|
||||
var DesktopSharingEventTypes
|
||||
= require("../../service/desktopsharing/DesktopSharingEventTypes");
|
||||
var MediaStreamType = require("../../service/RTC/MediaStreamTypes");
|
||||
|
@ -40,13 +40,16 @@ function getMediaStreamUsage()
|
|||
return result;
|
||||
}
|
||||
|
||||
var rtcReady = false;
|
||||
|
||||
function RTC(options)
|
||||
{
|
||||
|
||||
|
||||
function RTC(room, options) {
|
||||
this.devices = {
|
||||
audio: true,
|
||||
video: true
|
||||
};
|
||||
this.room = room;
|
||||
this.localStreams = [];
|
||||
this.remoteStreams = {};
|
||||
this.localAudio = null;
|
||||
|
@ -59,19 +62,6 @@ function RTC(options)
|
|||
self.changeLocalVideo(stream, isUsingScreenStream, callback);
|
||||
}, DesktopSharingEventTypes.NEW_STREAM_CREATED);
|
||||
|
||||
// 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);
|
||||
};
|
||||
|
||||
RTCUtils.init(onReady);
|
||||
|
||||
// Call onReady() if Temasys plugin is not used
|
||||
if (!RTCBrowserType.isTemasysPluginUsed()) {
|
||||
onReady();
|
||||
}
|
||||
}
|
||||
|
||||
RTC.prototype.obtainAudioAndVideoPermissions = function (options) {
|
||||
|
@ -80,15 +70,19 @@ RTC.prototype.obtainAudioAndVideoPermissions = function (options) {
|
|||
}
|
||||
|
||||
RTC.prototype.onIncommingCall = function(event) {
|
||||
DataChannels.init(event.peerconnection, self.eventEmitter);
|
||||
if(this.options.config.openSctp)
|
||||
this.dataChannels = new DataChannels(event.peerconnection, this.eventEmitter);
|
||||
this.room.addLocalStreams(this.localStreams);
|
||||
}
|
||||
|
||||
RTC.prototype.selectedEndpoint = function (id) {
|
||||
DataChannels.handleSelectedEndpointEvent(id);
|
||||
if(this.dataChannels)
|
||||
this.dataChannels.handleSelectedEndpointEvent(id);
|
||||
}
|
||||
|
||||
RTC.prototype.pinEndpoint = function (id) {
|
||||
DataChannels.handlePinnedEndpointEvent(id);
|
||||
if(this.dataChannels)
|
||||
this.dataChannels.handlePinnedEndpointEvent(id);
|
||||
}
|
||||
|
||||
RTC.prototype.addStreamListener = function (listener, eventType) {
|
||||
|
@ -99,17 +93,50 @@ RTC.prototype.addListener = function (type, listener) {
|
|||
this.eventEmitter.on(type, listener);
|
||||
};
|
||||
|
||||
RTC.prototype.removeListener = function (listener, eventType) {
|
||||
this.eventEmitter.removeListener(eventType, listener);
|
||||
};
|
||||
|
||||
RTC.prototype.removeStreamListener = function (listener, eventType) {
|
||||
if(!(eventType instanceof StreamEventTypes))
|
||||
throw "Illegal argument";
|
||||
|
||||
this.removeListener(eventType, listener);
|
||||
this.eventEmitter.removeListener(eventType, listener);
|
||||
};
|
||||
|
||||
RTC.addRTCReadyListener = function (listener) {
|
||||
RTCUtils.eventEmitter.on(RTCEvents.RTC_READY, listener);
|
||||
}
|
||||
|
||||
RTC.removeRTCReadyListener = function (listener) {
|
||||
RTCUtils.eventEmitter.removeListener(RTCEvents.RTC_READY, listener);
|
||||
}
|
||||
|
||||
RTC.isRTCReady = function () {
|
||||
return rtcReady;
|
||||
}
|
||||
|
||||
RTC.init = function (options) {
|
||||
// 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 () {
|
||||
rtcReady = true;
|
||||
RTCUtils.eventEmitter.emit(RTCEvents.RTC_READY, true);
|
||||
};
|
||||
|
||||
RTCUtils.init(onReady, options || {});
|
||||
|
||||
// Call onReady() if Temasys plugin is not used
|
||||
if (!RTCBrowserType.isTemasysPluginUsed()) {
|
||||
onReady();
|
||||
}
|
||||
}
|
||||
|
||||
RTC.prototype.createLocalStreams = function (streams, change) {
|
||||
for (var i = 0; i < streams.length; i++) {
|
||||
var localStream = new LocalStream(this, streams[i].stream,
|
||||
streams[i].type, this.eventEmitter, streams[i].videoType,
|
||||
var localStream = new JitsiLocalTrack(this, streams[i].stream,
|
||||
this.eventEmitter, streams[i].videoType,
|
||||
streams[i].isGUMStream);
|
||||
this.localStreams.push(localStream);
|
||||
if (streams[i].isMuted === true)
|
||||
|
@ -139,9 +166,9 @@ RTC.prototype.removeLocalStream = function (stream) {
|
|||
};
|
||||
|
||||
RTC.prototype.createRemoteStream = function (data, sid, thessrc) {
|
||||
var remoteStream = new MediaStream(data, sid, thessrc,
|
||||
var remoteStream = new JitsiRemoteTrack(this, data, sid, thessrc,
|
||||
RTCBrowserType.getBrowserType(), this.eventEmitter);
|
||||
if(data.peerjid)
|
||||
if(!data.peerjid)
|
||||
return;
|
||||
var jid = data.peerjid;
|
||||
if(!this.remoteStreams[jid]) {
|
||||
|
@ -223,9 +250,9 @@ RTC.prototype.changeLocalVideo = function (stream, isUsingScreenStream, callback
|
|||
|
||||
if(this.localVideo.isMuted() && this.localVideo.videoType !== type) {
|
||||
localCallback = function() {
|
||||
APP.xmpp.setVideoMute(false, function(mute) {
|
||||
self.eventEmitter.emit(RTCEvents.VIDEO_MUTE, mute);
|
||||
});
|
||||
this.room.setVideoMute(false, function(mute) {
|
||||
this.eventEmitter.emit(RTCEvents.VIDEO_MUTE, mute);
|
||||
}.bind(this));
|
||||
|
||||
callback();
|
||||
};
|
||||
|
@ -241,7 +268,7 @@ RTC.prototype.changeLocalVideo = function (stream, isUsingScreenStream, callback
|
|||
|
||||
this.switchVideoStreams(videoStream, oldStream);
|
||||
|
||||
APP.xmpp.switchStreams(videoStream, oldStream,localCallback);
|
||||
this.room.switchStreams(videoStream, oldStream,localCallback);
|
||||
};
|
||||
|
||||
RTC.prototype.changeLocalAudio = function (stream, callback) {
|
||||
|
@ -250,7 +277,7 @@ RTC.prototype.changeLocalAudio = function (stream, callback) {
|
|||
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);
|
||||
this.room.switchStreams(newStream, oldStream, callback, true);
|
||||
};
|
||||
|
||||
RTC.prototype.isVideoMuted = function (jid) {
|
||||
|
@ -279,7 +306,7 @@ RTC.prototype.setVideoMute = function (mute, callback, options) {
|
|||
else
|
||||
{
|
||||
this.localVideo.setMute(mute);
|
||||
APP.xmpp.setVideoMute(
|
||||
this.room.setVideoMute(
|
||||
mute,
|
||||
callback,
|
||||
options);
|
||||
|
|
|
@ -3,8 +3,8 @@ var RTCBrowserType = require("./RTCBrowserType");
|
|||
var Resolutions = require("../../service/RTC/Resolutions");
|
||||
var AdapterJS = require("./adapter.screenshare");
|
||||
var SDPUtil = require("../xmpp/SDPUtil");
|
||||
var EventEmitter = require("events");
|
||||
|
||||
var currentResolution = null;
|
||||
function DummyMediaStream(id) {
|
||||
this.id = id;
|
||||
this.label = id;
|
||||
|
@ -143,7 +143,8 @@ function getConstraints(um, resolution, bandwidth, fps, desktopStream, isAndroid
|
|||
|
||||
//Options parameter is to pass config options. Currently uses only "useIPv6".
|
||||
var RTCUtils = {
|
||||
init: function (onTemasysPluginReady) {
|
||||
eventEmitter: new EventEmitter(),
|
||||
init: function (onTemasysPluginReady, options) {
|
||||
var self = this;
|
||||
if (RTCBrowserType.isFirefox()) {
|
||||
var FFversion = RTCBrowserType.getFirefoxVersion();
|
||||
|
@ -217,7 +218,7 @@ var RTCUtils = {
|
|||
this.pc_constraints = {'optional': [
|
||||
{'DtlsSrtpKeyAgreement': 'true'}
|
||||
]};
|
||||
if (this.service.options.useIPv6) {
|
||||
if (options.useIPv6) {
|
||||
// https://code.google.com/p/webrtc/issues/detail?id=2828
|
||||
this.pc_constraints.optional.push({googIPv6: true});
|
||||
}
|
||||
|
@ -351,7 +352,7 @@ var RTCUtils = {
|
|||
|
||||
return new Promise(function (resolve, reject) {
|
||||
var successCallback = function (stream) {
|
||||
resolve(self.successCallback(stream, usageOptions));
|
||||
resolve(self.successCallback(RTC , stream, usageOptions));
|
||||
};
|
||||
|
||||
if (!devices)
|
||||
|
@ -436,13 +437,13 @@ var RTCUtils = {
|
|||
}.bind(this));
|
||||
},
|
||||
|
||||
successCallback: function (stream, usageOptions) {
|
||||
successCallback: function (RTC, 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);
|
||||
return this.handleLocalStream(stream, usageOptions);
|
||||
return this.handleLocalStream(RTC, stream, usageOptions);
|
||||
},
|
||||
|
||||
errorCallback: function (error, resolve, RTC, currentResolution) {
|
||||
|
@ -457,7 +458,7 @@ var RTCUtils = {
|
|||
&& resolution != null) {
|
||||
self.getUserMediaWithConstraints(RTC, ['audio', 'video'],
|
||||
function (stream) {
|
||||
resolve(self.successCallback(stream));
|
||||
resolve(self.successCallback(RTC, stream));
|
||||
}, function (error, resolution) {
|
||||
return self.errorCallback(error, resolve, RTC, resolution);
|
||||
}, resolution);
|
||||
|
@ -467,18 +468,18 @@ var RTCUtils = {
|
|||
RTC,
|
||||
['audio'],
|
||||
function (stream) {
|
||||
resolve(self.successCallback(stream));
|
||||
resolve(self.successCallback(RTC, stream));
|
||||
},
|
||||
function (error) {
|
||||
console.error('failed to obtain audio/video stream - stop',
|
||||
error);
|
||||
resolve(self.successCallback(null));
|
||||
resolve(self.successCallback(RTC, null));
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
handleLocalStream: function (stream, usageOptions) {
|
||||
handleLocalStream: function (service, stream, usageOptions) {
|
||||
// If this is FF, the stream parameter is *not* a MediaStream object, it's
|
||||
// an object with two properties: audioStream, videoStream.
|
||||
if (window.webkitMediaStream) {
|
||||
|
@ -517,7 +518,7 @@ var RTCUtils = {
|
|||
var audioGUM = (!usageOptions || usageOptions.audio !== false),
|
||||
videoGUM = (!usageOptions || usageOptions.video !== false);
|
||||
|
||||
return this.service.createLocalStreams(
|
||||
return service.createLocalStreams(
|
||||
[
|
||||
{stream: audioStream, type: "audio", isMuted: audioMuted, isGUMStream: audioGUM, videoType: null},
|
||||
{stream: videoStream, type: "video", isMuted: videoMuted, isGUMStream: videoGUM, videoType: "camera"}
|
||||
|
|
|
@ -0,0 +1,577 @@
|
|||
var XMPPEvents = require("../../service/xmpp/XMPPEvents");
|
||||
var Moderator = require("./moderator");
|
||||
var EventEmitter = require("events");
|
||||
|
||||
var parser = {
|
||||
packet2JSON: function (packet, nodes) {
|
||||
var self = this;
|
||||
$(packet).children().each(function (index) {
|
||||
var tagName = $(this).prop("tagName");
|
||||
var node = {}
|
||||
node["tagName"] = tagName;
|
||||
node.attributes = {};
|
||||
$($(this)[0].attributes).each(function( index, attr ) {
|
||||
node.attributes[ attr.name ] = attr.value;
|
||||
} );
|
||||
var text = Strophe.getText($(this)[0]);
|
||||
if(text)
|
||||
node.value = text;
|
||||
node.children = [];
|
||||
nodes.push(node);
|
||||
self.packet2JSON($(this), node.children);
|
||||
})
|
||||
},
|
||||
JSON2packet: function (nodes, packet) {
|
||||
for(var i = 0; i < nodes.length; i++)
|
||||
{
|
||||
var node = nodes[i];
|
||||
if(!node || node === null){
|
||||
continue;
|
||||
}
|
||||
packet.c(node.tagName, node.attributes);
|
||||
if(node.value)
|
||||
packet.t(node.value);
|
||||
if(node.children)
|
||||
this.JSON2packet(node.children, packet);
|
||||
packet.up();
|
||||
}
|
||||
packet.up();
|
||||
}
|
||||
};
|
||||
|
||||
function ChatRoom(connection, jid, password, XMPP, options)
|
||||
{
|
||||
this.eventEmitter = new EventEmitter();
|
||||
this.xmpp = XMPP;
|
||||
this.connection = connection;
|
||||
this.roomjid = Strophe.getBareJidFromJid(jid);
|
||||
this.myroomjid = jid;
|
||||
this.password = password;
|
||||
console.info("Joined MUC as " + this.myroomjid);
|
||||
this.members = {};
|
||||
this.presMap = {};
|
||||
this.presHandlers = {};
|
||||
this.joined = false;
|
||||
this.role = null;
|
||||
this.focusMucJid = null;
|
||||
this.bridgeIsDown = false;
|
||||
this.options = options || {};
|
||||
this.moderator = new Moderator(this.roomjid, this.xmpp, this.eventEmitter);
|
||||
this.initPresenceMap();
|
||||
this.session = null;
|
||||
var self = this;
|
||||
}
|
||||
|
||||
ChatRoom.prototype.initPresenceMap = function () {
|
||||
this.presMap['to'] = this.myroomjid;
|
||||
this.presMap['xns'] = 'http://jabber.org/protocol/muc';
|
||||
this.presMap["nodes"] = [];
|
||||
this.presMap["nodes"].push( {
|
||||
"tagName": "user-agent",
|
||||
"value": navigator.userAgent,
|
||||
"attributes": {xmlns: 'http://jitsi.org/jitmeet/user-agent'}
|
||||
});
|
||||
};
|
||||
|
||||
ChatRoom.prototype.join = function (password) {
|
||||
if(password)
|
||||
this.password = password;
|
||||
this.moderator.allocateConferenceFocus(function()
|
||||
{
|
||||
this.sendPresence();
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
ChatRoom.prototype.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.password) {
|
||||
pres.c('password').t(this.password).up();
|
||||
}
|
||||
|
||||
pres.up();
|
||||
|
||||
// Send XEP-0115 'c' stanza that contains our capabilities info
|
||||
if (this.connection.caps) {
|
||||
this.connection.caps.node = this.xmpp.options.clientNode;
|
||||
pres.c('c', this.connection.caps.generateCapsAttrs()).up();
|
||||
}
|
||||
|
||||
parser.JSON2packet(this.presMap.nodes, pres);
|
||||
this.connection.send(pres);
|
||||
};
|
||||
|
||||
|
||||
ChatRoom.prototype.doLeave = function () {
|
||||
console.log("do leave", this.myroomjid);
|
||||
var pres = $pres({to: this.myroomjid, type: 'unavailable' });
|
||||
this.presMap.length = 0;
|
||||
this.connection.send(pres);
|
||||
};
|
||||
|
||||
|
||||
ChatRoom.prototype.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");
|
||||
});
|
||||
};
|
||||
|
||||
ChatRoom.prototype.onPresence = function (pres) {
|
||||
var from = pres.getAttribute('from');
|
||||
// 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(this.moderator.getFocusUserJid() + "/") == 0) {
|
||||
member.isFocus = true;
|
||||
}
|
||||
|
||||
$(pres).find(">x").remove();
|
||||
var nodes = [];
|
||||
parser.packet2JSON(pres, nodes);
|
||||
for(var i = 0; i < nodes.length; i++)
|
||||
{
|
||||
var node = nodes[i];
|
||||
switch(node.tagName)
|
||||
{
|
||||
case "nick":
|
||||
member.nick = node.value;
|
||||
if(!member.isFocus) {
|
||||
var displayName = !this.xmpp.options.displayJids
|
||||
? member.nick : Strophe.getResourceFromJid(from);
|
||||
|
||||
if (displayName && displayName.length > 0) {
|
||||
this.eventEmitter.emit(XMPPEvents.DISPLAY_NAME_CHANGED, from, displayName);
|
||||
}
|
||||
console.info("Display name: " + displayName, pres);
|
||||
}
|
||||
break;
|
||||
case "userId":
|
||||
member.id = node.value;
|
||||
break;
|
||||
case "email":
|
||||
member.email = node.value;
|
||||
break;
|
||||
case "bridgeIsDown":
|
||||
if(!this.bridgeIsDown) {
|
||||
this.bridgeIsDown = true;
|
||||
this.eventEmitter.emit(XMPPEvents.BRIDGE_DOWN);
|
||||
}
|
||||
break;
|
||||
default :
|
||||
this.processNode(node, from);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (from == this.myroomjid) {
|
||||
if (member.affiliation == 'owner')
|
||||
|
||||
if (this.role !== member.role) {
|
||||
this.role = member.role;
|
||||
|
||||
this.eventEmitter.emit(XMPPEvents.LOCAL_ROLE_CHANGED,
|
||||
member, this.isModerator());
|
||||
}
|
||||
if (!this.joined) {
|
||||
this.joined = true;
|
||||
this.eventEmitter.emit(XMPPEvents.MUC_JOINED, from, member);
|
||||
}
|
||||
} else if (this.members[from] === undefined) {
|
||||
// new participant
|
||||
this.members[from] = member;
|
||||
console.log('entered', from, member);
|
||||
if (member.isFocus) {
|
||||
this.focusMucJid = from;
|
||||
console.info("Ignore focus: " + from + ", real JID: " + member.jid);
|
||||
}
|
||||
else {
|
||||
this.eventEmitter.emit(XMPPEvents.MUC_MEMBER_JOINED, from, member.id || member.email, member.nick);
|
||||
}
|
||||
} else {
|
||||
// Presence update for existing participant
|
||||
// Watch role change:
|
||||
if (this.members[from].role != member.role) {
|
||||
this.members[from].role = member.role;
|
||||
this.eventEmitter.emit(XMPPEvents.MUC_ROLE_CHANGED,
|
||||
member.role, member.nick);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
if(!member.isFocus)
|
||||
this.eventEmitter.emit(XMPPEvents.USER_ID_CHANGED, from, member.id || member.email);
|
||||
|
||||
// Trigger status message update
|
||||
if (member.status) {
|
||||
this.eventEmitter.emit(XMPPEvents.PRESENCE_STATUS, from, member);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
ChatRoom.prototype.processNode = function (node, from) {
|
||||
if(this.presHandlers[node.tagName])
|
||||
this.presHandlers[node.tagName](node, from);
|
||||
};
|
||||
|
||||
ChatRoom.prototype.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);
|
||||
this.eventEmitter.emit(XMPPEvents.SENDING_CHAT_MESSAGE, body);
|
||||
};
|
||||
|
||||
ChatRoom.prototype.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);
|
||||
};
|
||||
|
||||
|
||||
ChatRoom.prototype.onParticipantLeft = function (jid) {
|
||||
|
||||
this.eventEmitter.emit(XMPPEvents.MUC_MEMBER_LEFT, jid);
|
||||
|
||||
this.moderator.onMucMemberLeft(jid);
|
||||
};
|
||||
|
||||
ChatRoom.prototype.onPresenceUnavailable = function (pres, 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();
|
||||
}
|
||||
|
||||
this.xmpp.disposeConference(false);
|
||||
this.eventEmitter.emit(XMPPEvents.MUC_DESTROYED, reason);
|
||||
delete this.connection.emuc.rooms[Strophe.getBareJidFromJid(jid)];
|
||||
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.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 (Object.keys(this.members).length > 1) {
|
||||
for (var i in this.members) {
|
||||
var member = this.members[i];
|
||||
delete this.members[i];
|
||||
this.onParticipantLeft(member);
|
||||
}
|
||||
}
|
||||
if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="307"]').length) {
|
||||
if (this.myroomjid === from) {
|
||||
this.xmpp.disposeConference(false);
|
||||
this.eventEmitter.emit(XMPPEvents.KICKED);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ChatRoom.prototype.onMessage = function (msg, 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") {
|
||||
this.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 == "") {
|
||||
this.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);
|
||||
this.eventEmitter.emit(XMPPEvents.MESSAGE_RECEIVED,
|
||||
from, nick, txt, this.myroomjid, stamp);
|
||||
}
|
||||
}
|
||||
|
||||
ChatRoom.prototype.onPresenceError = function (pres, from) {
|
||||
if ($(pres).find('>error[type="auth"]>not-authorized[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) {
|
||||
console.log('on password required', from);
|
||||
this.eventEmitter.emit(XMPPEvents.PASSWORD_REQUIRED);
|
||||
} 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 === this.xmpp.options.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.
|
||||
this.eventEmitter.emit(XMPPEvents.ROOM_JOIN_ERROR, pres);
|
||||
|
||||
} else {
|
||||
console.warn('onPresError ', pres);
|
||||
this.eventEmitter.emit(XMPPEvents.ROOM_CONNECT_ERROR, pres);
|
||||
}
|
||||
} else {
|
||||
console.warn('onPresError ', pres);
|
||||
this.eventEmitter.emit(XMPPEvents.ROOM_CONNECT_ERROR, pres);
|
||||
}
|
||||
};
|
||||
|
||||
ChatRoom.prototype.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);
|
||||
});
|
||||
};
|
||||
|
||||
ChatRoom.prototype.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);
|
||||
};
|
||||
|
||||
ChatRoom.prototype.addToPresence = function (key, values) {
|
||||
values.tagName = key;
|
||||
this.presMap["nodes"].push(values);
|
||||
};
|
||||
|
||||
ChatRoom.prototype.removeFromPresence = function (key) {
|
||||
for(var i = 0; i < this.presMap.nodes.length; i++)
|
||||
{
|
||||
if(key === this.presMap.nodes[i].tagName)
|
||||
this.presMap.nodes.splice(i, 1);
|
||||
}
|
||||
};
|
||||
|
||||
ChatRoom.prototype.addPresenceListener = function (name, handler) {
|
||||
this.presHandlers[name] = handler;
|
||||
}
|
||||
|
||||
ChatRoom.prototype.removePresenceListener = function (name) {
|
||||
delete this.presHandlers[name];
|
||||
}
|
||||
|
||||
ChatRoom.prototype.isModerator = function (jid) {
|
||||
return this.role === 'moderator';
|
||||
};
|
||||
|
||||
ChatRoom.prototype.getMemberRole = function (peerJid) {
|
||||
if (this.members[peerJid]) {
|
||||
return this.members[peerJid].role;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
ChatRoom.prototype.setJingleSession = function(session){
|
||||
this.session = session;
|
||||
this.session.room = this;
|
||||
};
|
||||
|
||||
|
||||
ChatRoom.prototype.removeStream = function (stream) {
|
||||
if(!this.session)
|
||||
return;
|
||||
this.session.peerconnection.removeStream(stream)
|
||||
}
|
||||
|
||||
ChatRoom.prototype.switchStreams = function (stream, oldStream, callback, isAudio) {
|
||||
if(this.session) {
|
||||
// FIXME: will block switchInProgress on true value in case of exception
|
||||
this.session.switchStreams(stream, oldStream, callback, isAudio);
|
||||
} else {
|
||||
// We are done immediately
|
||||
console.warn("No conference handler or conference not started yet");
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
ChatRoom.prototype.addStream = function (stream, callback) {
|
||||
if(this.session) {
|
||||
// FIXME: will block switchInProgress on true value in case of exception
|
||||
this.session.addStream(stream, callback);
|
||||
} else {
|
||||
// We are done immediately
|
||||
console.warn("No conference handler or conference not started yet");
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
ChatRoom.prototype.setVideoMute = function (mute, callback, options) {
|
||||
var self = this;
|
||||
var localCallback = function (mute) {
|
||||
self.sendVideoInfoPresence(mute);
|
||||
return callback(mute);
|
||||
};
|
||||
|
||||
if(this.session)
|
||||
{
|
||||
this.session.setVideoMute(
|
||||
mute, localCallback, options);
|
||||
}
|
||||
else {
|
||||
localCallback(mute);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
ChatRoom.prototype.setAudioMute = function (mute, callback) {
|
||||
//This will be for remote streams only
|
||||
// if (this.forceMuted && !mute) {
|
||||
// console.info("Asking focus for unmute");
|
||||
// this.connection.moderate.setMute(this.connection.emuc.myroomjid, mute);
|
||||
// // FIXME: wait for result before resetting muted status
|
||||
// this.forceMuted = false;
|
||||
// }
|
||||
|
||||
|
||||
return this.sendAudioInfoPresence(mute, callback);;
|
||||
};
|
||||
|
||||
ChatRoom.prototype.addAudioInfoToPresence = function (mute) {
|
||||
this.addToPresence("audiomuted",
|
||||
{attributes:
|
||||
{"audions": "http://jitsi.org/jitmeet/audio"},
|
||||
value: mute.toString()});
|
||||
}
|
||||
|
||||
ChatRoom.prototype.sendAudioInfoPresence = function(mute, callback) {
|
||||
this.addAudioInfoToPresence(mute);
|
||||
if(this.connection) {
|
||||
this.sendPresence();
|
||||
}
|
||||
callback();
|
||||
};
|
||||
|
||||
ChatRoom.prototype.addVideoInfoToPresence = function (mute) {
|
||||
this.addToPresence("videomuted",
|
||||
{attributes:
|
||||
{"videons": "http://jitsi.org/jitmeet/video"},
|
||||
value: mute.toString()});
|
||||
}
|
||||
|
||||
|
||||
ChatRoom.prototype.sendVideoInfoPresence = function (mute) {
|
||||
this.addVideoInfoToPresence(mute);
|
||||
if(!this.connection)
|
||||
return;
|
||||
this.sendPresence();
|
||||
};
|
||||
|
||||
ChatRoom.prototype.addListener = function(type, listener) {
|
||||
this.eventEmitter.on(type, listener);
|
||||
};
|
||||
|
||||
ChatRoom.prototype.removeListener = function (type, listener) {
|
||||
this.eventEmitter.removeListener(type, listener);
|
||||
};
|
||||
|
||||
ChatRoom.prototype.fireRemoteStreamEvent = function(data, sid, thessrc) {
|
||||
this.eventEmitter.emit(XMPPEvents.REMOTE_STREAM_RECEIVED, data, sid, thessrc);
|
||||
}
|
||||
|
||||
ChatRoom.prototype.addLocalStreams = function (localStreams) {
|
||||
this.session.addLocalStreams(localStreams);
|
||||
}
|
||||
|
||||
module.exports = ChatRoom;
|
|
@ -46,6 +46,9 @@ function JingleSession(me, sid, connection, service, eventEmitter) {
|
|||
|
||||
// ICE servers config (RTCConfiguration?).
|
||||
this.ice_config = {};
|
||||
|
||||
// The chat room instance associated with the session.
|
||||
this.room = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -70,6 +70,7 @@ JingleSessionPC.prototype.setAnswer = function(answer) {
|
|||
JingleSessionPC.prototype.updateModifySourcesQueue = function() {
|
||||
var signalingState = this.peerconnection.signalingState;
|
||||
var iceConnectionState = this.peerconnection.iceConnectionState;
|
||||
console.debug(signalingState + " + " + iceConnectionState);
|
||||
if (signalingState === 'stable' && iceConnectionState === 'connected') {
|
||||
this.modifySourcesQueue.resume();
|
||||
} else {
|
||||
|
@ -108,10 +109,13 @@ JingleSessionPC.prototype.doInitialize = function () {
|
|||
this.peerconnection.onremovestream = function (event) {
|
||||
// Remove the stream from remoteStreams
|
||||
// FIXME: remotestreamremoved.jingle not defined anywhere(unused)
|
||||
|
||||
$(document).trigger('remotestreamremoved.jingle', [event, self.sid]);
|
||||
};
|
||||
this.peerconnection.onsignalingstatechange = function (event) {
|
||||
console.debug("signaling state11");
|
||||
if (!(self && self.peerconnection)) return;
|
||||
console.debug("signaling state222");
|
||||
self.updateModifySourcesQueue();
|
||||
};
|
||||
/**
|
||||
|
@ -122,7 +126,9 @@ JingleSessionPC.prototype.doInitialize = function () {
|
|||
* @param event the event containing information about the change
|
||||
*/
|
||||
this.peerconnection.oniceconnectionstatechange = function (event) {
|
||||
console.debug("ice state11");
|
||||
if (!(self && self.peerconnection)) return;
|
||||
console.debug("ice state222");
|
||||
self.updateModifySourcesQueue();
|
||||
switch (self.peerconnection.iceConnectionState) {
|
||||
case 'connected':
|
||||
|
@ -1100,6 +1106,40 @@ JingleSessionPC.prototype.switchStreams = function (new_stream, oldStream, succe
|
|||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds streams.
|
||||
* @param stream new stream that will be added.
|
||||
* @param success_callback callback executed after successful stream addition.
|
||||
*/
|
||||
JingleSessionPC.prototype.addStream = function (stream, callback) {
|
||||
|
||||
// Remember SDP to figure out added/removed SSRCs
|
||||
var oldSdp = null;
|
||||
if(this.peerconnection) {
|
||||
if(this.peerconnection.localDescription) {
|
||||
oldSdp = new SDP(this.peerconnection.localDescription.sdp);
|
||||
}
|
||||
if(stream)
|
||||
this.peerconnection.addStream(stream);
|
||||
}
|
||||
|
||||
// Conference is not active
|
||||
if(!oldSdp || !this.peerconnection) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
self.modifySourcesQueue.push(function() {
|
||||
console.log('modify sources done');
|
||||
|
||||
callback();
|
||||
|
||||
var newSdp = new SDP(this.peerconnection.localDescription.sdp);
|
||||
console.log("SDPs", oldSdp, newSdp);
|
||||
this.notifyMySSRCUpdate(oldSdp, newSdp);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Figures out added/removed ssrcs and send update IQs.
|
||||
* @param old_sdp SDP object for old description.
|
||||
|
@ -1430,7 +1470,7 @@ JingleSessionPC.prototype.remoteStreamAdded = function (data, times) {
|
|||
}
|
||||
}
|
||||
|
||||
RTC.createRemoteStream(data, this.sid, thessrc);
|
||||
this.room.fireRemoteStreamEvent(data, this.sid, thessrc);
|
||||
|
||||
var isVideo = data.stream.getVideoTracks().length > 0;
|
||||
// an attempt to work around https://github.com/jitsi/jitmeet/issues/32
|
||||
|
|
|
@ -5,6 +5,7 @@ var SSRCReplacement = require("./LocalSSRCReplacement");
|
|||
|
||||
function TraceablePeerConnection(ice_config, constraints, session) {
|
||||
var self = this;
|
||||
this.session = session;
|
||||
var RTCPeerConnectionType = null;
|
||||
if (RTCBrowserType.isFirefox()) {
|
||||
RTCPeerConnectionType = mozRTCPeerConnection;
|
||||
|
@ -374,7 +375,7 @@ TraceablePeerConnection.prototype.createOffer
|
|||
|
||||
offer = SSRCReplacement.mungeLocalVideoSSRC(offer);
|
||||
|
||||
if (config.enableSimulcast && self.simulcast.isSupported()) {
|
||||
if (self.session.room.options.enableSimulcast && self.simulcast.isSupported()) {
|
||||
offer = self.simulcast.mungeLocalDescription(offer);
|
||||
self.trace('createOfferOnSuccess::postTransform (simulcast)', dumpSDP(offer));
|
||||
}
|
||||
|
@ -404,7 +405,7 @@ TraceablePeerConnection.prototype.createAnswer
|
|||
// munge local video SSRC
|
||||
answer = SSRCReplacement.mungeLocalVideoSSRC(answer);
|
||||
|
||||
if (config.enableSimulcast && self.simulcast.isSupported()) {
|
||||
if (self.session.room.options.enableSimulcast && self.simulcast.isSupported()) {
|
||||
answer = self.simulcast.mungeLocalDescription(answer);
|
||||
self.trace('createAnswerOnSuccess::postTransform (simulcast)', dumpSDP(answer));
|
||||
}
|
||||
|
|
|
@ -2,496 +2,7 @@
|
|||
/* a simple MUC connection plugin
|
||||
* can only handle a single MUC room
|
||||
*/
|
||||
var XMPPEvents = require("../../service/xmpp/XMPPEvents");
|
||||
var Moderator = require("./moderator");
|
||||
var RTC = require("../RTC/RTC");
|
||||
var EventEmitter = require("events");
|
||||
|
||||
var parser = {
|
||||
packet2JSON: function (packet, nodes) {
|
||||
var self = this;
|
||||
$(packet).children().each(function (index) {
|
||||
var tagName = $(this).prop("tagName");
|
||||
var node = {}
|
||||
node["tagName"] = tagName;
|
||||
node.attributes = {};
|
||||
$($(this)[0].attributes).each(function( index, attr ) {
|
||||
node.attributes[ attr.name ] = attr.value;
|
||||
} );
|
||||
var text = Strophe.getText($(this)[0]);
|
||||
if(text)
|
||||
node.value = text;
|
||||
node.children = [];
|
||||
nodes.push(node);
|
||||
self.packet2JSON($(this), node.children);
|
||||
})
|
||||
},
|
||||
JSON2packet: function (nodes, packet) {
|
||||
for(var i = 0; i < nodes.length; i++)
|
||||
{
|
||||
var node = nodes[i];
|
||||
if(!node || node === null){
|
||||
continue;
|
||||
}
|
||||
packet.c(node.tagName, node.attributes);
|
||||
if(node.value)
|
||||
packet.t(node.value);
|
||||
if(node.children)
|
||||
this.JSON2packet(node.children, packet);
|
||||
packet.up();
|
||||
}
|
||||
packet.up();
|
||||
}
|
||||
};
|
||||
|
||||
function ChatRoom(connection, jid, password, XMPP, eventEmitter)
|
||||
{
|
||||
this.eventEmitter = eventEmitter;
|
||||
this.roomEmitter = new EventEmitter();
|
||||
this.xmpp = XMPP;
|
||||
this.connection = connection;
|
||||
this.roomjid = Strophe.getBareJidFromJid(jid);
|
||||
this.myroomjid = jid;
|
||||
this.password = password;
|
||||
console.info("Joined MUC as " + this.myroomjid);
|
||||
this.members = {};
|
||||
this.presMap = {};
|
||||
this.presHandlers = {};
|
||||
this.joined = false;
|
||||
this.role = null;
|
||||
this.focusMucJid = null;
|
||||
this.bridgeIsDown = false;
|
||||
this.moderator = new Moderator(this.roomjid, this.xmpp, eventEmitter);
|
||||
this.initPresenceMap();
|
||||
this.readyToJoin = false;
|
||||
this.joinRequested = false;
|
||||
var self = this;
|
||||
this.moderator.allocateConferenceFocus(function()
|
||||
{
|
||||
self.readyToJoin = true;
|
||||
if(self.joinRequested)
|
||||
{
|
||||
self.join();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ChatRoom.prototype.initPresenceMap = function () {
|
||||
this.presMap['to'] = this.myroomjid;
|
||||
this.presMap['xns'] = 'http://jabber.org/protocol/muc';
|
||||
this.presMap["nodes"] = [];
|
||||
if (RTC.localAudio && RTC.localAudio.isMuted()) {
|
||||
this.nodes.push({
|
||||
tagName: "audiomuted",
|
||||
attributes: {xmlns: "http://jitsi.org/jitmeet/audio"},
|
||||
value: "true"});
|
||||
}
|
||||
if (RTC.localVideo && RTC.localVideo.isMuted()) {
|
||||
this.nodes.push({
|
||||
tagName: "videomuted",
|
||||
attributes: {xmlns: "http://jitsi.org/jitmeet/video"},
|
||||
value: "true"});
|
||||
}
|
||||
this.presMap["nodes"].push( {
|
||||
"tagName": "user-agent",
|
||||
"value": navigator.userAgent,
|
||||
"attributes": {xmlns: 'http://jitsi.org/jitmeet/user-agent'}
|
||||
});
|
||||
};
|
||||
|
||||
ChatRoom.prototype.join = function (password) {
|
||||
if(password)
|
||||
this.password = password;
|
||||
if(!this.readyToJoin)
|
||||
{
|
||||
this.joinRequested = true;
|
||||
return;
|
||||
}
|
||||
this.joinRequested = false;
|
||||
this.sendPresence();
|
||||
}
|
||||
|
||||
ChatRoom.prototype.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.password) {
|
||||
pres.c('password').t(this.password).up();
|
||||
}
|
||||
|
||||
pres.up();
|
||||
|
||||
// Send XEP-0115 'c' stanza that contains our capabilities info
|
||||
if (this.connection.caps) {
|
||||
this.connection.caps.node = this.xmpp.options.clientNode;
|
||||
pres.c('c', this.connection.caps.generateCapsAttrs()).up();
|
||||
}
|
||||
|
||||
parser.JSON2packet(this.presMap.nodes, pres);
|
||||
this.connection.send(pres);
|
||||
};
|
||||
|
||||
|
||||
ChatRoom.prototype.doLeave = function () {
|
||||
console.log("do leave", this.myroomjid);
|
||||
var pres = $pres({to: this.myroomjid, type: 'unavailable' });
|
||||
this.presMap.length = 0;
|
||||
this.connection.send(pres);
|
||||
};
|
||||
|
||||
|
||||
ChatRoom.prototype.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");
|
||||
});
|
||||
};
|
||||
|
||||
ChatRoom.prototype.onPresence = function (pres) {
|
||||
var from = pres.getAttribute('from');
|
||||
// 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(this.moderator.getFocusUserJid() + "/") == 0) {
|
||||
member.isFocus = true;
|
||||
}
|
||||
|
||||
$(pres).find(">x").remove();
|
||||
var nodes = [];
|
||||
parser.packet2JSON(pres, nodes);
|
||||
for(var i = 0; i < nodes.length; i++)
|
||||
{
|
||||
var node = nodes[i];
|
||||
switch(node.tagName)
|
||||
{
|
||||
case "nick":
|
||||
member.nick = node.value;
|
||||
if(!member.isFocus) {
|
||||
var displayName = !this.xmpp.options.displayJids
|
||||
? member.nick : Strophe.getResourceFromJid(from);
|
||||
|
||||
if (displayName && displayName.length > 0) {
|
||||
this.eventEmitter.emit(XMPPEvents.DISPLAY_NAME_CHANGED, from, displayName);
|
||||
}
|
||||
console.info("Display name: " + displayName, pres);
|
||||
}
|
||||
break;
|
||||
case "userId":
|
||||
member.id = node.value;
|
||||
break;
|
||||
case "email":
|
||||
member.email = node.value;
|
||||
break;
|
||||
case "bridgeIsDown":
|
||||
if(!this.bridgeIsDown) {
|
||||
this.bridgeIsDown = true;
|
||||
this.eventEmitter.emit(XMPPEvents.BRIDGE_DOWN);
|
||||
}
|
||||
break;
|
||||
default :
|
||||
this.processNode(node, from);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (from == this.myroomjid) {
|
||||
if (member.affiliation == 'owner')
|
||||
|
||||
if (this.role !== member.role) {
|
||||
this.role = member.role;
|
||||
|
||||
this.eventEmitter.emit(XMPPEvents.LOCAL_ROLE_CHANGED,
|
||||
member, this.isModerator());
|
||||
}
|
||||
if (!this.joined) {
|
||||
this.joined = true;
|
||||
this.eventEmitter.emit(XMPPEvents.MUC_JOINED, from, member);
|
||||
}
|
||||
} else if (this.members[from] === undefined) {
|
||||
// new participant
|
||||
this.members[from] = member;
|
||||
console.log('entered', from, member);
|
||||
if (member.isFocus) {
|
||||
this.focusMucJid = from;
|
||||
console.info("Ignore focus: " + from + ", real JID: " + member.jid);
|
||||
}
|
||||
else {
|
||||
this.eventEmitter.emit(XMPPEvents.MUC_MEMBER_JOINED, from, member.id || member.email, member.nick);
|
||||
}
|
||||
} else {
|
||||
// Presence update for existing participant
|
||||
// Watch role change:
|
||||
if (this.members[from].role != member.role) {
|
||||
this.members[from].role = member.role;
|
||||
this.eventEmitter.emit(XMPPEvents.MUC_ROLE_CHANGED,
|
||||
member.role, member.nick);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
if(!member.isFocus)
|
||||
this.eventEmitter.emit(XMPPEvents.USER_ID_CHANGED, from, member.id || member.email);
|
||||
|
||||
// Trigger status message update
|
||||
if (member.status) {
|
||||
this.eventEmitter.emit(XMPPEvents.PRESENCE_STATUS, from, member);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
ChatRoom.prototype.processNode = function (node, from) {
|
||||
if(this.presHandlers[node.tagName])
|
||||
this.presHandlers[node.tagName](node, from);
|
||||
};
|
||||
|
||||
ChatRoom.prototype.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);
|
||||
this.eventEmitter.emit(XMPPEvents.SENDING_CHAT_MESSAGE, body);
|
||||
};
|
||||
|
||||
ChatRoom.prototype.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);
|
||||
};
|
||||
|
||||
|
||||
ChatRoom.prototype.onParticipantLeft = function (jid) {
|
||||
|
||||
this.eventEmitter.emit(XMPPEvents.MUC_MEMBER_LEFT, jid);
|
||||
|
||||
this.moderator.onMucMemberLeft(jid);
|
||||
};
|
||||
|
||||
ChatRoom.prototype.onPresenceUnavailable = function (pres, 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();
|
||||
}
|
||||
|
||||
this.xmpp.disposeConference(false);
|
||||
this.eventEmitter.emit(XMPPEvents.MUC_DESTROYED, reason);
|
||||
delete this.connection.emuc.rooms[Strophe.getBareJidFromJid(jid)];
|
||||
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.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 (Object.keys(this.members).length > 1) {
|
||||
for (var i in this.members) {
|
||||
var member = this.members[i];
|
||||
delete this.members[i];
|
||||
this.onParticipantLeft(member);
|
||||
}
|
||||
}
|
||||
if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="307"]').length) {
|
||||
if (this.myroomjid === from) {
|
||||
this.xmpp.disposeConference(false);
|
||||
this.eventEmitter.emit(XMPPEvents.KICKED);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ChatRoom.prototype.onMessage = function (msg, 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") {
|
||||
this.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 == "") {
|
||||
this.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);
|
||||
this.eventEmitter.emit(XMPPEvents.MESSAGE_RECEIVED,
|
||||
from, nick, txt, this.myroomjid, stamp);
|
||||
}
|
||||
}
|
||||
|
||||
ChatRoom.prototype.onPresenceError = function (pres, from) {
|
||||
if ($(pres).find('>error[type="auth"]>not-authorized[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) {
|
||||
console.log('on password required', from);
|
||||
this.eventEmitter.emit(XMPPEvents.PASSWORD_REQUIRED);
|
||||
} 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 === this.xmpp.options.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.
|
||||
this.eventEmitter.emit(XMPPEvents.ROOM_JOIN_ERROR, pres);
|
||||
|
||||
} else {
|
||||
console.warn('onPresError ', pres);
|
||||
this.eventEmitter.emit(XMPPEvents.ROOM_CONNECT_ERROR, pres);
|
||||
}
|
||||
} else {
|
||||
console.warn('onPresError ', pres);
|
||||
this.eventEmitter.emit(XMPPEvents.ROOM_CONNECT_ERROR, pres);
|
||||
}
|
||||
};
|
||||
|
||||
ChatRoom.prototype.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);
|
||||
});
|
||||
};
|
||||
|
||||
ChatRoom.prototype.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);
|
||||
};
|
||||
|
||||
ChatRoom.prototype.addToPresence = function (key, values) {
|
||||
values.tagName = key;
|
||||
this.presMap["nodes"].push(values);
|
||||
};
|
||||
|
||||
ChatRoom.prototype.removeFromPresence = function (key) {
|
||||
for(var i = 0; i < this.presMap.nodes.length; i++)
|
||||
{
|
||||
if(key === this.presMap.nodes[i].tagName)
|
||||
this.presMap.nodes.splice(i, 1);
|
||||
}
|
||||
};
|
||||
|
||||
ChatRoom.prototype.addPresenceListener = function (name, handler) {
|
||||
this.presHandlers[name] = handler;
|
||||
}
|
||||
|
||||
ChatRoom.prototype.removePresenceListener = function (name) {
|
||||
delete this.presHandlers[name];
|
||||
}
|
||||
|
||||
ChatRoom.prototype.isModerator = function (jid) {
|
||||
return this.role === 'moderator';
|
||||
};
|
||||
|
||||
ChatRoom.prototype.getMemberRole = function (peerJid) {
|
||||
if (this.members[peerJid]) {
|
||||
return this.members[peerJid].role;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
var ChatRoom = require("./ChatRoom");
|
||||
|
||||
module.exports = function(XMPP) {
|
||||
Strophe.addConnectionPlugin('emuc', {
|
||||
|
@ -505,13 +16,13 @@ module.exports = function(XMPP) {
|
|||
this.connection.addHandler(this.onPresenceError.bind(this), null, 'presence', 'error', null);
|
||||
this.connection.addHandler(this.onMessage.bind(this), null, 'message', null, null);
|
||||
},
|
||||
createRoom: function (jid, password, eventEmitter) {
|
||||
createRoom: function (jid, password, options) {
|
||||
var roomJid = Strophe.getBareJidFromJid(jid);
|
||||
if (this.rooms[roomJid]) {
|
||||
console.error("You are already in the room!");
|
||||
return;
|
||||
}
|
||||
this.rooms[roomJid] = new ChatRoom(this.connection, jid, password, XMPP, eventEmitter);
|
||||
this.rooms[roomJid] = new ChatRoom(this.connection, jid, password, XMPP, options);
|
||||
return this.rooms[roomJid];
|
||||
},
|
||||
doLeave: function (jid) {
|
||||
|
@ -566,6 +77,14 @@ module.exports = function(XMPP) {
|
|||
|
||||
room.onMessage(msg, from);
|
||||
return true;
|
||||
},
|
||||
|
||||
setJingleSession: function (from, session) {
|
||||
var room = this.rooms[Strophe.getBareJidFromJid(from)];
|
||||
if(!room)
|
||||
return;
|
||||
|
||||
room.setJingleSession(session);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -104,10 +104,14 @@ module.exports = function(XMPP, eventEmitter) {
|
|||
this.connection, XMPP, eventEmitter);
|
||||
// configure session
|
||||
|
||||
var fromBareJid = Strophe.getBareJidFromJid(fromJid);
|
||||
this.connection.emuc.setJingleSession(fromBareJid, sess);
|
||||
|
||||
sess.media_constraints = this.media_constraints;
|
||||
sess.ice_config = this.ice_config;
|
||||
|
||||
sess.initialize(Strophe.getBareJidFromJid(fromJid), false);
|
||||
sess.initialize(fromJid, false);
|
||||
eventEmitter.emit(XMPPEvents.CALL_INCOMING, sess);
|
||||
// FIXME: setRemoteDescription should only be done when this call is to be accepted
|
||||
sess.setOffer($(iq).find('>jingle'));
|
||||
|
||||
|
@ -118,8 +122,6 @@ module.exports = function(XMPP, eventEmitter) {
|
|||
// .sendAnswer and .accept
|
||||
// or .sendTerminate -- not necessarily synchronous
|
||||
|
||||
eventEmitter.emit(XMPPEvents.CALL_INCOMING, sess);
|
||||
|
||||
sess.sendAnswer();
|
||||
sess.accept();
|
||||
break;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
/* global $, APP, config, Strophe*/
|
||||
var Moderator = require("./moderator");
|
||||
var EventEmitter = require("events");
|
||||
var Pako = require("pako");
|
||||
var StreamEventTypes = require("../../service/RTC/StreamEventTypes");
|
||||
|
@ -165,7 +164,7 @@ XMPP.prototype.connect = function (jid, password) {
|
|||
return this._connect(jid, password);
|
||||
};
|
||||
|
||||
XMPP.prototype.createRoom = function (roomName, useNicks, nick) {
|
||||
XMPP.prototype.createRoom = function (roomName, options, useNicks, nick) {
|
||||
var roomjid = roomName + '@' + this.options.hosts.muc;
|
||||
|
||||
if (useNicks) {
|
||||
|
@ -183,7 +182,7 @@ XMPP.prototype.createRoom = function (roomName, useNicks, nick) {
|
|||
roomjid += '/' + tmpJid;
|
||||
}
|
||||
|
||||
return this.connection.emuc.createRoom(roomjid, null, this.eventEmitter);
|
||||
return this.connection.emuc.createRoom(roomjid, null, options);
|
||||
}
|
||||
|
||||
XMPP.prototype.addListener = function(type, listener) {
|
||||
|
@ -194,6 +193,7 @@ XMPP.prototype.removeListener = function (type, listener) {
|
|||
this.eventEmitter.removeListener(type, listener);
|
||||
};
|
||||
|
||||
//FIXME: this should work with the room
|
||||
XMPP.prototype.leaveRoom = function (jid) {
|
||||
var handler = this.connection.jingle.jid2session[jid];
|
||||
if (handler && handler.peerconnection) {
|
||||
|
@ -213,80 +213,6 @@ XMPP.prototype.leaveRoom = function (jid) {
|
|||
this.connection.emuc.doLeave(jid);
|
||||
};
|
||||
|
||||
XMPP.prototype.isConferenceInProgress = function () {
|
||||
return this.connection && this.connection.jingle.activecall &&
|
||||
this.connection.jingle.activecall.peerconnection;
|
||||
};
|
||||
|
||||
XMPP.prototype.switchStreams = function (stream, oldStream, callback, isAudio) {
|
||||
if (this.isConferenceInProgress()) {
|
||||
// FIXME: will block switchInProgress on true value in case of exception
|
||||
this.connection.jingle.activecall.switchStreams(stream, oldStream, callback, isAudio);
|
||||
} else {
|
||||
// We are done immediately
|
||||
console.warn("No conference handler or conference not started yet");
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
XMPP.prototype.sendVideoInfoPresence = function (mute) {
|
||||
if(!this.connection)
|
||||
return;
|
||||
this.connection.emuc.addVideoInfoToPresence(mute);
|
||||
this.connection.emuc.sendPresence();
|
||||
};
|
||||
|
||||
XMPP.prototype.setVideoMute = function (mute, callback, options) {
|
||||
if(!this.connection)
|
||||
return;
|
||||
var self = this;
|
||||
var localCallback = function (mute) {
|
||||
self.sendVideoInfoPresence(mute);
|
||||
return callback(mute);
|
||||
};
|
||||
|
||||
if(this.connection.jingle.activecall)
|
||||
{
|
||||
this.connection.jingle.activecall.setVideoMute(
|
||||
mute, localCallback, options);
|
||||
}
|
||||
else {
|
||||
localCallback(mute);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
XMPP.prototype.setAudioMute = function (mute, callback) {
|
||||
if (!(this.connection && RTC.localAudio)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.forceMuted && !mute) {
|
||||
console.info("Asking focus for unmute");
|
||||
this.connection.moderate.setMute(this.connection.emuc.myroomjid, mute);
|
||||
// FIXME: wait for result before resetting muted status
|
||||
this.forceMuted = false;
|
||||
}
|
||||
|
||||
if (mute == RTC.localAudio.isMuted()) {
|
||||
// Nothing to do
|
||||
return true;
|
||||
}
|
||||
|
||||
RTC.localAudio.setMute(mute);
|
||||
this.sendAudioInfoPresence(mute, callback);
|
||||
return true;
|
||||
};
|
||||
|
||||
XMPP.prototype.sendAudioInfoPresence = function(mute, callback) {
|
||||
if(this.connection) {
|
||||
this.connection.emuc.addAudioInfoToPresence(mute);
|
||||
this.connection.emuc.sendPresence();
|
||||
}
|
||||
callback();
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends 'data' as a log message to the focus. Returns true iff a message
|
||||
* was sent.
|
||||
|
@ -351,11 +277,6 @@ XMPP.prototype.getSessions = function () {
|
|||
return this.connection.jingle.sessions;
|
||||
};
|
||||
|
||||
XMPP.prototype.removeStream = function (stream) {
|
||||
if (!this.isConferenceInProgress())
|
||||
return;
|
||||
this.connection.jingle.activecall.peerconnection.removeStream(stream);
|
||||
};
|
||||
|
||||
XMPP.prototype.disconnect = function () {
|
||||
if (this.disconnectInProgress || !this.connection || !this.connection.connected)
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
"pako": "*",
|
||||
"i18next-client": "1.7.7",
|
||||
"sdp-interop": "0.1.4",
|
||||
"sdp-transform": "1.4.0",
|
||||
"sdp-transform": "1.4.1",
|
||||
"sdp-simulcast": "0.1.0",
|
||||
"async": "0.9.0",
|
||||
"retry": "0.6.1",
|
||||
|
|
|
@ -50,6 +50,7 @@ var XMPPEvents = {
|
|||
ROOM_CONNECT_ERROR: 'xmpp.room_connect_error',
|
||||
// xmpp is connected and obtained user media
|
||||
READY_TO_JOIN: 'xmpp.ready_to_join',
|
||||
FOCUS_LEFT: "xmpp.focus_left"
|
||||
FOCUS_LEFT: "xmpp.focus_left",
|
||||
REMOTE_STREAM_RECEIVED: "xmpp.remote_stream_received"
|
||||
};
|
||||
module.exports = XMPPEvents;
|
Loading…
Reference in New Issue