Merge branch 'master' into firefox
Conflicts: app.js libs/strophe/strophe.jingle.adapter.js simulcast.js videolayout.js
This commit is contained in:
commit
5213583af2
|
@ -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
43
app.js
|
@ -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
|
||||
|
|
|
@ -25,6 +25,6 @@ var config = {
|
|||
useRtcpMux: true,
|
||||
useBundle: true,
|
||||
enableRecording: false,
|
||||
enableWelcomePage: false,
|
||||
enableWelcomePage: true,
|
||||
enableSimulcast: false
|
||||
};
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
16
css/chat.css
16
css/chat.css
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
#!/bin/sh -e
|
||||
|
||||
# Source debconf library.
|
||||
. /usr/share/debconf/confmodule
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
;;
|
||||
|
||||
*)
|
||||
|
|
|
@ -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.
|
|
@ -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
|
||||
|
|
10
doc/api.md
10
doc/api.md
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
10
index.html
10
index.html
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
4
muc.js
|
@ -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});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 = [
|
||||
|
|
112
simulcast.js
112
simulcast.js
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
248
videolayout.js
248
videolayout.js
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue