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:
parent
9a31fa3d63
commit
74e7507a73
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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;
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
|
@ -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",
|
||||||
|
|
Loading…
Reference in New Issue