Re-uses SSRC of the first video stream created for any streams created in future. Does video mute and switching to the screen stream without 'source-add'/'source-remove' signaling. Moves video type signaling from Jingle to MUC presence.

This commit is contained in:
paweldomas 2015-08-05 14:09:12 +02:00
parent 9a31fa3d63
commit 74e7507a73
15 changed files with 328 additions and 89 deletions

View File

@ -198,7 +198,7 @@ var RTC = {
}, },
changeLocalVideo: function (stream, isUsingScreenStream, callback) { changeLocalVideo: function (stream, isUsingScreenStream, callback) {
var oldStream = this.localVideo.getOriginalStream(); var oldStream = this.localVideo.getOriginalStream();
var type = (isUsingScreenStream? "screen" : "video"); var type = (isUsingScreenStream ? "screen" : "camera");
var localCallback = callback; var localCallback = callback;
if(this.localVideo.isMuted() && this.localVideo.videoType !== type) { if(this.localVideo.isMuted() && this.localVideo.videoType !== type) {
localCallback = function() { localCallback = function() {
@ -242,32 +242,6 @@ var RTC = {
return APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE].muted; return APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE].muted;
} }
}, },
/**
* Checks if video identified by given src is desktop stream.
* @param videoSrc eg.
* blob:https%3A//pawel.jitsi.net/9a46e0bd-131e-4d18-9c14-a9264e8db395
* @returns {boolean}
*/
isVideoSrcDesktop: function (jid) {
if(!jid)
return false;
var isDesktop = false;
var stream = null;
if (APP.xmpp.myJid() === jid) {
// local video
stream = this.localVideo;
} else {
var peerStreams = this.remoteStreams[jid];
if(!peerStreams)
return false;
stream = peerStreams[MediaStreamType.VIDEO_TYPE];
}
if(stream)
isDesktop = (stream.videoType === "screen");
return isDesktop;
},
setVideoMute: function (mute, callback, options) { setVideoMute: function (mute, callback, options) {
if (!this.localVideo) if (!this.localVideo)
return; return;

View File

@ -522,7 +522,7 @@ RTCUtils.prototype.handleLocalStream = function(stream, usageOptions) {
this.service.createLocalStream(audioStream, "audio", null, null, this.service.createLocalStream(audioStream, "audio", null, null,
audioMuted, audioGUM); audioMuted, audioGUM);
this.service.createLocalStream(videoStream, "video", null, null, this.service.createLocalStream(videoStream, "video", null, 'camera',
videoMuted, videoGUM); videoMuted, videoGUM);
}; };

View File

@ -154,9 +154,6 @@ function registerListeners() {
APP.RTC.addStreamListener(function (stream) { APP.RTC.addStreamListener(function (stream) {
VideoLayout.onRemoteStreamAdded(stream); VideoLayout.onRemoteStreamAdded(stream);
}, StreamEventTypes.EVENT_TYPE_REMOTE_CREATED); }, StreamEventTypes.EVENT_TYPE_REMOTE_CREATED);
APP.RTC.addStreamListener(function (jid) {
VideoLayout.onVideoTypeChanged(jid);
}, StreamEventTypes.EVENT_TYPE_REMOTE_CHANGED);
APP.RTC.addListener(RTCEvents.LASTN_CHANGED, onLastNChanged); APP.RTC.addListener(RTCEvents.LASTN_CHANGED, onLastNChanged);
APP.RTC.addListener(RTCEvents.DOMINANTSPEAKER_CHANGED, APP.RTC.addListener(RTCEvents.DOMINANTSPEAKER_CHANGED,
function (resourceJid) { function (resourceJid) {
@ -264,6 +261,7 @@ function registerListeners() {
APP.xmpp.addListener(XMPPEvents.ETHERPAD, initEtherpad); APP.xmpp.addListener(XMPPEvents.ETHERPAD, initEtherpad);
APP.xmpp.addListener(XMPPEvents.AUTHENTICATION_REQUIRED, APP.xmpp.addListener(XMPPEvents.AUTHENTICATION_REQUIRED,
onAuthenticationRequired); onAuthenticationRequired);
APP.xmpp.addListener(XMPPEvents.VIDEO_TYPE, onPeerVideoTypeChanged);
APP.xmpp.addListener(XMPPEvents.DEVICE_AVAILABLE, APP.xmpp.addListener(XMPPEvents.DEVICE_AVAILABLE,
function (resource, devices) { function (resource, devices) {
VideoLayout.setDeviceAvailabilityIcons(resource, devices); VideoLayout.setDeviceAvailabilityIcons(resource, devices);
@ -607,6 +605,10 @@ function onMucPresenceStatus(jid, info) {
VideoLayout.setPresenceStatus(Strophe.getResourceFromJid(jid), info.status); VideoLayout.setPresenceStatus(Strophe.getResourceFromJid(jid), info.status);
} }
function onPeerVideoTypeChanged(resourceJid, newVideoType) {
VideoLayout.onVideoTypeChanged(resourceJid, newVideoType);
}
function onMucRoleChanged(role, displayName) { function onMucRoleChanged(role, displayName) {
VideoLayout.showModeratorIndicator(); VideoLayout.showModeratorIndicator();

View File

@ -255,7 +255,7 @@ function changeVideo(isVisible) {
"none"; "none";
} }
var isDesktop = APP.RTC.isVideoSrcDesktop(currentSmallVideo.peerJid); var isDesktop = currentSmallVideo.getVideoType() === 'screen';
// Change the way we'll be measuring and positioning large video // Change the way we'll be measuring and positioning large video
getVideoSize = isDesktop ? getDesktopVideoSize : getCameraVideoSize; getVideoSize = isDesktop ? getDesktopVideoSize : getCameraVideoSize;
@ -421,13 +421,12 @@ var LargeVideo = {
currentSmallVideo.enableDominantSpeaker(false); currentSmallVideo.enableDominantSpeaker(false);
} }
}, },
onVideoTypeChanged: function (jid) { onVideoTypeChanged: function (resourceJid, newVideoType) {
if(!isEnabled) if (!isEnabled)
return; return;
var resourceJid = Strophe.getResourceFromJid(jid);
if (LargeVideo.isCurrentlyOnLarge(resourceJid)) if (LargeVideo.isCurrentlyOnLarge(resourceJid))
{ {
var isDesktop = APP.RTC.isVideoSrcDesktop(jid); var isDesktop = newVideoType === 'screen';
getVideoSize = isDesktop ? getDesktopVideoSize : getCameraVideoSize; getVideoSize = isDesktop ? getDesktopVideoSize : getCameraVideoSize;
getVideoPosition = isDesktop ? getDesktopVideoPosition getVideoPosition = isDesktop ? getDesktopVideoPosition
: getCameraVideoPosition; : getCameraVideoPosition;

View File

@ -53,6 +53,22 @@ SmallVideo.prototype.setDeviceAvailabilityIcons = function (devices) {
} }
}; };
/**
* Sets the type of the video displayed by this instance.
* @param videoType 'camera' or 'screen'
*/
SmallVideo.prototype.setVideoType = function (videoType) {
this.videoType = videoType;
};
/**
* Returns the type of the video displayed by this instance.
* @returns {String} 'camera', 'screen' or undefined.
*/
SmallVideo.prototype.getVideoType = function () {
return this.videoType;
};
/** /**
* Shows the presence status message for the given video. * Shows the presence status message for the given video.
*/ */

View File

@ -12,6 +12,7 @@ var LargeVideo = require("./LargeVideo");
var LocalVideo = require("./LocalVideo"); var LocalVideo = require("./LocalVideo");
var remoteVideos = {}; var remoteVideos = {};
var remoteVideoTypes = {};
var localVideoThumbnail = null; var localVideoThumbnail = null;
var currentDominantSpeaker = null; var currentDominantSpeaker = null;
@ -68,6 +69,8 @@ var VideoLayout = (function (my) {
localVideoThumbnail.setDisplayName(); localVideoThumbnail.setDisplayName();
localVideoThumbnail.createConnectionIndicator(); localVideoThumbnail.createConnectionIndicator();
this.onVideoTypeChanged(APP.xmpp.myResource(), stream.videoType);
AudioLevels.updateAudioLevelCanvas(null, VideoLayout); AudioLevels.updateAudioLevelCanvas(null, VideoLayout);
localVideoThumbnail.changeVideo(stream, isMuted); localVideoThumbnail.changeVideo(stream, isMuted);
@ -252,14 +255,21 @@ var VideoLayout = (function (my) {
var resourceJid = Strophe.getResourceFromJid(peerJid); var resourceJid = Strophe.getResourceFromJid(peerJid);
if(!remoteVideos[resourceJid]) { if (!remoteVideos[resourceJid]) {
remoteVideos[resourceJid] = new RemoteVideo(peerJid, VideoLayout);
var remoteVideo = new RemoteVideo(peerJid, VideoLayout);
remoteVideos[resourceJid] = remoteVideo;
var videoType = remoteVideoTypes[resourceJid];
if (videoType) {
remoteVideo.setVideoType(videoType);
}
// In case this is not currently in the last n we don't show it. // In case this is not currently in the last n we don't show it.
if (localLastNCount && if (localLastNCount &&
localLastNCount > 0 && localLastNCount > 0 &&
$('#remoteVideos>span').length >= localLastNCount + 2) { $('#remoteVideos>span').length >= localLastNCount + 2) {
remoteVideos[resourceJid].showPeerContainer('hide'); remoteVideo.showPeerContainer('hide');
} }
else else
VideoLayout.resizeThumbnails(); VideoLayout.resizeThumbnails();
@ -809,8 +819,30 @@ var VideoLayout = (function (my) {
VideoLayout.resizeThumbnails(); VideoLayout.resizeThumbnails();
}; };
my.onVideoTypeChanged = function (jid) { my.onVideoTypeChanged = function (resourceJid, newVideoType) {
LargeVideo.onVideoTypeChanged(jid); if (remoteVideoTypes[resourceJid] === newVideoType) {
return;
}
console.info("Peer video type changed: ", resourceJid, newVideoType);
remoteVideoTypes[resourceJid] = newVideoType;
var smallVideo;
if (resourceJid === APP.xmpp.myResource()) {
if (!localVideoThumbnail) {
console.warn("Local video not ready yet");
return;
}
smallVideo = localVideoThumbnail;
} else if (remoteVideos[resourceJid]) {
smallVideo = remoteVideos[resourceJid];
} else {
return;
}
smallVideo.setVideoType(newVideoType);
LargeVideo.onVideoTypeChanged(resourceJid, newVideoType);
}; };
my.showMore = function (jid) { my.showMore = function (jid) {

View File

@ -7,6 +7,7 @@ var async = require("async");
var transform = require("sdp-transform"); var transform = require("sdp-transform");
var XMPPEvents = require("../../service/xmpp/XMPPEvents"); var XMPPEvents = require("../../service/xmpp/XMPPEvents");
var RTCBrowserType = require("../RTC/RTCBrowserType"); var RTCBrowserType = require("../RTC/RTCBrowserType");
var VideoSSRCHack = require("./VideoSSRCHack");
// Jingle stuff // Jingle stuff
function JingleSession(me, sid, connection, service, eventEmitter) { function JingleSession(me, sid, connection, service, eventEmitter) {
@ -210,10 +211,6 @@ function onIceConnectionStateChange(sid, session) {
} }
} }
JingleSession.prototype.getVideoType = function () {
return APP.desktopsharing.isUsingScreenStream() ? 'screen' : 'camera';
};
JingleSession.prototype.accept = function () { JingleSession.prototype.accept = function () {
this.state = 'active'; this.state = 'active';
@ -245,8 +242,7 @@ JingleSession.prototype.accept = function () {
prsdp.toJingle( prsdp.toJingle(
accept, accept,
this.initiator == this.me ? 'initiator' : 'responder', this.initiator == this.me ? 'initiator' : 'responder',
this.localStreamsSSRC, this.localStreamsSSRC);
self.getVideoType());
var sdp = this.peerconnection.localDescription.sdp; var sdp = this.peerconnection.localDescription.sdp;
while (SDPUtil.find_line(sdp, 'a=inactive')) { while (SDPUtil.find_line(sdp, 'a=inactive')) {
// FIXME: change any inactive to sendrecv or whatever they were originally // FIXME: change any inactive to sendrecv or whatever they were originally
@ -258,6 +254,8 @@ JingleSession.prototype.accept = function () {
//console.log('setLocalDescription success'); //console.log('setLocalDescription success');
self.setLocalDescription(); self.setLocalDescription();
VideoSSRCHack.processSessionInit(accept);
self.connection.sendIQ(accept, self.connection.sendIQ(accept,
function () { function () {
var ack = {}; var ack = {};
@ -348,8 +346,10 @@ JingleSession.prototype.sendIceCandidate = function (candidate) {
self.localSDP.toJingle( self.localSDP.toJingle(
init, init,
self.initiator == self.me ? 'initiator' : 'responder', self.initiator == self.me ? 'initiator' : 'responder',
ssrc, ssrc);
self.getVideoType());
VideoSSRCHack.processSessionInit(init);
self.connection.sendIQ(init, self.connection.sendIQ(init,
function () { function () {
//console.log('session initiate ack'); //console.log('session initiate ack');
@ -465,8 +465,10 @@ JingleSession.prototype.createdOffer = function (sdp) {
self.localSDP.toJingle( self.localSDP.toJingle(
init, init,
this.initiator == this.me ? 'initiator' : 'responder', this.initiator == this.me ? 'initiator' : 'responder',
this.localStreamsSSRC, this.localStreamsSSRC);
self.getVideoType());
VideoSSRCHack.processSessionInit(init);
self.connection.sendIQ(init, self.connection.sendIQ(init,
function () { function () {
var ack = {}; var ack = {};
@ -522,9 +524,7 @@ JingleSession.prototype.readSsrcInfo = function (contents) {
$(this).find('>ssrc-info[xmlns="http://jitsi.org/jitmeet"]').each( $(this).find('>ssrc-info[xmlns="http://jitsi.org/jitmeet"]').each(
function () { function () {
var owner = this.getAttribute('owner'); var owner = this.getAttribute('owner');
var videoType = this.getAttribute('video-type');
self.ssrcOwners[ssrc] = owner; self.ssrcOwners[ssrc] = owner;
self.ssrcVideoTypes[ssrc] = videoType;
} }
); );
}); });
@ -731,8 +731,10 @@ JingleSession.prototype.createdAnswer = function (sdp, provisional) {
self.localSDP.toJingle( self.localSDP.toJingle(
accept, accept,
self.initiator == self.me ? 'initiator' : 'responder', self.initiator == self.me ? 'initiator' : 'responder',
ssrcs, ssrcs);
self.getVideoType());
VideoSSRCHack.processSessionInit(accept);
self.connection.sendIQ(accept, self.connection.sendIQ(accept,
function () { function () {
var ack = {}; var ack = {};
@ -1129,7 +1131,13 @@ JingleSession.prototype.notifyMySSRCUpdate = function (old_sdp, new_sdp) {
} }
); );
var removed = sdpDiffer.toJingle(remove); var removed = sdpDiffer.toJingle(remove);
if (removed) {
// Let 'source-remove' IQ through the hack and see if we're allowed to send
// it in the current form
if (removed)
remove = VideoSSRCHack.processSourceRemove(remove);
if (removed && remove) {
this.connection.sendIQ(remove, this.connection.sendIQ(remove,
function (res) { function (res) {
console.info('got remove result', res); console.info('got remove result', res);
@ -1152,8 +1160,14 @@ JingleSession.prototype.notifyMySSRCUpdate = function (old_sdp, new_sdp) {
sid: this.sid sid: this.sid
} }
); );
var added = sdpDiffer.toJingle(add, this.getVideoType()); var added = sdpDiffer.toJingle(add);
if (added) {
// Let 'source-add' IQ through the hack and see if we're allowed to send
// it in the current form
if (added)
add = VideoSSRCHack.processSourceAdd(add);
if (added & add) {
this.connection.sendIQ(add, this.connection.sendIQ(add,
function (res) { function (res) {
console.info('got add result', res); console.info('got add result', res);
@ -1346,9 +1360,6 @@ JingleSession.prototype.setLocalDescription = function () {
var ssrc = newssrcs[i-1].ssrc; var ssrc = newssrcs[i-1].ssrc;
var myJid = self.connection.emuc.myroomjid; var myJid = self.connection.emuc.myroomjid;
self.ssrcOwners[ssrc] = myJid; self.ssrcOwners[ssrc] = myJid;
if (newssrcs[i-1].type === 'video'){
self.ssrcVideoTypes[ssrc] = self.getVideoType();
}
} }
} }
} }
@ -1420,9 +1431,7 @@ JingleSession.prototype.remoteStreamAdded = function (data, times) {
return; return;
} }
data.peerjid = self.ssrcOwners[thessrc]; data.peerjid = self.ssrcOwners[thessrc];
data.videoType = self.ssrcVideoTypes[thessrc] console.log('associated jid', self.ssrcOwners[thessrc]);
console.log('associated jid', self.ssrcOwners[thessrc],
thessrc, data.videoType);
} else { } else {
console.error("No SSRC lines for ", streamId); console.error("No SSRC lines for ", streamId);
} }

View File

@ -125,7 +125,7 @@ SDP.prototype.removeMediaLines = function(mediaindex, prefix) {
} }
// add content's to a jingle element // add content's to a jingle element
SDP.prototype.toJingle = function (elem, thecreator, ssrcs, videoType) { SDP.prototype.toJingle = function (elem, thecreator, ssrcs) {
// console.log("SSRC" + ssrcs["audio"] + " - " + ssrcs["video"]); // console.log("SSRC" + ssrcs["audio"] + " - " + ssrcs["video"]);
var i, j, k, mline, ssrc, rtpmap, tmp, line, lines; var i, j, k, mline, ssrc, rtpmap, tmp, line, lines;
var self = this; var self = this;
@ -258,14 +258,6 @@ SDP.prototype.toJingle = function (elem, thecreator, ssrcs, videoType) {
elem.up(); elem.up();
} }
} }
// Video type
if (videoType && mline.media == "video") {
elem.c('ssrc-info',
{
xmlns: 'http://jitsi.org/jitmeet',
'video-type': videoType
}).up();
}
elem.up(); elem.up();
// XEP-0339 handle ssrc-group attributes // XEP-0339 handle ssrc-group attributes

View File

@ -106,7 +106,7 @@ SDPDiffer.prototype.getNewMedia = function() {
* @param toJid destination Jid * @param toJid destination Jid
* @param isAdd indicates if this is remove or add operation. * @param isAdd indicates if this is remove or add operation.
*/ */
SDPDiffer.prototype.toJingle = function(modify, videoType) { SDPDiffer.prototype.toJingle = function(modify) {
var sdpMediaSsrcs = this.getNewMedia(); var sdpMediaSsrcs = this.getNewMedia();
var self = this; var self = this;
@ -141,15 +141,6 @@ SDPDiffer.prototype.toJingle = function(modify, videoType) {
} }
modify.up(); // end of parameter modify.up(); // end of parameter
}); });
// indicate video type
if (videoType && media.mid == 'video') {
modify.c('ssrc-info',
{
xmlns: 'http://jitsi.org/jitmeet',
'video-type': videoType
})
.up();
}
modify.up(); // end of source modify.up(); // end of source
}); });

View File

@ -1,6 +1,7 @@
var RTC = require('../RTC/RTC'); var RTC = require('../RTC/RTC');
var RTCBrowserType = require("../RTC/RTCBrowserType.js"); var RTCBrowserType = require("../RTC/RTCBrowserType.js");
var XMPPEvents = require("../../service/xmpp/XMPPEvents"); var XMPPEvents = require("../../service/xmpp/XMPPEvents");
var VideoSSRCHack = require("./VideoSSRCHack");
function TraceablePeerConnection(ice_config, constraints, session) { function TraceablePeerConnection(ice_config, constraints, session) {
var self = this; var self = this;
@ -211,6 +212,9 @@ if (TraceablePeerConnection.prototype.__defineGetter__ !== undefined) {
'localDescription', 'localDescription',
function() { function() {
var desc = this.peerconnection.localDescription; var desc = this.peerconnection.localDescription;
desc = VideoSSRCHack.mungeLocalVideoSSRC(desc);
this.trace('getLocalDescription::preTransform', dumpSDP(desc)); this.trace('getLocalDescription::preTransform', dumpSDP(desc));
// if we're running on FF, transform to Plan B first. // if we're running on FF, transform to Plan B first.
@ -359,14 +363,17 @@ TraceablePeerConnection.prototype.createOffer
this.peerconnection.createOffer( this.peerconnection.createOffer(
function (offer) { function (offer) {
self.trace('createOfferOnSuccess::preTransform', dumpSDP(offer)); self.trace('createOfferOnSuccess::preTransform', dumpSDP(offer));
// if we're running on FF, transform to Plan B first.
// NOTE this is not tested because in meet the focus generates the // NOTE this is not tested because in meet the focus generates the
// offer. // offer.
// if we're running on FF, transform to Plan B first.
if (RTCBrowserType.usesUnifiedPlan()) { if (RTCBrowserType.usesUnifiedPlan()) {
offer = self.interop.toPlanB(offer); offer = self.interop.toPlanB(offer);
self.trace('createOfferOnSuccess::postTransform (Plan B)', dumpSDP(offer)); self.trace('createOfferOnSuccess::postTransform (Plan B)', dumpSDP(offer));
} }
offer = VideoSSRCHack.mungeLocalVideoSSRC(offer);
if (config.enableSimulcast && self.simulcast.isSupported()) { if (config.enableSimulcast && self.simulcast.isSupported()) {
offer = self.simulcast.mungeLocalDescription(offer); offer = self.simulcast.mungeLocalDescription(offer);
self.trace('createOfferOnSuccess::postTransform (simulcast)', dumpSDP(offer)); self.trace('createOfferOnSuccess::postTransform (simulcast)', dumpSDP(offer));
@ -393,6 +400,10 @@ TraceablePeerConnection.prototype.createAnswer
answer = self.interop.toPlanB(answer); answer = self.interop.toPlanB(answer);
self.trace('createAnswerOnSuccess::postTransform (Plan B)', dumpSDP(answer)); self.trace('createAnswerOnSuccess::postTransform (Plan B)', dumpSDP(answer));
} }
// munge local video SSRC
answer = VideoSSRCHack.mungeLocalVideoSSRC(answer);
if (config.enableSimulcast && self.simulcast.isSupported()) { if (config.enableSimulcast && self.simulcast.isSupported()) {
answer = self.simulcast.mungeLocalDescription(answer); answer = self.simulcast.mungeLocalDescription(answer);
self.trace('createAnswerOnSuccess::postTransform (simulcast)', dumpSDP(answer)); self.trace('createAnswerOnSuccess::postTransform (simulcast)', dumpSDP(answer));

View File

@ -0,0 +1,169 @@
/* global $ */
/*
The purpose of this hack is to re-use SSRC of first video stream ever created
for any video streams created later on. In order to do that this hack:
1. Stores the SSRC of the first video stream created by
a) scanning Jingle session-accept/session-invite for existing video SSRC
b) watching for 'source-add' for new video stream if it has not been
created in step a)
2. Exposes method 'mungeLocalVideoSSRC' which replaces any new video SSRC with
the stored one. It is called by 'TracablePeerConnection' before local SDP is
returned to the other parts of the application.
3. Scans 'source-remove'/'source-add' notifications for stored video SSRC and
blocks those notifications. This makes Jicofo and all participants think
that it exists all the time even if the video stream has been removed or
replaced locally. Thanks to that there is no additional signaling activity
on video mute or when switching to the desktop stream.
*/
var SDP = require('./SDP');
/**
* Stored SSRC of local video stream.
*/
var localVideoSSRC;
/**
* Method removes <source> element which describes <tt>localVideoSSRC</tt>
* from given Jingle IQ.
* @param modifyIq 'source-add' or 'source-remove' Jingle IQ.
* @param actionName display name of the action which will be printed in log
* messages.
* @returns {*} modified Jingle IQ, so that it does not contain <source> element
* corresponding to <tt>localVideoSSRC</tt> or <tt>null</tt> if no
* other SSRCs left to be signaled after removing it.
*/
var filterOutSource = function (modifyIq, actionName) {
if (!localVideoSSRC)
return modifyIq;
var modifyIqTree = $(modifyIq.tree());
var videoSSRC = modifyIqTree.find(
'>jingle>content[name="video"]' +
'>description>source[ssrc="' + localVideoSSRC + '"]');
if (!videoSSRC.length) {
return modifyIqTree;
}
console.info(
'Blocking ' + actionName + ' for local video SSRC: ' + localVideoSSRC);
videoSSRC.remove();
// Check if any sources still left to be added/removed
if (modifyIqTree.find('>jingle>content>description>source').length) {
return modifyIqTree;
} else {
return null;
}
};
/**
* Scans given Jingle IQ for video SSRC and stores it.
* @param jingleIq the Jingle IQ to be scanned for video SSRC.
*/
var storeLocalVideoSSRC = function (jingleIq) {
var videoSSRCs =
$(jingleIq.tree())
.find('>jingle>content[name="video"]>description>source');
console.info('Video desc: ', videoSSRCs);
if (!videoSSRCs.length)
return;
var ssrc = videoSSRCs.attr('ssrc');
if (ssrc) {
localVideoSSRC = ssrc;
console.info(
'Stored local video SSRC for future re-use: ' + localVideoSSRC);
} else {
console.error('No "ssrc" attribute present in <source> element');
}
};
var LocalVideoSSRCHack = {
/**
* Method must be called before 'session-initiate' or 'session-invite' is
* sent. Scans the IQ for local video SSRC and stores it if detected.
*
* @param sessionInit our 'session-initiate' or 'session-accept' Jingle IQ
* which will be scanned for local video SSRC.
*/
processSessionInit: function (sessionInit) {
if (localVideoSSRC) {
console.error("Local SSRC stored already: " + localVideoSSRC);
return;
}
storeLocalVideoSSRC(sessionInit);
},
/**
* If we have local video SSRC stored searched given
* <tt>localDescription</tt> for video SSRC and makes sure it is replaced
* with the stored one.
* @param localDescription local description object that will have local
* video SSRC replaced with the stored one
* @returns modified <tt>localDescription</tt> object.
*/
mungeLocalVideoSSRC: function (localDescription) {
// IF we have local video SSRC stored make sure it is replaced
// with old SSRC
if (localVideoSSRC) {
var newSdp = new SDP(localDescription.sdp);
if (newSdp.media[1].indexOf("a=ssrc:") !== -1 &&
!newSdp.containsSSRC(localVideoSSRC)) {
// Get new video SSRC
var map = newSdp.getMediaSsrcMap();
var videoPart = map[1];
var videoSSRCs = videoPart.ssrcs;
var newSSRC = Object.keys(videoSSRCs)[0];
console.info(
"Replacing new video SSRC: " + newSSRC +
" with " + localVideoSSRC);
localDescription.sdp =
newSdp.raw.replace(
new RegExp('a=ssrc:' + newSSRC, 'g'),
'a=ssrc:' + localVideoSSRC);
}
}
return localDescription;
},
/**
* Method must be called before 'source-add' notification is sent. In case
* we have local video SSRC advertised already it will be removed from the
* notification. If no other SSRCs are described by given IQ null will be
* returned which means that there is no point in sending the notification.
* @param sourceAdd 'source-add' Jingle IQ to be processed
* @returns modified 'source-add' IQ which can be sent to the focus or
* <tt>null</tt> if no notification shall be sent. It is no longer
* a Strophe IQ Builder instance, but DOM element tree.
*/
processSourceAdd: function (sourceAdd) {
if (!localVideoSSRC) {
// Store local SSRC if available
storeLocalVideoSSRC(sourceAdd);
return sourceAdd;
} else {
return filterOutSource(sourceAdd, 'source-add');
}
},
/**
* Method must be called before 'source-remove' notification is sent.
* Removes local video SSRC from the notification. If there are no other
* SSRCs described in the given IQ <tt>null</tt> will be returned which
* means that there is no point in sending the notification.
* @param sourceRemove 'source-remove' Jingle IQ to be processed
* @returns modified 'source-remove' IQ which can be sent to the focus or
* <tt>null</tt> if no notification shall be sent. It is no longer
* a Strophe IQ Builder instance, but DOM element tree.
*/
processSourceRemove: function (sourceRemove) {
return filterOutSource(sourceRemove, 'source-remove');
}
};
module.exports = LocalVideoSSRCHack;

View File

@ -174,6 +174,16 @@ module.exports = function(XMPP, eventEmitter) {
Strophe.getResourceFromJid(from), devicesValues); Strophe.getResourceFromJid(from), devicesValues);
} }
var videoType = $(pres).find('>videoType');
if (videoType.length)
{
if (videoType.text().length)
{
eventEmitter.emit(XMPPEvents.VIDEO_TYPE,
Strophe.getResourceFromJid(from), videoType.text());
}
}
var stats = $(pres).find('>stats'); var stats = $(pres).find('>stats');
if (stats.length) { if (stats.length) {
var statsObj = {}; var statsObj = {};
@ -482,6 +492,11 @@ module.exports = function(XMPP, eventEmitter) {
.t(this.presMap['videomuted']).up(); .t(this.presMap['videomuted']).up();
} }
if (this.presMap['videoTypeNs']) {
pres.c('videoType', { xmlns: this.presMap['videoTypeNs'] })
.t(this.presMap['videoType']).up();
}
if (this.presMap['statsns']) { if (this.presMap['statsns']) {
var stats = pres.c('stats', {xmlns: this.presMap['statsns']}); var stats = pres.c('stats', {xmlns: this.presMap['statsns']});
for (var stat in this.presMap["stats"]) for (var stat in this.presMap["stats"])
@ -514,6 +529,14 @@ module.exports = function(XMPP, eventEmitter) {
addDevicesToPresence: function (devices) { addDevicesToPresence: function (devices) {
this.presMap['devices'] = devices; this.presMap['devices'] = devices;
}, },
/**
* Adds the info about the type of our video stream.
* @param videoType 'camera' or 'screen'
*/
addVideoTypeToPresence: function (videoType) {
this.presMap['videoTypeNs'] = 'http://jitsi.org/jitmeet/video';
this.presMap['videoType'] = videoType;
},
addPreziToPresence: function (url, currentSlide) { addPreziToPresence: function (url, currentSlide) {
this.presMap['prezins'] = 'http://jitsi.org/jitmeet/prezi'; this.presMap['prezins'] = 'http://jitsi.org/jitmeet/prezi';
this.presMap['preziurl'] = url; this.presMap['preziurl'] = url;

View File

@ -171,9 +171,28 @@ function initStrophePlugins()
require("./strophe.logger")(); require("./strophe.logger")();
} }
/**
* If given <tt>localStream</tt> is video one this method will advertise it's
* video type in MUC presence.
* @param localStream new or modified <tt>LocalStream</tt>.
*/
function broadcastLocalVideoType(localStream) {
if (localStream.videoType)
XMPP.addToPresence('videoType', localStream.videoType);
}
function registerListeners() { function registerListeners() {
APP.RTC.addStreamListener(maybeDoJoin, APP.RTC.addStreamListener(
StreamEventTypes.EVENT_TYPE_LOCAL_CREATED); function (localStream) {
maybeDoJoin();
broadcastLocalVideoType(localStream);
},
StreamEventTypes.EVENT_TYPE_LOCAL_CREATED
);
APP.RTC.addStreamListener(
broadcastLocalVideoType,
StreamEventTypes.EVENT_TYPE_LOCAL_CHANGED
);
APP.RTC.addListener(RTCEvents.AVAILABLE_DEVICES_CHANGED, function (devices) { APP.RTC.addListener(RTCEvents.AVAILABLE_DEVICES_CHANGED, function (devices) {
XMPP.addToPresence("devices", devices); XMPP.addToPresence("devices", devices);
}); });
@ -439,6 +458,9 @@ var XMPP = {
case "devices": case "devices":
connection.emuc.addDevicesToPresence(value); connection.emuc.addDevicesToPresence(value);
break; break;
case "videoType":
connection.emuc.addVideoTypeToPresence(value);
break;
case "startMuted": case "startMuted":
if(!Moderator.isModerator()) if(!Moderator.isModerator())
return; return;

View File

@ -7,9 +7,7 @@ var StreamEventTypes = {
EVENT_TYPE_REMOTE_CREATED: "stream.remote_created", EVENT_TYPE_REMOTE_CREATED: "stream.remote_created",
EVENT_TYPE_REMOTE_ENDED: "stream.remote_ended", EVENT_TYPE_REMOTE_ENDED: "stream.remote_ended"
EVENT_TYPE_REMOTE_CHANGED: "stream.changed"
}; };
module.exports = StreamEventTypes; module.exports = StreamEventTypes;

View File

@ -32,6 +32,7 @@ var XMPPEvents = {
CHAT_ERROR_RECEIVED: "xmpp.chat_error_received", CHAT_ERROR_RECEIVED: "xmpp.chat_error_received",
ETHERPAD: "xmpp.etherpad", ETHERPAD: "xmpp.etherpad",
DEVICE_AVAILABLE: "xmpp.device_available", DEVICE_AVAILABLE: "xmpp.device_available",
VIDEO_TYPE: "xmpp.video_type",
PEERCONNECTION_READY: "xmpp.peerconnection_ready", PEERCONNECTION_READY: "xmpp.peerconnection_ready",
CONFERENCE_SETUP_FAILED: "xmpp.conference_setup_failed", CONFERENCE_SETUP_FAILED: "xmpp.conference_setup_failed",
AUDIO_MUTED: "xmpp.audio_muted", AUDIO_MUTED: "xmpp.audio_muted",