Merge branch 'master' into firefox

Conflicts:
	app.js
	libs/strophe/strophe.jingle.adapter.js
	simulcast.js
	videolayout.js
This commit is contained in:
hristoterezov 2014-11-25 17:01:46 +02:00
commit 5213583af2
20 changed files with 709 additions and 271 deletions

View File

@ -31,7 +31,7 @@ var APIConnector = (function () {
* Maps the supported events and their status
* (true it the event is enabled and false if it is disabled)
* @type {{
* incommingMessage: boolean,
* incomingMessage: boolean,
* outgoingMessage: boolean,
* displayNameChange: boolean,
* participantJoined: boolean,
@ -40,7 +40,7 @@ var APIConnector = (function () {
*/
var events =
{
incommingMessage: false,
incomingMessage: false,
outgoingMessage:false,
displayNameChange: false,
participantJoined: false,

43
app.js
View File

@ -254,6 +254,24 @@ function doJoin() {
}
function waitForRemoteVideo(selector, ssrc, stream, jid) {
// XXX(gp) so, every call to this function is *always* preceded by a call
// to the RTC.attachMediaStream() function but that call is *not* followed
// by an update to the videoSrcToSsrc map!
//
// The above way of doing things results in video SRCs that don't correspond
// to any SSRC for a short period of time (to be more precise, for as long
// the waitForRemoteVideo takes to complete). This causes problems (see
// bellow).
//
// I'm wondering why we need to do that; i.e. why call RTC.attachMediaStream()
// a second time in here and only then update the videoSrcToSsrc map? Why
// not simply update the videoSrcToSsrc map when the RTC.attachMediaStream()
// is called the first time? I actually do that in the lastN changed event
// handler because the "orphan" video SRC is causing troubles there. The
// purpose of this method would then be to fire the "videoactive.jingle".
//
// Food for though I guess :-)
if (selector.removed || !selector.parent().is(":visible")) {
console.warn("Media removed before had started", selector);
return;
@ -771,6 +789,7 @@ $(document).bind('left.muc', function (event, jid) {
'disconnected');
// Need to call this with a slight delay, otherwise the element couldn't be
// found for some reason.
// XXX(gp) it works fine without the timeout for me (with Chrome 38).
window.setTimeout(function () {
var container = document.getElementById(
'participant_' + Strophe.getResourceFromJid(jid));
@ -1598,6 +1617,30 @@ $(document).bind("selectedendpointchanged", function(event, userJid) {
onSelectedEndpointChanged(userJid);
});
function onPinnedEndpointChanged(userJid)
{
console.log('pinned endpoint changed: ', userJid);
if (_dataChannels && _dataChannels.length != 0)
{
_dataChannels.some(function (dataChannel) {
if (dataChannel.readyState == 'open')
{
dataChannel.send(JSON.stringify({
'colibriClass': 'PinnedEndpointChangedEvent',
'pinnedEndpoint': (!userJid || userJid == null)
? null : Strophe.getResourceFromJid(userJid)
}));
return true;
}
});
}
}
$(document).bind("pinnedendpointchanged", function(event, userJid) {
onPinnedEndpointChanged(userJid);
});
function callSipButtonClicked()
{
var defaultNumber

View File

@ -25,6 +25,6 @@ var config = {
useRtcpMux: true,
useBundle: true,
enableRecording: false,
enableWelcomePage: false,
enableWelcomePage: true,
enableSimulcast: false
};

View File

@ -41,17 +41,13 @@ var ContactList = (function (my) {
var contactlist = $('#contactlist>ul');
var newContact = document.createElement('li');
// XXX(gp) contact click event handling is now in videolayout.js. Is the
// following statement (newContact.id = resourceJid) still relevant?
newContact.id = resourceJid;
newContact.className = "clickable";
newContact.onclick = function(event) {
if(event.currentTarget.className === "clickable") {
var jid = event.currentTarget.id;
var videoContainer = $("#participant_" + jid);
if (videoContainer.length > 0) {
videoContainer.click();
} else if (jid == Strophe.getResourceFromJid(connection.emuc.myroomjid)) {
$("#localVideoContainer").click();
}
$(ContactList).trigger('contactclicked', [peerJid]);
}
};

View File

@ -221,3 +221,19 @@
#usermsg::-webkit-scrollbar-track-piece {
background: #3a3a3a;
}
a:link {
color: rgb(184, 184, 184);
}
a:visited {
color: white;
}
a:hover {
color: rgb(213, 213, 213);
}
a:active {
color: black;
}

4
debian/jitsi-meet-prosody.config vendored Normal file
View File

@ -0,0 +1,4 @@
#!/bin/sh -e
# Source debconf library.
. /usr/share/debconf/confmodule

View File

@ -23,6 +23,15 @@ case "$1" in
. /etc/jitsi/videobridge/config
# loading debconf
. /usr/share/debconf/confmodule
# stores the hostname so we will reuse it later, like in purge
db_set jitsi-meet-prosody/jvb-hostname $JVB_HOSTNAME
# and we're done with debconf
db_stop
PROSODY_CONFIG_PRESENT="true"
PROSODY_HOST_CONFIG="/etc/prosody/conf.avail/$JVB_HOSTNAME.cfg.lua"
# if there is no prosody config extract our template

View File

@ -29,7 +29,16 @@ case "$1" in
fi
;;
purge|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
purge)
db_get jitsi-meet-prosody/jvb-hostname
JVB_HOSTNAME=$RET
if [ -n "$RET" ]; then
rm -f /etc/prosody/conf.avail/$JVB_HOSTNAME.cfg.lua
rm -f /etc/prosody/conf.d/$JVB_HOSTNAME.cfg.lua
fi
;;
upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
;;
*)

5
debian/jitsi-meet-prosody.templates vendored Normal file
View File

@ -0,0 +1,5 @@
Template: jitsi-meet-prosody/jvb-hostname
Type: string
Default: ${default-key}
_Description: The hostname of the current installation:
The value for the hostname that is set in Jitsi Videobridge installation.

View File

@ -41,31 +41,29 @@ case "$1" in
sed -i "s/#\ server_names_hash_bucket_size\ 64/\ server_names_hash_bucket_size\ 64/" /etc/nginx/nginx.conf
fi
if [ ! -f /etc/ssl/$JVB_HOSTNAME.key ] || [ ! -f /etc/ssl/$JVB_HOSTNAME.crt ]; then
# SSL for nginx
db_get jitsi-meet/cert-choice
CERT_CHOICE="$RET"
if [ "$CERT_CHOICE" = 'A certificate is available and the files are uploaded on the server' ]; then
db_set jitsi-meet/cert-path-key "/etc/ssl/$JVB_HOSTNAME.key"
db_input critical jitsi-meet/cert-path-key || true
db_go
db_get jitsi-meet/cert-path-key
CERT_KEY="$RET"
db_set jitsi-meet/cert-path-crt "/etc/ssl/$JVB_HOSTNAME.crt"
db_input critical jitsi-meet/cert-path-crt || true
db_go
db_get jitsi-meet/cert-path-crt
CERT_CRT="$RET"
# replace self-signed certificate paths with user provided ones
CERT_KEY_ESC=$(echo $CERT_KEY | sed 's/\./\\\./g')
CERT_KEY_ESC=$(echo $CERT_KEY_ESC | sed 's/\//\\\//g')
sed -i "s/ssl_certificate_key\ \/var\/lib\/prosody\/.*key/ssl_certificate_key\ $CERT_KEY_ESC/g" \
/etc/nginx/sites-available/$JVB_HOSTNAME.conf
CERT_CRT_ESC=$(echo $CERT_CRT | sed 's/\./\\\./g')
CERT_CRT_ESC=$(echo $CERT_CRT_ESC | sed 's/\//\\\//g')
sed -i "s/ssl_certificate\ \/var\/lib\/prosody\/.*crt/ssl_certificate\ $CERT_CRT_ESC/g" \
/etc/nginx/sites-available/$JVB_HOSTNAME.conf
fi
# SSL for nginx
db_get jitsi-meet/cert-choice
CERT_CHOICE="$RET"
if [ "$CERT_CHOICE" = 'A certificate is available and the files are uploaded on the server' ]; then
db_set jitsi-meet/cert-path-key "/etc/ssl/$JVB_HOSTNAME.key"
db_input critical jitsi-meet/cert-path-key || true
db_go
db_get jitsi-meet/cert-path-key
CERT_KEY="$RET"
db_set jitsi-meet/cert-path-crt "/etc/ssl/$JVB_HOSTNAME.crt"
db_input critical jitsi-meet/cert-path-crt || true
db_go
db_get jitsi-meet/cert-path-crt
CERT_CRT="$RET"
# replace self-signed certificate paths with user provided ones
CERT_KEY_ESC=$(echo $CERT_KEY | sed 's/\./\\\./g')
CERT_KEY_ESC=$(echo $CERT_KEY_ESC | sed 's/\//\\\//g')
sed -i "s/ssl_certificate_key\ \/var\/lib\/prosody\/.*key/ssl_certificate_key\ $CERT_KEY_ESC/g" \
/etc/nginx/sites-available/$JVB_HOSTNAME.conf
CERT_CRT_ESC=$(echo $CERT_CRT | sed 's/\./\\\./g')
CERT_CRT_ESC=$(echo $CERT_CRT_ESC | sed 's/\//\\\//g')
sed -i "s/ssl_certificate\ \/var\/lib\/prosody\/.*crt/ssl_certificate\ $CERT_CRT_ESC/g" \
/etc/nginx/sites-available/$JVB_HOSTNAME.conf
fi
# jitsi meet

View File

@ -91,7 +91,7 @@ with data related to the event.
Currently we support the following events:
* **incommingMessage** - event notifications about incomming
* **incomingMessage** - event notifications about incoming
messages. The listener will receive object with the following structure:
```
{
@ -135,7 +135,7 @@ This method requires one argument of type Object. The object argument must
have keys with the names of the events and values the listeners of the events.
```
function incommingMessageListener(object)
function incomingMessageListener(object)
{
...
}
@ -146,19 +146,19 @@ function outgoingMessageListener(object)
}
api.addEventListeners({
incommingMessage: incommingMessageListener,
incomingMessage: incomingMessageListener,
outgoingMessage: outgoingMessageListener})
```
If you want to remove a listener you can use ```removeEventListener``` method with argument the name of the event.
```
api.removeEventListener("incommingMessage");
api.removeEventListener("incomingMessage");
```
If you want to remove more than one event you can use ```removeEventListeners``` method with argument
array with the names of the events.
```
api.removeEventListeners(["incommingMessage", "outgoingMessageListener"]);
api.removeEventListeners(["incomingMessage", "outgoingMessageListener"]);
```
You can remove the embedded Jitsi Meet Conference with the following code:

View File

@ -48,7 +48,7 @@ var JitsiMeetExternalAPI = (function()
this.iframeHolder.style.width = width + "px";
this.iframeHolder.style.height = height + "px";
this.frameName = "jitsiConferenceFrame" + JitsiMeetExternalAPI.id;
this.url = "http://" + domain + "/";
this.url = "//" + domain + "/";
if(room_name)
this.url += room_name;
this.url += "#external";
@ -139,7 +139,7 @@ var JitsiMeetExternalAPI = (function()
* event and value - the listener.
* Currently we support the following
* events:
* incommingMessage - receives event notifications about incomming
* incomingMessage - receives event notifications about incoming
* messages. The listener will receive object with the following structure:
* {{
* "from": from,//JID of the user that sent the message
@ -185,7 +185,7 @@ var JitsiMeetExternalAPI = (function()
/**
* Adds event listeners to Meet Jitsi. Currently we support the following
* events:
* incommingMessage - receives event notifications about incomming
* incomingMessage - receives event notifications about incoming
* messages. The listener will receive object with the following structure:
* {{
* "from": from,//JID of the user that sent the message

View File

@ -11,8 +11,8 @@
<meta itemprop="image" content="/images/jitsilogo.png"/>
<script src="libs/jquery-2.1.1.min.js"></script>
<script src="config.js?v=5"></script><!-- adapt to your needs, i.e. set hosts and bosh path -->
<script src="simulcast.js?v=6"></script><!-- simulcast handling -->
<script src="libs/strophe/strophe.jingle.adapter.js?v=2"></script><!-- strophe.jingle bundles -->
<script src="simulcast.js?v=7"></script><!-- simulcast handling -->
<script src="libs/strophe/strophe.jingle.adapter.js?v=3"></script><!-- strophe.jingle bundles -->
<script src="libs/strophe/strophe.min.js?v=1"></script>
<script src="libs/strophe/strophe.disco.min.js?v=1"></script>
<script src="libs/strophe/strophe.caps.jsonly.min.js?v=1"></script>
@ -22,7 +22,7 @@
<script src="libs/strophe/strophe.jingle.sessionbase.js?v=1"></script>
<script src="libs/strophe/strophe.jingle.session.js?v=2"></script>
<script src="libs/strophe/strophe.util.js"></script>
<script src="libs/colibri/colibri.focus.js?v=11"></script><!-- colibri focus implementation -->
<script src="libs/colibri/colibri.focus.js?v=12"></script><!-- colibri focus implementation -->
<script src="libs/colibri/colibri.session.js?v=1"></script>
<script src="libs/jquery-ui.js"></script>
<script src="libs/rayo.js?v=1"></script>
@ -37,7 +37,7 @@
<script src="app.js?v=20"></script><!-- application logic -->
<script src="commands.js?v=1"></script><!-- application logic -->
<script src="chat.js?v=14"></script><!-- chat logic -->
<script src="contact_list.js?v=5"></script><!-- contact list logic -->
<script src="contact_list.js?v=6"></script><!-- contact list logic -->
<script src="util.js?v=6"></script><!-- utility functions -->
<script src="etherpad.js?v=9"></script><!-- etherpad plugin -->
<script src="prezi.js?v=6"></script><!-- prezi plugin -->
@ -47,7 +47,7 @@
<script src="analytics.js?v=1"></script><!-- google analytics plugin -->
<script src="rtp_sts.js?v=5"></script><!-- RTP stats processing -->
<script src="local_sts.js?v=2"></script><!-- Local stats processing -->
<script src="videolayout.js?v=26"></script><!-- video ui -->
<script src="videolayout.js?v=28"></script><!-- video ui -->
<script src="connectionquality.js?v=1"></script>
<script src="toolbar.js?v=6"></script><!-- toolbar ui -->
<script src="toolbar_toggler.js?v=2"></script>

View File

@ -540,7 +540,7 @@ ColibriFocus.prototype.createdConference = function (result) {
}
bridgeSDP.raw = bridgeSDP.session + bridgeSDP.media.join('');
var bridgeDesc = new RTCSessionDescription({type: 'offer', sdp: bridgeSDP.raw});
var bridgeDesc = simulcast.transformRemoteDescription(bridgeDesc);
bridgeDesc = simulcast.transformRemoteDescription(bridgeDesc);
this.peerconnection.setRemoteDescription(bridgeDesc,
function () {
@ -552,82 +552,8 @@ ColibriFocus.prototype.createdConference = function (result) {
console.log('setLocalDescription succeeded.');
// make sure our presence is updated
$(document).trigger('setLocalDescription.jingle', [self.sid]);
var elem = $iq({to: self.bridgejid, type: 'get'});
elem.c('conference', {xmlns: 'http://jitsi.org/protocol/colibri', id: self.confid});
var localSDP = new SDP(self.peerconnection.localDescription.sdp);
localSDP.media.forEach(function (media, channel) {
var name = SDPUtil.parse_mid(SDPUtil.find_line(media, 'a=mid:'));
elem.c('content', {name: name});
var mline = SDPUtil.parse_mline(media.split('\r\n')[0]);
if (name !== 'data')
{
elem.c('channel', {
initiator: 'true',
expire: self.channelExpire,
id: self.mychannel[channel].attr('id'),
endpoint: self.myMucResource
});
// signal (through COLIBRI) to the bridge
// the SSRC groups of the participant
// that plays the role of the focus
var ssrc_group_lines = SDPUtil.find_lines(media, 'a=ssrc-group:');
var idx = 0;
ssrc_group_lines.forEach(function(line) {
idx = line.indexOf(' ');
var semantics = line.substr(0, idx).substr(13);
var ssrcs = line.substr(14 + semantics.length).split(' ');
if (ssrcs.length != 0) {
elem.c('ssrc-group', { semantics: semantics, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
ssrcs.forEach(function(ssrc) {
elem.c('source', { ssrc: ssrc })
.up();
});
elem.up();
}
});
// FIXME: should reuse code from .toJingle
for (var j = 0; j < mline.fmt.length; j++)
{
var rtpmap = SDPUtil.find_line(media, 'a=rtpmap:' + mline.fmt[j]);
if (rtpmap)
{
elem.c('payload-type', SDPUtil.parse_rtpmap(rtpmap));
elem.up();
}
}
}
else
{
var sctpmap = SDPUtil.find_line(media, 'a=sctpmap:' + mline.fmt[0]);
var sctpPort = SDPUtil.parse_sctpmap(sctpmap)[0];
elem.c("sctpconnection",
{
initiator: 'true',
expire: self.channelExpire,
id: self.mychannel[channel].attr('id'),
endpoint: self.myMucResource,
port: sctpPort
}
);
}
localSDP.TransportToJingle(channel, elem);
elem.up(); // end of channel
elem.up(); // end of content
});
self.connection.sendIQ(elem,
function (result) {
// ...
},
function (error) {
console.error(
"ERROR sending colibri message",
error, elem);
}
);
self.updateLocalChannel(localSDP);
// now initiate sessions
for (var i = 0; i < numparticipants; i++) {
@ -660,6 +586,96 @@ ColibriFocus.prototype.createdConference = function (result) {
};
ColibriFocus.prototype.updateLocalChannel = function(localSDP, parts) {
var self = this;
var elem = $iq({to: self.bridgejid, type: 'get'});
elem.c('conference', {xmlns: 'http://jitsi.org/protocol/colibri', id: self.confid});
localSDP.media.forEach(function (media, channel) {
var name = SDPUtil.parse_mid(SDPUtil.find_line(media, 'a=mid:'));
elem.c('content', {name: name});
var mline = SDPUtil.parse_mline(media.split('\r\n')[0]);
if (name !== 'data') {
elem.c('channel', {
initiator: 'true',
expire: self.channelExpire,
id: self.mychannel[channel].attr('id'),
endpoint: self.myMucResource
});
if (!parts || parts.indexOf('sources') !== -1) {
// signal (through COLIBRI) to the bridge
// the SSRC groups of the participant
// that plays the role of the focus
var ssrc_group_lines = SDPUtil.find_lines(media, 'a=ssrc-group:');
var idx = 0;
var hasSIM = false;
ssrc_group_lines.forEach(function (line) {
idx = line.indexOf(' ');
var semantics = line.substr(0, idx).substr(13);
var ssrcs = line.substr(14 + semantics.length).split(' ');
if (ssrcs.length != 0) {
elem.c('ssrc-group', { semantics: semantics, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
ssrcs.forEach(function (ssrc) {
elem.c('source', { ssrc: ssrc })
.up();
});
elem.up();
}
});
if (!hasSIM && name == 'video') {
// disable simulcast with an empty ssrc-group element.
elem.c('ssrc-group', { semantics: 'SIM', xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
elem.up();
}
}
if (!parts || parts.indexOf('payload-type') !== -1) {
// FIXME: should reuse code from .toJingle
for (var j = 0; j < mline.fmt.length; j++) {
var rtpmap = SDPUtil.find_line(media, 'a=rtpmap:' + mline.fmt[j]);
if (rtpmap) {
elem.c('payload-type', SDPUtil.parse_rtpmap(rtpmap));
elem.up();
}
}
}
}
else
{
var sctpmap = SDPUtil.find_line(media, 'a=sctpmap:' + mline.fmt[0]);
var sctpPort = SDPUtil.parse_sctpmap(sctpmap)[0];
elem.c("sctpconnection",
{
initiator: 'true',
expire: self.channelExpire,
id: self.mychannel[channel].attr('id'),
endpoint: self.myMucResource,
port: sctpPort
}
);
}
if (!parts || parts.indexOf('transport') !== -1) {
localSDP.TransportToJingle(channel, elem);
}
elem.up(); // end of channel
elem.up(); // end of content
});
self.connection.sendIQ(elem,
function (result) {
// ...
},
function (error) {
console.error(
"ERROR sending colibri message",
error, elem);
}
);
};
// send a session-initiate to a new participant
ColibriFocus.prototype.initiate = function (peer, isInitiator) {
var participant = this.peers.indexOf(peer);
@ -895,61 +911,73 @@ ColibriFocus.prototype.addNewParticipant = function (peer) {
};
// update the channel description (payload-types + dtls fp) for a participant
ColibriFocus.prototype.updateChannel = function (remoteSDP, participant) {
ColibriFocus.prototype.updateRemoteChannel = function (remoteSDP, participant, parts) {
console.log('change allocation for', this.confid);
var self = this;
var change = $iq({to: this.bridgejid, type: 'set'});
change.c('conference', {xmlns: 'http://jitsi.org/protocol/colibri', id: this.confid});
for (channel = 0; channel < this.channels[participant].length; channel++)
{
for (channel = 0; channel < this.channels[participant].length; channel++) {
if (!remoteSDP.media[channel])
continue;
var name = SDPUtil.parse_mid(SDPUtil.find_line(remoteSDP.media[channel], 'a=mid:'));
change.c('content', {name: name});
if (name !== 'data')
{
if (name !== 'data') {
change.c('channel', {
id: $(this.channels[participant][channel]).attr('id'),
endpoint: $(this.channels[participant][channel]).attr('endpoint'),
expire: self.channelExpire
});
// signal (throught COLIBRI) to the bridge the SSRC groups of this
// participant
var ssrc_group_lines = SDPUtil.find_lines(remoteSDP.media[channel], 'a=ssrc-group:');
var idx = 0;
ssrc_group_lines.forEach(function(line) {
idx = line.indexOf(' ');
var semantics = line.substr(0, idx).substr(13);
var ssrcs = line.substr(14 + semantics.length).split(' ');
if (ssrcs.length != 0) {
change.c('ssrc-group', { semantics: semantics, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
ssrcs.forEach(function(ssrc) {
change.c('source', { ssrc: ssrc })
.up();
});
if (!parts || parts.indexOf('sources') !== -1) {
// signal (throught COLIBRI) to the bridge the SSRC groups of this
// participant
var ssrc_group_lines = SDPUtil.find_lines(remoteSDP.media[channel], 'a=ssrc-group:');
var idx = 0;
var hasSIM = false;
ssrc_group_lines.forEach(function (line) {
idx = line.indexOf(' ');
var semantics = line.substr(0, idx).substr(13);
if (semantics == 'SIM') {
hasSIM = true;
}
var ssrcs = line.substr(14 + semantics.length).split(' ');
if (ssrcs.length != 0) {
change.c('ssrc-group', { semantics: semantics, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
ssrcs.forEach(function (ssrc) {
change.c('source', { ssrc: ssrc })
.up();
});
change.up();
}
});
if (!hasSIM && name == 'video') {
// disable simulcast with an empty ssrc-group element.
change.c('ssrc-group', { semantics: 'SIM', xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
change.up();
}
});
}
var rtpmap = SDPUtil.find_lines(remoteSDP.media[channel], 'a=rtpmap:');
rtpmap.forEach(function (val) {
// TODO: too much copy-paste
var rtpmap = SDPUtil.parse_rtpmap(val);
change.c('payload-type', rtpmap);
//
// put any 'a=fmtp:' + mline.fmt[j] lines into <param name=foo value=bar/>
/*
if (SDPUtil.find_line(remoteSDP.media[channel], 'a=fmtp:' + rtpmap.id)) {
tmp = SDPUtil.parse_fmtp(SDPUtil.find_line(remoteSDP.media[channel], 'a=fmtp:' + rtpmap.id));
for (var k = 0; k < tmp.length; k++) {
change.c('parameter', tmp[k]).up();
}
}
*/
change.up();
});
if (!parts || parts.indexOf('payload-type') !== -1) {
var rtpmap = SDPUtil.find_lines(remoteSDP.media[channel], 'a=rtpmap:');
rtpmap.forEach(function (val) {
// TODO: too much copy-paste
var rtpmap = SDPUtil.parse_rtpmap(val);
change.c('payload-type', rtpmap);
//
// put any 'a=fmtp:' + mline.fmt[j] lines into <param name=foo value=bar/>
/*
if (SDPUtil.find_line(remoteSDP.media[channel], 'a=fmtp:' + rtpmap.id)) {
tmp = SDPUtil.parse_fmtp(SDPUtil.find_line(remoteSDP.media[channel], 'a=fmtp:' + rtpmap.id));
for (var k = 0; k < tmp.length; k++) {
change.c('parameter', tmp[k]).up();
}
}
*/
change.up();
});
}
}
else
{
@ -961,8 +989,11 @@ ColibriFocus.prototype.updateChannel = function (remoteSDP, participant) {
port: SDPUtil.parse_sctpmap(sctpmap)[0]
});
}
// now add transport
remoteSDP.TransportToJingle(channel, change);
if (!parts || parts.indexOf('transport') !== -1) {
// now add transport
remoteSDP.TransportToJingle(channel, change);
}
change.up(); // end of channel/sctpconnection
change.up(); // end of content
@ -977,6 +1008,57 @@ ColibriFocus.prototype.updateChannel = function (remoteSDP, participant) {
);
};
/**
* Switches video streams.
* @param new_stream new stream that will be used as video of this session.
* @param oldStream old video stream of this session.
* @param success_callback callback executed after successful stream switch.
*/
ColibriFocus.prototype.switchStreams = function (new_stream, oldStream, success_callback) {
var self = this;
// Stop the stream to trigger onended event for old stream
oldStream.stop();
// Remember SDP to figure out added/removed SSRCs
var oldSdp = null;
if(self.peerconnection) {
if(self.peerconnection.localDescription) {
oldSdp = new SDP(self.peerconnection.localDescription.sdp);
}
self.peerconnection.removeStream(oldStream);
self.peerconnection.addStream(new_stream);
}
self.connection.jingle.localVideo = new_stream;
self.connection.jingle.localStreams = [];
self.connection.jingle.localStreams.push(self.connection.jingle.localAudio);
self.connection.jingle.localStreams.push(self.connection.jingle.localVideo);
// Conference is not active
if(!oldSdp || !self.peerconnection) {
success_callback();
return;
}
self.peerconnection.switchstreams = true;
self.modifySources(function() {
console.log('modify sources done');
var newSdp = new SDP(self.peerconnection.localDescription.sdp);
// change allocation on bridge
self.updateLocalChannel(newSdp, ['sources']);
console.log("SDPs", oldSdp, newSdp);
self.notifyMySSRCUpdate(oldSdp, newSdp);
success_callback();
});
};
// tell everyone about a new participants a=ssrc lines (isadd is true)
// or a leaving participants a=ssrc lines
ColibriFocus.prototype.sendSSRCUpdate = function (sdpMediaSsrcs, fromJid, isadd) {
@ -1011,32 +1093,56 @@ ColibriFocus.prototype.addSource = function (elem, fromJid) {
// FIXME: dirty waiting
if (!this.peerconnection.localDescription)
{
console.warn("addSource - localDescription not ready yet")
console.warn("addSource - localDescription not ready yet");
setTimeout(function() { self.addSource(elem, fromJid); }, 200);
return;
}
this.peerconnection.addSource(elem);
// NOTE(gp) this could be a useful thing to have in every Array object.
var diffArray = function(a) {
return this.filter(function(i) {return a.indexOf(i) < 0;});
};
var peerSsrc = this.remotessrc[fromJid];
//console.log("On ADD", self.addssrc, peerSsrc);
// console.log("On ADD", this.peerconnection.addssrc, peerSsrc);
this.peerconnection.addssrc.forEach(function(val, idx){
if(!peerSsrc[idx]){
// add ssrc
peerSsrc[idx] = val;
} else {
if(peerSsrc[idx].indexOf(val) == -1){
peerSsrc[idx] = peerSsrc[idx]+val;
}
} else if (val) {
// NOTE(gp) we can't expect the lines in the removessrc SDP fragment
// to be in the same order as in the lines in the peerSsrc SDP
// fragment. So, here we remove the val lines and re-add them.
var lines = peerSsrc[idx].split('\r\n');
var diffLines = val.split('\r\n');
// Remove ssrc
peerSsrc[idx] = diffArray.apply(lines, [diffLines]).join('\r\n');
// Add ssrc
peerSsrc[idx] = peerSsrc[idx]+val;
}
});
var oldRemoteSdp = new SDP(this.peerconnection.remoteDescription.sdp);
this.modifySources(function(){
this.modifySources(function() {
// Notify other participants about added ssrc
var remoteSDP = new SDP(self.peerconnection.remoteDescription.sdp);
var newSSRCs = oldRemoteSdp.getNewMedia(remoteSDP);
self.sendSSRCUpdate(newSSRCs, fromJid, true);
// change allocation on bridge
if (peerSsrc[1] /* video */) {
// If the remote peer has changed its video sources, then we need to
// update the bridge with this information, in order for the
// simulcast manager of the remote peer to update its layers, and
// any associated receivers to adjust to the change.
var videoSDP = new SDP(['v=0', 'm=audio', 'a=mid:audio', peerSsrc[0]].join('\r\n') + ['m=video', 'a=mid:video', peerSsrc[1]].join('\r\n'));
var participant = self.peers.indexOf(fromJid);
self.updateRemoteChannel(videoSDP, participant, ['sources']);
}
});
};
@ -1059,12 +1165,22 @@ ColibriFocus.prototype.removeSource = function (elem, fromJid) {
this.peerconnection.removeSource(elem);
// NOTE(gp) this could be a useful thing to have in every Array object.
var diffArray = function(a) {
return this.filter(function(i) {return a.indexOf(i) < 0;});
};
var peerSsrc = this.remotessrc[fromJid];
//console.log("On REMOVE", self.removessrc, peerSsrc);
// console.log("On REMOVE", this.peerconnection.removessrc, peerSsrc);
this.peerconnection.removessrc.forEach(function(val, idx){
if(peerSsrc[idx]){
if(peerSsrc[idx] && val){
// NOTE(gp) we can't expect the lines in the removessrc SDP fragment
// to be in the same order as in the lines in the peerSsrc SDP
// fragment.
var lines = peerSsrc[idx].split('\r\n');
var diffLines = val.split('\r\n');
// Remove ssrc
peerSsrc[idx] = peerSsrc[idx].replace(val, '');
peerSsrc[idx] = diffArray.apply(lines, [diffLines]).join('\r\n');
}
});
@ -1074,6 +1190,16 @@ ColibriFocus.prototype.removeSource = function (elem, fromJid) {
var remoteSDP = new SDP(self.peerconnection.remoteDescription.sdp);
var removedSSRCs = remoteSDP.getNewMedia(oldSDP);
self.sendSSRCUpdate(removedSSRCs, fromJid, false);
// change allocation on bridge
if (peerSsrc[1] /* video */) {
// If the remote peer has changed its video sources, then we need to
// update the bridge with this information, in order for the
// simulcast manager of the remote peer to update its layers, and
// any associated receivers to adjust to the change.
var videoSDP = new SDP(['v=0', 'm=audio', 'a=mid:audio', peerSsrc[0]].join('\r\n') + ['m=video', 'a=mid:video', peerSsrc[1]].join('\r\n'));
var participant = self.peers.indexOf(fromJid);
self.updateRemoteChannel(videoSDP, participant, ['sources']);
}
});
};
@ -1085,7 +1211,7 @@ ColibriFocus.prototype.setRemoteDescription = function (session, elem, desctype)
remoteSDP.fromJingle(elem);
// ACT 1: change allocation on bridge
this.updateChannel(remoteSDP, participant);
this.updateRemoteChannel(remoteSDP, participant);
// ACT 2: tell anyone else about the new SSRCs
this.sendSSRCUpdate(remoteSDP.getMediaSsrcMap(), session.peerjid, true);

View File

@ -140,6 +140,7 @@ if (TraceablePeerConnection.prototype.__defineGetter__ !== undefined) {
TraceablePeerConnection.prototype.addStream = function (stream) {
this.trace('addStream', stream.id);
simulcast.resetSender();
try
{
this.peerconnection.addStream(stream);
@ -149,11 +150,11 @@ TraceablePeerConnection.prototype.addStream = function (stream) {
console.error(e);
return;
}
};
TraceablePeerConnection.prototype.removeStream = function (stream, stopStreams) {
this.trace('removeStream', stream.id);
simulcast.resetSender();
if(stopStreams) {
stream.getAudioTracks().forEach(function (track) {
track.stop();
@ -221,7 +222,7 @@ TraceablePeerConnection.prototype.enqueueAddSsrc = function(channel, ssrcLines)
this.addssrc[channel] = '';
}
this.addssrc[channel] += ssrcLines;
}
};
TraceablePeerConnection.prototype.addSource = function (elem) {
console.log('addssrc', new Date().getTime());
@ -277,7 +278,7 @@ TraceablePeerConnection.prototype.enqueueRemoveSsrc = function(channel, ssrcLine
this.removessrc[channel] = '';
}
this.removessrc[channel] += ssrcLines;
}
};
TraceablePeerConnection.prototype.removeSource = function (elem) {
console.log('removessrc', new Date().getTime());
@ -337,6 +338,7 @@ TraceablePeerConnection.prototype.modifySources = function(successCallback) {
// FIXME: this is a big hack
// https://code.google.com/p/webrtc/issues/detail?id=2688
// ^ has been fixed.
if (!(this.signalingState == 'stable' && this.iceConnectionState == 'connected')) {
console.warn('modifySources not yet', this.signalingState, this.iceConnectionState);
this.wait = true;

View File

@ -42,7 +42,7 @@ SDP.prototype.getMediaSsrcMap = function() {
});
}
return media_ssrcs;
}
};
/**
* Returns <tt>true</tt> if this SDP contains given SSRC.
* @param ssrc the ssrc to check.
@ -59,7 +59,8 @@ SDP.prototype.containsSSRC = function(ssrc) {
}
});
return contains;
}
};
/**
* Returns map of MediaChannel that contains only media not contained in <tt>otherSdp</tt>. Mapped by channel idx.
* @param otherSdp the other SDP to check ssrc with.
@ -89,7 +90,7 @@ SDP.prototype.getNewMedia = function(otherSdp) {
}
}
return true;
};
}
var myMedia = this.getMediaSsrcMap();
var othersMedia = otherSdp.getMediaSsrcMap();
@ -111,7 +112,7 @@ SDP.prototype.getNewMedia = function(otherSdp) {
}
newMedia[channelNum].ssrcs[ssrc] = othersChannel.ssrcs[ssrc];
}
})
});
// Look for new ssrc groups across the channels
othersChannel.ssrcGroups.forEach(function(otherSsrcGroup){
@ -120,7 +121,7 @@ SDP.prototype.getNewMedia = function(otherSdp) {
var matched = false;
for (var i = 0; i < myChannel.ssrcGroups.length; i++) {
var mySsrcGroup = myChannel.ssrcGroups[i];
if (otherSsrcGroup.semantics == mySsrcGroup
if (otherSsrcGroup.semantics == mySsrcGroup.semantics
&& arrayEquals.apply(otherSsrcGroup.ssrcs, [mySsrcGroup.ssrcs])) {
matched = true;
@ -140,7 +141,8 @@ SDP.prototype.getNewMedia = function(otherSdp) {
});
});
return newMedia;
}
};
// remove iSAC and CN from SDP
SDP.prototype.mangle = function () {
var i, j, mline, lines, rtpmap, newdesc;
@ -676,7 +678,7 @@ SDP.prototype.jingle2media = function (content) {
media += SDPUtil.build_rtpmap(this) + '\r\n';
if ($(this).find('>parameter').length) {
media += 'a=fmtp:' + this.getAttribute('id') + ' ';
media += $(this).find('parameter').map(function () { return (this.getAttribute('name') ? (this.getAttribute('name') + '=') : '') + this.getAttribute('value'); }).get().join(';');
media += $(this).find('parameter').map(function () { return (this.getAttribute('name') ? (this.getAttribute('name') + '=') : '') + this.getAttribute('value'); }).get().join('; ');
media += '\r\n';
}
// xep-0293

4
muc.js
View File

@ -239,10 +239,10 @@ Strophe.addConnectionPlugin('emuc', {
if (txt) {
console.log('chat', nick, txt);
Chat.updateChatConversation(from, nick, txt);
if(APIConnector.isEnabled() && APIConnector.isEventEnabled("incommingMessage"))
if(APIConnector.isEnabled() && APIConnector.isEventEnabled("incomingMessage"))
{
if(from != this.myroomjid)
APIConnector.triggerEvent("incommingMessage",
APIConnector.triggerEvent("incomingMessage",
{"from": from, "nick": nick, "message": txt});
}
}

View File

@ -11,17 +11,17 @@ var RoomNameGenerator = function(my) {
//];
var pluralNouns = [
"Aliens", "Animals", "Antelopes", "Ants", "Apes", "Apples", "Baboons", "Bacteria", "Badgers", "Bananas", "Bats",
"Bears", "Birds", "Blokes", "Bonobos", "Boys", "Brides", "Brothers", "Bugs", "Bulls", "Butterflies", "Cheetahs",
"Bears", "Birds", "Bonobos", "Brides", "Bugs", "Bulls", "Butterflies", "Cheetahs",
"Cherries", "Chicken", "Children", "Chimps", "Clowns", "Cows", "Creatures", "Dinosaurs", "Dogs", "Dolphins",
"Donkeys", "Dragons", "Ducks", "Dudes", "Dwarfs", "Eagles", "Elephants", "Elves", "FAIL", "Fathers", "Fellows",
"Fish", "Flowers", "Folk", "Folks", "Frogs", "Fruit", "Fungi", "Galaxies", "Gals", "Geese", "Girls", "Goats",
"Gorillas", "Grooms", "Guys", "Hedgehogs", "Hippos", "Horses", "Hunters", "Insects", "Kids", "Knights",
"Lemons", "Lemurs", "Leopards", "LifeForms", "Lions", "Lizards", "Men", "Mice", "Monkeys", "Monsters",
"Mothers", "Mushrooms", "Octopodes", "Oranges", "Orangutans", "Organisms", "Pants", "Parrots", "Penguins",
"Donkeys", "Dragons", "Ducks", "Dwarfs", "Eagles", "Elephants", "Elves", "FAIL", "Fathers",
"Fish", "Flowers", "Frogs", "Fruit", "Fungi", "Galaxies", "Geese", "Goats",
"Gorillas", "Hedgehogs", "Hippos", "Horses", "Hunters", "Insects", "Kids", "Knights",
"Lemons", "Lemurs", "Leopards", "LifeForms", "Lions", "Lizards", "Mice", "Monkeys", "Monsters",
"Mushrooms", "Octopodes", "Oranges", "Orangutans", "Organisms", "Pants", "Parrots", "Penguins",
"People", "Pigeons", "Pigs", "Pineapples", "Plants", "Potatoes", "Priests", "Rats", "Reptiles", "Reptilians",
"Rhinos", "Seagulls", "Sheep", "Siblings", "Sisters", "Snakes", "Spaghetti", "Spiders", "Squid", "Squirrels",
"Stars", "Students", "Teachers", "Tigers", "Tomatoes", "Trees", "Vegetables", "Virgins", "Viruses", "Vulcans",
"Weasels", "Wenches", "Whales", "Witches", "Wizards", "Wolves", "Women", "Workers", "Worms", "Zebras"
"Rhinos", "Seagulls", "Sheep", "Siblings", "Snakes", "Spaghetti", "Spiders", "Squid", "Squirrels",
"Stars", "Students", "Teachers", "Tigers", "Tomatoes", "Trees", "Vampires", "Vegetables", "Viruses", "Vulcans",
"Warewolves", "Weasels", "Whales", "Witches", "Wizards", "Wolves", "Workers", "Worms", "Zebras"
];
//var places = [
//"Pub", "University", "Airport", "Library", "Mall", "Theater", "Stadium", "Office", "Show", "Gallows", "Beach",
@ -30,7 +30,7 @@ var RoomNameGenerator = function(my) {
// "Bridge"
//];
var verbs = [
"Abandon", "Adapt", "Advertise", "Answer", "Answer", "Anticipate", "Appreciate", "Appreciate",
"Abandon", "Adapt", "Advertise", "Answer", "Anticipate", "Appreciate",
"Approach", "Argue", "Ask", "Bite", "Blossom", "Blush", "Breathe", "Breed", "Bribe", "Burn", "Calculate",
"Clean", "Code", "Communicate", "Compute", "Confess", "Confiscate", "Conjugate", "Conjure", "Consume",
"Contemplate", "Crawl", "Dance", "Delegate", "Devour", "Develop", "Differ", "Discuss",
@ -39,9 +39,9 @@ var RoomNameGenerator = function(my) {
"Gather", "Glow", "Grow", "Hex", "Hide", "Hug", "Hurry", "Improve", "Intersect", "Investigate", "Jinx",
"Joke", "Jubilate", "Kiss", "Laugh", "Manage", "Meet", "Merge", "Move", "Object", "Observe", "Offer",
"Paint", "Participate", "Party", "Perform", "Plan", "Pursue", "Pierce", "Play", "Postpone", "Pray", "Proclaim",
"Question", "Read", "Reckon", "Rejoice", "Represent", "Resize", "Rhyme", "Scream", "Search", "Secrete", "Select", "Share", "Shoot",
"Shout", "Shout", "Signal", "Sing", "Skate", "Sleep", "Smile", "Smoke", "Solve", "Spell", "Spell", "Steer", "Stink",
"Substitute", "Sweat", "Swim", "Taste", "Teach", "Terminate", "Think", "Type", "Unite", "Vanish", "Worship"
"Question", "Read", "Reckon", "Rejoice", "Represent", "Resize", "Rhyme", "Scream", "Search", "Select", "Share", "Shoot",
"Shout", "Signal", "Sing", "Skate", "Sleep", "Smile", "Smoke", "Solve", "Spell", "Steer", "Stink",
"Substitute", "Swim", "Taste", "Teach", "Terminate", "Think", "Type", "Unite", "Vanish", "Worship"
];
var adverbs = [
"Absently", "Accurately", "Accusingly", "Adorably", "AllTheTime", "Alone", "Always", "Amazingly", "Angrily",
@ -69,24 +69,24 @@ var RoomNameGenerator = function(my) {
"Choppy", "Chosen", "Clever", "Cold", "Cool", "Crawly", "Crazy", "Creepy", "Cruel", "Curious", "Cynical",
"Dangerous", "Dark", "Delicate", "Desperate", "Difficult", "Discreet", "Disguised", "Dizzy",
"Dumb", "Eager", "Easy", "Edgy", "Electric", "Elegant", "Emancipated", "Enormous", "Euphoric", "Evil",
"FAIL", "Fast", "Fat", "Ferocious", "Fierce", "Fine", "Flawed", "Flying", "Foolish", "Foxy",
"FAIL", "Fast", "Ferocious", "Fierce", "Fine", "Flawed", "Flying", "Foolish", "Foxy",
"Freezing", "Funny", "Furious", "Gentle", "Glorious", "Golden", "Good", "Green", "Green", "Guilty",
"Hairy", "Happy", "Hard", "Hasty", "Hazy", "Heroic", "Hostile", "Hot", "Humble", "Humongous",
"Humorous", "Hysterical", "Idealistic", "Ignorant", "Immense", "Impartial", "Impolite", "Indifferent",
"Infuriated", "Insightful", "Intense", "Interesting", "Intimidated", "Intriguing", "Jealous", "Jolly", "Jovial",
"Jumpy", "Kind", "Laughing", "Lazy", "Liquid", "Lonely", "Longing", "Loud", "Loving", "Loyal", "Macabre", "Mad",
"Magical", "Magnificent", "Malevolent", "Manly", "Medieval", "Memorable", "Mere", "Merry", "Mighty",
"Magical", "Magnificent", "Malevolent", "Medieval", "Memorable", "Mere", "Merry", "Mighty",
"Mischievous", "Miserable", "Modified", "Moody", "Most", "Mysterious", "Mystical", "Needy",
"Nervous", "Nice", "Objective", "Obnoxious", "Obsessive", "Obvious", "Opinionated", "Orange",
"Painful", "Passionate", "Perfect", "Pink", "Playful", "Poisonous", "Polite", "Poor", "Popular", "Powerful",
"Precise", "Preserved", "Pretty", "Purple", "Quick", "Quiet", "Random", "Rapid", "Rare", "Real",
"Reassuring", "Reckless", "Red", "Regular", "Remorseful", "Responsible", "Rich", "Rotten", "Rude", "Ruthless",
"Sad", "Scared", "Scary", "Scornful", "Screaming", "Selfish", "Selfish", "Serious", "Shady", "Shaky", "Sharp",
"Reassuring", "Reckless", "Red", "Regular", "Remorseful", "Responsible", "Rich", "Rude", "Ruthless",
"Sad", "Scared", "Scary", "Scornful", "Screaming", "Selfish", "Serious", "Shady", "Shaky", "Sharp",
"Shiny", "Shy", "Simple", "Sleepy", "Slow", "Sly", "Small", "Smart", "Smelly", "Smiling", "Smooth",
"Smug", "Sober", "Soft", "Solemn", "Square", "Square", "Steady", "Strange", "Strong",
"Stunning", "Subjective", "Successful", "Surly", "Sweet", "Tactful", "Tense",
"Thoughtful", "Tight", "Tiny", "Tolerant", "Uneasy", "Unique", "Unseen", "Warm", "Weak",
"Weird", "WellCooked", "WellFed", "Wicked", "Wild", "Wise", "Witty", "Wonderful", "Worried", "Yellow", "Young",
"Weird", "WellCooked", "Wild", "Wise", "Witty", "Wonderful", "Worried", "Yellow", "Young",
"Zealous"
];
//var pronouns = [

View File

@ -6,7 +6,7 @@
* @constructor
*/
function SimulcastUtils() {
this.logger = new SimulcastLogger("SimulcastUtils");
this.logger = new SimulcastLogger("SimulcastUtils", 1);
}
/**
@ -55,6 +55,13 @@ SimulcastUtils.prototype._replaceVideoSources = function (lines, videoSources) {
};
SimulcastUtils.prototype.isValidDescription = function (desc)
{
return desc && desc != null
&& desc.type && desc.type != ''
&& desc.sdp && desc.sdp != '';
};
SimulcastUtils.prototype._getVideoSources = function (lines) {
var i, inVideo = false, sb = [];
@ -137,7 +144,7 @@ SimulcastUtils.prototype.parseMedia = function (lines, mediatypes) {
lines[i].substring(0, 'a=sendonly'.length) === 'a=sendonly' ||
lines[i].substring(0, 'a=inactive'.length) === 'a=inactive')) {
cur_media.direction = lines[i].substring('a='.length, 8);
cur_media.direction = lines[i].substring('a='.length);
}
}
@ -226,7 +233,7 @@ SimulcastUtils.prototype._compileVideoSources = function (videoSources) {
function SimulcastReceiver() {
this.simulcastUtils = new SimulcastUtils();
this.logger = new SimulcastLogger('SimulcastReceiver');
this.logger = new SimulcastLogger('SimulcastReceiver', 1);
}
SimulcastReceiver.prototype._remoteVideoSourceCache = '';
@ -275,7 +282,7 @@ SimulcastReceiver.prototype._restoreSimulcastGroups = function (sb) {
SimulcastReceiver.prototype.reverseTransformRemoteDescription = function (desc) {
var sb;
if (!desc || desc == null) {
if (!this.simulcastUtils.isValidDescription(desc)) {
return desc;
}
@ -487,11 +494,9 @@ SimulcastReceiver.prototype.getRemoteVideoStreamIdBySSRC = function (ssrc) {
function SimulcastSender() {
this.simulcastUtils = new SimulcastUtils();
this.logger = new SimulcastLogger('SimulcastSender');
this.logger = new SimulcastLogger('SimulcastSender', 1);
}
SimulcastSender.prototype._localVideoSourceCache = '';
SimulcastSender.prototype.localStream = null;
SimulcastSender.prototype.displayedLocalVideoStream = null;
SimulcastSender.prototype._generateGuid = (function () {
@ -507,14 +512,6 @@ SimulcastSender.prototype._generateGuid = (function () {
};
}());
SimulcastSender.prototype._cacheLocalVideoSources = function (lines) {
this._localVideoSourceCache = this.simulcastUtils._getVideoSources(lines);
};
SimulcastSender.prototype._restoreLocalVideoSources = function (lines) {
this.simulcastUtils._replaceVideoSources(lines, this._localVideoSourceCache);
};
// Returns a random integer between min (included) and max (excluded)
// Using Math.round() gives a non-uniform distribution!
SimulcastSender.prototype._generateRandomSSRC = function () {
@ -522,7 +519,37 @@ SimulcastSender.prototype._generateRandomSSRC = function () {
return Math.floor(Math.random() * (max - min)) + min;
};
SimulcastSender.prototype._appendSimulcastGroup = function (lines) {
SimulcastSender.prototype.getLocalVideoStream = function () {
return (this.displayedLocalVideoStream != null)
? this.displayedLocalVideoStream
// in case we have no simulcast at all, i.e. we didn't perform the GUM
: connection.jingle.localVideo;
};
function NativeSimulcastSender() {
SimulcastSender.call(this); // call the super constructor.
}
NativeSimulcastSender.prototype = Object.create(SimulcastSender.prototype);
NativeSimulcastSender.prototype._localExplosionMap = {};
NativeSimulcastSender.prototype._isUsingScreenStream = false;
NativeSimulcastSender.prototype._localVideoSourceCache = '';
NativeSimulcastSender.prototype.reset = function () {
this._localExplosionMap = {};
this._isUsingScreenStream = isUsingScreenStream;
};
NativeSimulcastSender.prototype._cacheLocalVideoSources = function (lines) {
this._localVideoSourceCache = this.simulcastUtils._getVideoSources(lines);
};
NativeSimulcastSender.prototype._restoreLocalVideoSources = function (lines) {
this.simulcastUtils._replaceVideoSources(lines, this._localVideoSourceCache);
};
NativeSimulcastSender.prototype._appendSimulcastGroup = function (lines) {
var videoSources, ssrcGroup, simSSRC, numOfSubs = 2, i, sb, msid;
this.logger.info('Appending simulcast group...');
@ -558,7 +585,7 @@ SimulcastSender.prototype._appendSimulcastGroup = function (lines) {
};
// Does the actual patching.
SimulcastSender.prototype._ensureSimulcastGroup = function (lines) {
NativeSimulcastSender.prototype._ensureSimulcastGroup = function (lines) {
this.logger.info('Ensuring simulcast group...');
@ -572,21 +599,6 @@ SimulcastSender.prototype._ensureSimulcastGroup = function (lines) {
}
};
SimulcastSender.prototype.getLocalVideoStream = function () {
return (this.displayedLocalVideoStream != null)
? this.displayedLocalVideoStream
// in case we have no simulcast at all, i.e. we didn't perform the GUM
: connection.jingle.localVideo;
};
function NativeSimulcastSender() {
SimulcastSender.call(this); // call the super constructor.
}
NativeSimulcastSender.prototype = Object.create(SimulcastSender.prototype);
NativeSimulcastSender.prototype._localExplosionMap = {};
/**
* Produces a single stream with multiple tracks for local video sources.
*
@ -644,10 +656,7 @@ NativeSimulcastSender.prototype._explodeSimulcastSenderSources = function (lines
NativeSimulcastSender.prototype.getUserMedia = function (constraints, success, err) {
// There's nothing special to do for native simulcast, so just do a normal GUM.
var self = this;
navigator.webkitGetUserMedia(constraints, function (hqStream) {
self.localStream = hqStream;
success(hqStream);
}, err);
};
@ -662,7 +671,7 @@ NativeSimulcastSender.prototype.getUserMedia = function (constraints, success, e
NativeSimulcastSender.prototype.reverseTransformLocalDescription = function (desc) {
var sb;
if (!desc || desc == null) {
if (!this.simulcastUtils.isValidDescription(desc) || this._isUsingScreenStream) {
return desc;
}
@ -690,6 +699,10 @@ NativeSimulcastSender.prototype.reverseTransformLocalDescription = function (des
*/
NativeSimulcastSender.prototype.transformAnswer = function (desc) {
if (!this.simulcastUtils.isValidDescription(desc) || this._isUsingScreenStream) {
return desc;
}
var sb = desc.sdp.split('\r\n');
// Even if we have enabled native simulcasting previously
@ -738,7 +751,8 @@ SimulcastReceiver.prototype.transformRemoteDescription = function (desc) {
this._updateRemoteMaps(sb);
this._cacheRemoteVideoSources(sb);
// NOTE(gp) this needs to be called after updateRemoteMaps because we need the simulcast group in the _updateRemoteMaps() method.
// NOTE(gp) this needs to be called after updateRemoteMaps because we
// need the simulcast group in the _updateRemoteMaps() method.
this.simulcastUtils._removeSimulcastGroup(sb);
if (desc.sdp.indexOf('a=ssrc-group:SIM') !== -1) {
@ -770,6 +784,7 @@ function SimpleSimulcastSender() {
SimpleSimulcastSender.prototype = Object.create(SimulcastSender.prototype);
SimpleSimulcastSender.prototype.localStream = null;
SimpleSimulcastSender.prototype._localMaps = {
msids: [],
msid2ssrc: {}
@ -885,7 +900,7 @@ SimpleSimulcastSender.prototype.getUserMedia = function (constraints, success, e
SimpleSimulcastSender.prototype.reverseTransformLocalDescription = function (desc) {
var sb;
if (!desc || desc == null) {
if (!this.simulcastUtils.isValidDescription(desc)) {
return desc;
}
@ -982,10 +997,8 @@ NoSimulcastSender.prototype = Object.create(SimulcastSender.prototype);
* @param err
*/
NoSimulcastSender.prototype.getUserMedia = function (constraints, success, err) {
var self = this;
navigator.webkitGetUserMedia(constraints, function (hqStream) {
self.localStream = hqStream;
success(self.localStream);
success(hqStream);
}, err);
};
@ -1194,24 +1207,37 @@ SimulcastManager.prototype._setLocalVideoStreamEnabled = function(ssrc, enabled)
this.simulcastSender._setLocalVideoStreamEnabled(ssrc, enabled);
};
SimulcastManager.prototype.resetSender = function() {
if (typeof this.simulcastSender.reset === 'function'){
this.simulcastSender.reset();
}
};
/**
*
* @constructor
*/
function SimulcastLogger(name) {
function SimulcastLogger(name, lvl) {
this.name = name;
this.lvl = lvl;
}
SimulcastLogger.prototype.log = function (text) {
if (this.lvl) {
console.log(text);
}
};
SimulcastLogger.prototype.info = function (text) {
if (this.lvl > 1) {
console.info(text);
}
};
SimulcastLogger.prototype.fine = function (text) {
if (this.lvl > 2) {
console.log(text);
}
};
SimulcastLogger.prototype.error = function (text) {
@ -1235,4 +1261,4 @@ $(document).bind('startsimulcastlayer', function (event, simulcastLayer) {
$(document).bind('stopsimulcastlayer', function (event, simulcastLayer) {
var ssrc = simulcastLayer.primarySSRC;
simulcast._setLocalVideoStreamEnabled(ssrc, false);
});
});

View File

@ -1,7 +1,10 @@
var VideoLayout = (function (my) {
var currentDominantSpeaker = null;
var lastNCount = config.channelLastN;
var localLastNCount = config.channelLastN;
var localLastNSet = [];
var lastNEndpointsCache = [];
var lastNPickupJid = null;
var largeVideoState = {
updateInProgress: false,
newSrc: ''
@ -271,7 +274,7 @@ var VideoLayout = (function (my) {
}
};
my.handleVideoThumbClicked = function(videoSrc, jid) {
my.handleVideoThumbClicked = function(videoSrc, noPinnedEndpointChangedEvent, jid) {
// Restore style for previously focused video
var oldContainer = null;
if(focusedVideoSrc) {
@ -283,7 +286,7 @@ var VideoLayout = (function (my) {
oldContainer.removeClass("videoContainerFocused");
}
// Unlock current focused.
// Unlock current focused.
if (focusedVideoSrc && focusedVideoSrc.src === videoSrc)
{
focusedVideoSrc = null;
@ -299,6 +302,9 @@ var VideoLayout = (function (my) {
}
}
if (!noPinnedEndpointChangedEvent) {
$(document).trigger("pinnedendpointchanged");
}
return;
}
@ -313,6 +319,14 @@ var VideoLayout = (function (my) {
{
var container = getParticipantContainer(jid);
container.addClass("videoContainerFocused");
if (!noPinnedEndpointChangedEvent) {
$(document).trigger("pinnedendpointchanged", [userJid]);
}
}
if ($('#largeVideo').attr('src') == videoSrc) {
return;
}
// Triggers a "video.selected" event. The "false" parameter indicates
@ -426,10 +440,10 @@ var VideoLayout = (function (my) {
container.appendChild(nickfield);
// In case this is not currently in the last n we don't show it.
if (lastNCount
&& lastNCount > 0
&& $('#remoteVideos>span').length >= lastNCount + 2) {
showPeerContainer(resourceJid, false);
if (localLastNCount
&& localLastNCount > 0
&& $('#remoteVideos>span').length >= localLastNCount + 2) {
showPeerContainer(resourceJid, 'hide');
}
else
VideoLayout.resizeThumbnails();
@ -594,24 +608,46 @@ var VideoLayout = (function (my) {
/**
* Show/hide peer container for the given resourceJid.
*/
function showPeerContainer(resourceJid, isShow) {
function showPeerContainer(resourceJid, state) {
var peerContainer = $('#participant_' + resourceJid);
if (!peerContainer)
return;
if (!peerContainer.is(':visible') && isShow)
peerContainer.show();
else if (peerContainer.is(':visible') && !isShow)
var isHide = state === 'hide';
var resizeThumbnails = false;
if (!isHide) {
if (!peerContainer.is(':visible')) {
resizeThumbnails = true;
peerContainer.show();
}
// TODO(gp) add proper avatars handling.
if (state == 'show')
{
peerContainer.css('-webkit-filter', '');
}
else // if (state == 'avatar')
{
peerContainer.css('-webkit-filter', 'grayscale(100%)');
}
}
else if (peerContainer.is(':visible') && isHide)
{
resizeThumbnails = true;
peerContainer.hide();
if(VideoLayout.connectionIndicators['participant_' + resourceJid])
VideoLayout.connectionIndicators['participant_' + resourceJid].hide();
}
VideoLayout.resizeThumbnails();
if (resizeThumbnails) {
VideoLayout.resizeThumbnails();
}
ContactList.setClickable(resourceJid, isShow);
// We want to be able to pin a participant from the contact list, even
// if he's not in the lastN set!
// ContactList.setClickable(resourceJid, !isHide);
};
@ -1044,8 +1080,8 @@ var VideoLayout = (function (my) {
var availableHeight = 100;
var numvids = $('#remoteVideos>span:visible').length;
if (lastNCount && lastNCount > 0) {
numvids = Math.min(lastNCount + 1, numvids);
if (localLastNCount && localLastNCount > 0) {
numvids = Math.min(localLastNCount + 1, numvids);
}
// Remove the 3px borders arround videos and border around the remote
@ -1183,6 +1219,22 @@ var VideoLayout = (function (my) {
return containerElement.id.substring(i + 12);
};
my.getLargeVideoResource = function () {
var largeVideoJid, largeVideoResource;
// Another approach could be to compare the srcs of the thumbnails and
// then call getPeerContainerResourceJid.
var largeVideoSsrc
= videoSrcToSsrc[$('#largeVideo').attr('src')];
if (largeVideoSsrc
/* variables/state checking to prevent exceptions */
&& (largeVideoJid = ssrc2jid[largeVideoSsrc])
&& (largeVideoResource = Strophe.getResourceFromJid(largeVideoJid)))
return largeVideoResource;
};
/**
* Adds the remote video menu element for the given <tt>jid</tt> in the
* given <tt>parentElement</tt>.
@ -1265,6 +1317,48 @@ var VideoLayout = (function (my) {
popupmenuElement.appendChild(paddingSpan);
}
/**
* On contact list item clicked.
*/
$(ContactList).bind('contactclicked', function(event, jid) {
if (!jid) {
return;
}
var resource = Strophe.getResourceFromJid(jid);
var videoContainer = $("#participant_" + resource);
if (videoContainer.length > 0) {
var videoThumb = $('video', videoContainer).get(0);
// It is not always the case that a videoThumb exists (if there is
// no actual video).
if (videoThumb) {
if (videoThumb.src && videoThumb.src != '') {
// We have a video src, great! Let's update the large video
// now.
VideoLayout.handleVideoThumbClicked(videoThumb.src);
} else {
// If we don't have a video src for jid, there's absolutely
// no point in calling handleVideoThumbClicked; Quite
// simply, it won't work because it needs an src to attach
// to the large video.
//
// Instead, we trigger the pinned endpoint changed event to
// let the bridge adjust its lastN set for myjid and store
// the pinned user in the lastNPickupJid variable to be
// picked up later by the lastN changed event handler.
lastNPickupJid = jid;
$(document).trigger("pinnedendpointchanged", [jid]);
}
} else if (jid == connection.emuc.myroomjid) {
$("#localVideoContainer").click();
}
}
});
/**
* On audio muted event.
*/
@ -1392,13 +1486,68 @@ var VideoLayout = (function (my) {
lastNEndpointsCache = lastNEndpoints;
// Say A, B, C, D, E, and F are in a conference and LastN = 3.
//
// If LastN drops to, say, 2, because of adaptivity, then E should see
// thumbnails for A, B and C. A and B are in E's server side LastN set,
// so E sees them. C is only in E's local LastN set.
//
// If F starts talking and LastN = 3, then E should see thumbnails for
// F, A, B. B gets "ejected" from E's server side LastN set, but it
// enters E's local LastN ejecting C.
// Increase the local LastN set size, if necessary.
if (lastNCount > localLastNCount) {
localLastNCount = lastNCount;
}
// Update the local LastN set preserving the order in which the
// endpoints appeared in the LastN/local LastN set.
var nextLocalLastNSet = lastNEndpoints.slice(0);
for (var i = 0; i < localLastNSet.length; i++) {
if (nextLocalLastNSet.length >= localLastNCount) {
break;
}
var resourceJid = localLastNSet[i];
if (nextLocalLastNSet.indexOf(resourceJid) === -1) {
nextLocalLastNSet.push(resourceJid);
}
}
localLastNSet = nextLocalLastNSet;
var updateLargeVideo = false;
// Handle LastN/local LastN changes.
$('#remoteVideos>span').each(function( index, element ) {
var resourceJid = VideoLayout.getPeerContainerResourceJid(element);
var isReceived = true;
if (resourceJid
&& lastNEndpoints.indexOf(resourceJid) < 0) {
&& lastNEndpoints.indexOf(resourceJid) < 0
&& localLastNSet.indexOf(resourceJid) < 0) {
console.log("Remove from last N", resourceJid);
showPeerContainer(resourceJid, false);
showPeerContainer(resourceJid, 'hide');
isReceived = false;
} else if (resourceJid
&& $('#participant_' + resourceJid).is(':visible')
&& lastNEndpoints.indexOf(resourceJid) < 0
&& localLastNSet.indexOf(resourceJid) >= 0) {
showPeerContainer(resourceJid, 'avatar');
isReceived = false;
}
if (!isReceived) {
// resourceJid has dropped out of the server side lastN set, so
// it is no longer being received. If resourceJid was being
// displayed in the large video we have to switch to another
// user.
var largeVideoResource = VideoLayout.getLargeVideoResource();
if (!updateLargeVideo && resourceJid === largeVideoResource) {
updateLargeVideo = true;
}
}
});
@ -1408,9 +1557,10 @@ var VideoLayout = (function (my) {
if (endpointsEnteringLastN && endpointsEnteringLastN.length > 0) {
endpointsEnteringLastN.forEach(function (resourceJid) {
if (!$('#participant_' + resourceJid).is(':visible')) {
var isVisible = $('#participant_' + resourceJid).is(':visible');
showPeerContainer(resourceJid, 'show');
if (!isVisible) {
console.log("Add to last N", resourceJid);
showPeerContainer(resourceJid, true);
mediaStreams.some(function (mediaStream) {
if (mediaStream.peerjid
@ -1421,6 +1571,18 @@ var VideoLayout = (function (my) {
var videoStream = simulcast.getReceivingVideoStream(mediaStream.stream);
RTC.attachMediaStream(sel, videoStream);
videoSrcToSsrc[sel.attr('src')] = mediaStream.ssrc;
if (lastNPickupJid == mediaStream.peerjid) {
// Clean up the lastN pickup jid.
lastNPickupJid = null;
// Don't fire the events again, they've already
// been fired in the contact list click handler.
VideoLayout.handleVideoThumbClicked($(sel).attr('src'), false);
updateLargeVideo = false;
}
waitForRemoteVideo(
sel,
mediaStream.ssrc,
@ -1431,6 +1593,37 @@ var VideoLayout = (function (my) {
}
});
}
// The endpoint that was being shown in the large video has dropped out
// of the lastN set and there was no lastN pickup jid. We need to update
// the large video now.
if (updateLargeVideo) {
var resource, container, src;
var myResource
= Strophe.getResourceFromJid(connection.emuc.myroomjid);
// Find out which endpoint to show in the large video.
for (var i = 0; i < lastNEndpoints.length; i++) {
resource = lastNEndpoints[i];
if (!resource || resource === myResource)
continue;
container = $("#participant_" + resource);
if (container.length == 0)
continue;
src = $('video', container).attr('src');
if (!src)
continue;
// videoSrcToSsrc needs to be update for this call to succeed.
VideoLayout.updateLargeVideo(src);
break;
}
}
});
$(document).bind('videoactive.jingle', function (event, videoelem) {
@ -1462,6 +1655,12 @@ var VideoLayout = (function (my) {
$(document).bind('simulcastlayerschanging', function (event, endpointSimulcastLayers) {
endpointSimulcastLayers.forEach(function (esl) {
var resource = esl.endpoint;
if (lastNCount < 1 || lastNEndpointsCache.indexOf(resource) === -1) {
return;
}
var primarySSRC = esl.simulcastLayer.primarySSRC;
// Get session and stream from primary ssrc.
@ -1503,6 +1702,11 @@ var VideoLayout = (function (my) {
$(document).bind('simulcastlayerschanged', function (event, endpointSimulcastLayers) {
endpointSimulcastLayers.forEach(function (esl) {
var resource = esl.endpoint;
if (lastNCount < 1 || lastNEndpointsCache.indexOf(resource) === -1) {
return;
}
var primarySSRC = esl.simulcastLayer.primarySSRC;
// Get session and stream from primary ssrc.
@ -1521,7 +1725,7 @@ var VideoLayout = (function (my) {
var updateLargeVideo = (Strophe.getResourceFromJid(ssrc2jid[primarySSRC])
== largeVideoState.userJid);
var updateFocusedVideoSrc = (focusedVideoSrc &&
var updateFocusedVideoSrc = (focusedVideoSrc && focusVideoSrc.src && focusVideoSrc.src != '' &&
(RTC.getVideoSrc(selRemoteVideo[0]) == focusedVideoSrc.src));
var electedStreamUrl;
@ -1541,7 +1745,6 @@ var VideoLayout = (function (my) {
RTC.attachMediaStream(selRemoteVideo, electedStream);
}
var jid = ssrc2jid[primarySSRC];
jid2Ssrc[jid] = primarySSRC;
@ -1554,15 +1757,14 @@ var VideoLayout = (function (my) {
focusedVideoSrc.src = RTC.getVideoSrc(selRemoteVideo[0]);
}
var videoId;
if(jid == connection.emuc.myroomjid)
if(resource == Strophe.getResourceFromJid(connection.emuc.myroomjid))
{
videoId = "localVideoContainer";
}
else
{
videoId = "participant_" + Strophe.getResourceFromJid(jid);
videoId = "participant_" + resource;
}
var connectionIndicator = VideoLayout.connectionIndicators[videoId];
if(connectionIndicator)