Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
5a8819cd7b
|
@ -6,3 +6,4 @@ node_modules
|
|||
deploy-local.sh
|
||||
libs/app.bundle.*
|
||||
all.css
|
||||
.remote-sync.json
|
||||
|
|
|
@ -69,4 +69,5 @@ var config = {
|
|||
/*noticeMessage: 'Service update is scheduled for 16th March 2015. ' +
|
||||
'During that time service will not be available. ' +
|
||||
'Apologise for inconvenience.'*/
|
||||
disableThirdPartyRequests: false
|
||||
};
|
||||
|
|
|
@ -44,6 +44,8 @@
|
|||
vertical-align: middle;
|
||||
font-size: 22pt;
|
||||
border-radius: 20px;
|
||||
max-height: 30px;
|
||||
max-width: 30px;
|
||||
}
|
||||
|
||||
#contactlist .clickable {
|
||||
|
|
|
@ -248,6 +248,7 @@ form {
|
|||
}
|
||||
|
||||
div.feedbackButton {
|
||||
display: none;
|
||||
position: absolute;
|
||||
background-color: rgba(0,0,0,.50);
|
||||
border-radius: 50%;
|
||||
|
|
|
@ -11,8 +11,8 @@ body {
|
|||
#wrap{
|
||||
display: block;
|
||||
position: absolute;
|
||||
width:900px;
|
||||
height: 365px;
|
||||
width:500px;
|
||||
height: 565px;
|
||||
overflow:hidden;
|
||||
text-align: center;
|
||||
margin: auto;
|
||||
|
@ -29,7 +29,7 @@ body {
|
|||
#text{
|
||||
display:inline-block;
|
||||
font-size: 28px;
|
||||
width: 568px;
|
||||
/* width: 568px; */
|
||||
vertical-align:middle;
|
||||
padding-top: 25px;
|
||||
}
|
||||
|
@ -51,18 +51,23 @@ a {
|
|||
.browser_wrapper
|
||||
{
|
||||
width: 138px;
|
||||
height: 188px;
|
||||
/* height: 188px; */
|
||||
vertical-align: middle;
|
||||
color: #929391;
|
||||
font-size: 20px;
|
||||
float: left;
|
||||
margin-left: 15px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.browser_text
|
||||
{
|
||||
height: 2em;
|
||||
}
|
||||
.supported_browsers
|
||||
{
|
||||
margin: 0px auto 0px auto;
|
||||
width: 660px;
|
||||
/* width: 660px; */
|
||||
}
|
||||
|
||||
.clear
|
||||
|
@ -97,14 +102,14 @@ a {
|
|||
}
|
||||
#chromium_logo
|
||||
{
|
||||
width: 85px;
|
||||
height: 79px;
|
||||
width: 77px;
|
||||
height: 78px;
|
||||
background-image: url('/images/chromium.png');
|
||||
}
|
||||
#firefox_logo
|
||||
{
|
||||
width: 73px;
|
||||
height: 79px;
|
||||
width: 86px;
|
||||
height: 80px;
|
||||
background-image: url('/images/firefox.png');
|
||||
}
|
||||
|
||||
|
@ -114,5 +119,18 @@ a {
|
|||
height: 78px;
|
||||
background-image: url('/images/opera.png');
|
||||
}
|
||||
|
||||
|
||||
|
||||
#safari_logo
|
||||
{
|
||||
width: 78px;
|
||||
height: 79px;
|
||||
background-image: url('/images/safari.png');
|
||||
}
|
||||
|
||||
#ie_logo
|
||||
{
|
||||
width: 80px;
|
||||
height: 78px;
|
||||
background-image: url('/images/ie.png');
|
||||
}
|
||||
|
||||
|
|
|
@ -319,6 +319,27 @@
|
|||
z-index: 3;
|
||||
}
|
||||
|
||||
.videocontainer>span.dominantspeakerindicator {
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
z-index: 3;
|
||||
text-align: center;
|
||||
border-radius: 50%;
|
||||
background: #0cf;
|
||||
margin: 5px;
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
color: #FFFFFF;
|
||||
font-size: 11pt;
|
||||
border: 0px;
|
||||
}
|
||||
|
||||
#speakerindicatoricon {
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
#reloadPresentation {
|
||||
display: none;
|
||||
position: absolute;
|
||||
|
|
|
@ -35,6 +35,6 @@ Description: Prosody configuration for Jitsi Meet
|
|||
|
||||
Package: jitsi-meet-tokens
|
||||
Architecture: all
|
||||
Depends: ${misc:Depends}, prosody | prosody-trunk, jitsi-meet-prosody
|
||||
Depends: ${misc:Depends}, prosody-trunk (>= 1nightly603), libssl-dev, luarocks, jitsi-meet-prosody
|
||||
Description: Prosody token authentication plugin for Jitsi Meet
|
||||
|
||||
|
|
|
@ -62,13 +62,23 @@ case "$1" in
|
|||
sed -i 's/--plugin_paths/plugin_paths/g' $PROSODY_HOST_CONFIG
|
||||
sed -i 's/authentication = "anonymous"/authentication = "token"/g' $PROSODY_HOST_CONFIG
|
||||
sed -i 's/ --allow_unencrypted_plain_auth/ allow_unencrypted_plain_auth/g' $PROSODY_HOST_CONFIG
|
||||
sed -i "s/ --app_id=example_app_id/ app_id=$APP_ID/g" $PROSODY_HOST_CONFIG
|
||||
sed -i "s/ --app_secret=example_app_secret/ app_secret=$APP_SECRET/g" $PROSODY_HOST_CONFIG
|
||||
sed -i "s/ --app_id=\"example_app_id\"/ app_id=\"$APP_ID\"/g" $PROSODY_HOST_CONFIG
|
||||
sed -i "s/ --app_secret=\"example_app_secret\"/ app_secret=\"$APP_SECRET\"/g" $PROSODY_HOST_CONFIG
|
||||
sed -i 's/ --modules_enabled = { "token_verification" }/ modules_enabled = { "token_verification" }/g' $PROSODY_HOST_CONFIG
|
||||
|
||||
if [ -x "/etc/init.d/prosody" ]; then
|
||||
invoke-rc.d prosody reload
|
||||
# Install luajwt
|
||||
if ! luarocks install luajwt; then
|
||||
echo "Failed to install luajwt - try installing it manually"
|
||||
fi
|
||||
|
||||
if [ -x "/etc/init.d/prosody" ]; then
|
||||
invoke-rc.d prosody restart
|
||||
fi
|
||||
|
||||
echo "This package requires BOSH Prosody module to be patched !"
|
||||
echo "Use the following command, after this package has been installed and"
|
||||
echo "after every prosody-trunk upgrade:"
|
||||
echo "sudo patch -N /usr/lib/prosody/modules/mod_bosh.lua /usr/share/jitsi-meet/prosody-plugins/mod_bosh.lua.patch"
|
||||
else
|
||||
echo "Failed apply auto-config to $PROSODY_HOST_CONFIG which most likely comes from not supported version of jitsi-meet"
|
||||
fi
|
||||
|
|
|
@ -39,13 +39,12 @@ case "$1" in
|
|||
# Revert prosody config
|
||||
sed -i 's/plugin_paths/--plugin_paths/g' $PROSODY_HOST_CONFIG
|
||||
sed -i 's/authentication = "token"/authentication = "anonymous"/g' $PROSODY_HOST_CONFIG
|
||||
sed -i 's/ allow_unencrypted_plain_auth/ --allow_unencrypted_plain_auth/g' $PROSODY_HOST_CONFIG
|
||||
sed -i "s/ app_id=$APP_ID/ --app_id=example_app_id/g" $PROSODY_HOST_CONFIG
|
||||
sed -i "s/ app_secret=$APP_SECRET/ --app_secret=example_app_secret/g" $PROSODY_HOST_CONFIG
|
||||
sed -i "s/ app_id=\"$APP_ID\"/ --app_id=\"example_app_id\"/g" $PROSODY_HOST_CONFIG
|
||||
sed -i "s/ app_secret=\"$APP_SECRET\"/ --app_secret=\"example_app_secret\"/g" $PROSODY_HOST_CONFIG
|
||||
sed -i 's/ modules_enabled = { "token_verification" }/ --modules_enabled = { "token_verification" }/g' $PROSODY_HOST_CONFIG
|
||||
|
||||
if [ -x "/etc/init.d/prosody" ]; then
|
||||
invoke-rc.d prosody reload
|
||||
invoke-rc.d prosody restart
|
||||
fi
|
||||
fi
|
||||
|
||||
|
|
|
@ -4,11 +4,10 @@
|
|||
VirtualHost "jitmeet.example.com"
|
||||
-- enabled = false -- Remove this line to enable this host
|
||||
authentication = "anonymous"
|
||||
-- Three properties below get uncommented by jitsi-meet-tokens package config
|
||||
-- Properties below are modified by jitsi-meet-tokens package config
|
||||
-- and authentication above is switched to "token"
|
||||
--allow_unencrypted_plain_auth = true;
|
||||
--app_id=example_app_id
|
||||
--app_secret=example_app_secret
|
||||
--app_id="example_app_id"
|
||||
--app_secret="example_app_secret"
|
||||
-- Assign this host a certificate for TLS, otherwise it would use the one
|
||||
-- set in the global section (if any).
|
||||
-- Note that old-style SSL on port 5223 only supports one certificate, and will always
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 4.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
17
index.html
17
index.html
|
@ -15,11 +15,9 @@
|
|||
<link rel="stylesheet" href="css/bootstrap.min.css"/>
|
||||
<link rel="stylesheet" href="css/all.css"/>
|
||||
<script>console.log("(TIME) index.html loaded:\t", window.performance.now());</script>
|
||||
<script src="https://api.callstats.io/static/callstats.min.js"></script>
|
||||
<script src="config.js?v=15"></script><!-- adapt to your needs, i.e. set hosts and bosh path -->
|
||||
<script src="interface_config.js?v=6"></script>
|
||||
<script src="libs/app.bundle.min.js?v=139"></script>
|
||||
<script src="analytics.js?v=1"></script><!-- google analytics plugin -->
|
||||
<!--
|
||||
Link used for inline installation of chrome desktop streaming extension,
|
||||
is updated automatically from the code with the value defined in config.js -->
|
||||
|
@ -225,7 +223,7 @@
|
|||
</div>
|
||||
<div id="settingsmenu" class="right-panel">
|
||||
<div class="icon-settings" data-i18n="settings.title"></div>
|
||||
<img id="avatar" src="https://www.gravatar.com/avatar/87291c37c25be69a072a4514931b1749?d=wavatar&size=30"/>
|
||||
<img id="avatar" src="images/avatar2.png"/>
|
||||
<div class="arrow-up"></div>
|
||||
<input type="text" id="setDisplayName" data-i18n="[placeholder]settings.name" placeholder="Name">
|
||||
<input type="text" id="setEmail" placeholder="E-Mail">
|
||||
|
@ -246,5 +244,18 @@
|
|||
<a id="feedbackButton" data-container="body" data-toggle="popover" data-placement="right" data-i18n="[data-content]feedback"><i class="fa fa-heart"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
if (!config.disableThirdPartyRequests) {
|
||||
[
|
||||
'https://api.callstats.io/static/callstats.min.js',
|
||||
'analytics.js?v=1'
|
||||
].forEach(function(extSrc) {
|
||||
var extScript = document.createElement('script');
|
||||
extScript.src = extSrc;
|
||||
extScript.async = false;
|
||||
document.head.appendChild(extScript);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -5,7 +5,6 @@ var interfaceConfig = {
|
|||
INITIAL_TOOLBAR_TIMEOUT: 20000,
|
||||
TOOLBAR_TIMEOUT: 4000,
|
||||
DEFAULT_REMOTE_DISPLAY_NAME: "Fellow Jitster",
|
||||
DEFAULT_DOMINANT_SPEAKER_DISPLAY_NAME: "speaker",
|
||||
DEFAULT_LOCAL_DISPLAY_NAME: "me",
|
||||
SHOW_JITSI_WATERMARK: true,
|
||||
JITSI_WATERMARK_LINK: "https://jitsi.org",
|
||||
|
@ -28,5 +27,7 @@ var interfaceConfig = {
|
|||
/**
|
||||
* Whether to only show the filmstrip (and hide the toolbar).
|
||||
*/
|
||||
filmStripOnly: false
|
||||
filmStripOnly: false,
|
||||
RANDOM_AVATAR_URL_PREFIX: false,
|
||||
RANDOM_AVATAR_URL_SUFFIX: false
|
||||
};
|
||||
|
|
|
@ -300,18 +300,18 @@ var RTC = {
|
|||
* @param handler the handler
|
||||
*/
|
||||
addMediaStreamInactiveHandler: function (mediaStream, handler) {
|
||||
if (mediaStream.addEventListener) {
|
||||
// chrome
|
||||
if(typeof mediaStream.active !== "undefined")
|
||||
mediaStream.inactive = handler;
|
||||
else
|
||||
mediaStream.onended = handler;
|
||||
} else {
|
||||
if(RTCBrowserType.isTemasysPluginUsed()) {
|
||||
// themasys
|
||||
mediaStream.attachEvent('ended', function () {
|
||||
handler(mediaStream);
|
||||
});
|
||||
}
|
||||
else {
|
||||
if(typeof mediaStream.active !== "undefined")
|
||||
mediaStream.oninactive = handler;
|
||||
else
|
||||
mediaStream.onended = handler;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Removes onended/inactive handler.
|
||||
|
@ -319,16 +319,16 @@ var RTC = {
|
|||
* @param handler the handler to remove.
|
||||
*/
|
||||
removeMediaStreamInactiveHandler: function (mediaStream, handler) {
|
||||
if (mediaStream.removeEventListener) {
|
||||
// chrome
|
||||
if(typeof mediaStream.active !== "undefined")
|
||||
mediaStream.inactive = null;
|
||||
else
|
||||
mediaStream.onended = null;
|
||||
} else {
|
||||
if(RTCBrowserType.isTemasysPluginUsed()) {
|
||||
// themasys
|
||||
mediaStream.detachEvent('ended', handler);
|
||||
}
|
||||
else {
|
||||
if(typeof mediaStream.active !== "undefined")
|
||||
mediaStream.oninactive = null;
|
||||
else
|
||||
mediaStream.onended = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -146,7 +146,9 @@ function getConstraints(um, resolution, bandwidth, fps, desktopStream) {
|
|||
// this later can be a problem with some of the tests
|
||||
if(RTCBrowserType.isFirefox() && config.firefox_fake_device)
|
||||
{
|
||||
constraints.audio = true;
|
||||
// seems to be fixed now, removing this experimental fix, as having
|
||||
// multiple audio tracks brake the tests
|
||||
//constraints.audio = true;
|
||||
constraints.fake = true;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* global $, interfaceConfig */
|
||||
/* global $, config, interfaceConfig */
|
||||
|
||||
/*
|
||||
* Created by Yana Stamcheva on 2/10/15.
|
||||
|
@ -73,6 +73,28 @@ var Feedback = {
|
|||
* The feedback score. -1 indicates no score has been given for now.
|
||||
*/
|
||||
feedbackScore: -1,
|
||||
/**
|
||||
* Initialise the Feedback functionality.
|
||||
*/
|
||||
init: function () {
|
||||
// CallStats is the way we send feedback, so we don't have to initialise
|
||||
// if callstats isn't enabled.
|
||||
if (!callStats.isEnabled())
|
||||
return;
|
||||
|
||||
$("div.feedbackButton").css("display", "block");
|
||||
$("#feedbackButton").click(function (event) {
|
||||
Feedback.openFeedbackWindow();
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Indicates if the feedback functionality is enabled.
|
||||
*
|
||||
* @return true if the feedback functionality is enabled, false otherwise.
|
||||
*/
|
||||
isEnabled: function() {
|
||||
return callStats.isEnabled();
|
||||
},
|
||||
/**
|
||||
* Opens the feedback window.
|
||||
*/
|
||||
|
@ -120,7 +142,7 @@ var Feedback = {
|
|||
var states = {
|
||||
overall_feedback: {
|
||||
html: constructOverallFeedbackHtml(),
|
||||
persistent: true,
|
||||
persistent: false,
|
||||
buttons: {},
|
||||
closeText: '',
|
||||
focus: "div[id='stars']",
|
||||
|
@ -161,7 +183,7 @@ var Feedback = {
|
|||
var feedbackDialog
|
||||
= APP.UI.messageHandler.openDialogWithStates(
|
||||
states,
|
||||
{ persistent: true,
|
||||
{ persistent: false,
|
||||
buttons: {},
|
||||
closeText: '',
|
||||
loaded: onLoadFunction,
|
||||
|
|
|
@ -107,7 +107,7 @@ function setupChat() {
|
|||
function setupToolbars() {
|
||||
Toolbar.init(UI);
|
||||
Toolbar.setupButtonsFromConfig();
|
||||
BottomToolbar.init();
|
||||
BottomToolbar.init(eventEmitter);
|
||||
}
|
||||
|
||||
function streamHandler(stream, isMuted) {
|
||||
|
@ -325,8 +325,16 @@ function registerListeners() {
|
|||
"dialog.connectError", pres);
|
||||
});
|
||||
APP.xmpp.addListener(XMPPEvents.ROOM_CONNECT_ERROR, function (pres) {
|
||||
UI.messageHandler.openReportDialog(null,
|
||||
"dialog.connectError", pres);
|
||||
if (config.token &&
|
||||
$(pres).find(
|
||||
'>error[type="cancel"]' +
|
||||
'>not-allowed[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]'
|
||||
).length) {
|
||||
messageHandler.showError("dialog.error", "dialog.tokenAuthFailed");
|
||||
} else {
|
||||
UI.messageHandler.openReportDialog(null,
|
||||
"dialog.connectError", pres);
|
||||
}
|
||||
});
|
||||
|
||||
APP.xmpp.addListener(XMPPEvents.READY_TO_JOIN, function () {
|
||||
|
@ -343,6 +351,10 @@ function registerListeners() {
|
|||
AudioLevels.init();
|
||||
});
|
||||
|
||||
UI.addListener(UIEvents.FILM_STRIP_TOGGLED, function (isToggled) {
|
||||
VideoLayout.onFilmStripToggled(isToggled);
|
||||
});
|
||||
|
||||
if (!interfaceConfig.filmStripOnly) {
|
||||
APP.xmpp.addListener(XMPPEvents.MESSAGE_RECEIVED, updateChatConversation);
|
||||
APP.xmpp.addListener(XMPPEvents.CHAT_ERROR_RECEIVED, chatAddError);
|
||||
|
@ -425,15 +437,12 @@ UI.start = function (init) {
|
|||
$("#downloadlog").click(function (event) {
|
||||
dump(event.target);
|
||||
});
|
||||
$("#feedbackButton").click(function (event) {
|
||||
Feedback.openFeedbackWindow();
|
||||
});
|
||||
Feedback.init();
|
||||
}
|
||||
else
|
||||
{
|
||||
$("#header").css("display", "none");
|
||||
$("#bottomToolbar").css("display", "none");
|
||||
$("#feedbackButton").css("display", "none");
|
||||
$("#downloadlog").css("display", "none");
|
||||
$("#remoteVideos").css("padding", "0px 0px 18px 0px");
|
||||
$("#remoteVideos").css("right", "0px");
|
||||
|
@ -524,6 +533,8 @@ function onMucJoined(jid, info) {
|
|||
|
||||
|
||||
VideoLayout.mucJoined();
|
||||
|
||||
Toolbar.checkAutoEnableDesktopSharing();
|
||||
}
|
||||
|
||||
function initEtherpad(name) {
|
||||
|
@ -697,6 +708,15 @@ UI.getLargeVideoResource = function () {
|
|||
return VideoLayout.getLargeVideoResource();
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the type of the remote video.
|
||||
* @param jid the jid for the remote video
|
||||
* @returns the video type video or screen.
|
||||
*/
|
||||
UI.getRemoteVideoType = function (jid) {
|
||||
return VideoLayout.getRemoteVideoType(jid);
|
||||
};
|
||||
|
||||
UI.getRoomNode = function () {
|
||||
if (roomNode)
|
||||
return roomNode;
|
||||
|
@ -884,11 +904,11 @@ UI.setVideoMuteButtonsState = function (mute) {
|
|||
}
|
||||
};
|
||||
|
||||
UI.userAvatarChanged = function (resourceJid, thumbUrl, contactListUrl) {
|
||||
VideoLayout.userAvatarChanged(resourceJid, thumbUrl);
|
||||
ContactList.userAvatarChanged(resourceJid, contactListUrl);
|
||||
UI.userAvatarChanged = function (resourceJid, avatarUrl) {
|
||||
VideoLayout.userAvatarChanged(resourceJid, avatarUrl);
|
||||
ContactList.userAvatarChanged(resourceJid, avatarUrl);
|
||||
if(resourceJid === APP.xmpp.myResource())
|
||||
SettingsMenu.changeAvatar(thumbUrl);
|
||||
SettingsMenu.changeAvatar(avatarUrl);
|
||||
};
|
||||
|
||||
UI.setVideoMute = setVideoMute;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* global Strophe, APP, MD5 */
|
||||
/* global Strophe, APP, MD5, config, interfaceConfig */
|
||||
var Settings = require("../../settings/Settings");
|
||||
|
||||
var users = {};
|
||||
|
@ -18,51 +18,51 @@ var Avatar = {
|
|||
}
|
||||
users[jid] = id;
|
||||
}
|
||||
var thumbUrl = this.getThumbUrl(jid);
|
||||
var contactListUrl = this.getContactListUrl(jid);
|
||||
var avatarUrl = this.getAvatarUrl(jid);
|
||||
var resourceJid = Strophe.getResourceFromJid(jid);
|
||||
|
||||
APP.UI.userAvatarChanged(resourceJid, thumbUrl, contactListUrl);
|
||||
APP.UI.userAvatarChanged(resourceJid, avatarUrl);
|
||||
},
|
||||
/**
|
||||
* Returns image URL for the avatar to be displayed on large video area
|
||||
* where current active speaker is presented.
|
||||
* Returns the URL of the image for the avatar of a particular user,
|
||||
* identified by its jid
|
||||
* @param jid
|
||||
* @param jid full MUC jid of the user for whom we want to obtain avatar URL
|
||||
*/
|
||||
getActiveSpeakerUrl: function (jid) {
|
||||
return this.getGravatarUrl(jid, 100);
|
||||
},
|
||||
/**
|
||||
* Returns image URL for the avatar to be displayed on small video thumbnail
|
||||
* @param jid full MUC jid of the user for whom we want to obtain avatar URL
|
||||
*/
|
||||
getThumbUrl: function (jid) {
|
||||
return this.getGravatarUrl(jid, 100);
|
||||
},
|
||||
/**
|
||||
* Returns the URL for the avatar to be displayed as contactlist item
|
||||
* @param jid full MUC jid of the user for whom we want to obtain avatar URL
|
||||
*/
|
||||
getContactListUrl: function (jid) {
|
||||
return this.getGravatarUrl(jid, 30);
|
||||
},
|
||||
getGravatarUrl: function (jid, size) {
|
||||
if (!jid) {
|
||||
console.error("Get gravatar - jid is undefined");
|
||||
return null;
|
||||
}
|
||||
var id = users[jid];
|
||||
if (!id) {
|
||||
console.warn(
|
||||
"No avatar stored yet for " + jid + " - using JID as ID");
|
||||
id = jid;
|
||||
}
|
||||
return 'https://www.gravatar.com/avatar/' +
|
||||
MD5.hexdigest(id.trim().toLowerCase()) +
|
||||
"?d=wavatar&size=" + (size || "30");
|
||||
}
|
||||
getAvatarUrl: function (jid) {
|
||||
if (config.disableThirdPartyRequests) {
|
||||
return 'images/avatar2.png';
|
||||
} else {
|
||||
if (!jid) {
|
||||
console.error("Get avatar - jid is undefined");
|
||||
return null;
|
||||
}
|
||||
var id = users[jid];
|
||||
|
||||
// If the ID looks like an email, we'll use gravatar.
|
||||
// Otherwise, it's a random avatar, and we'll use the configured
|
||||
// URL.
|
||||
var random = !id || id.indexOf('@') < 0;
|
||||
|
||||
if (!id) {
|
||||
console.warn(
|
||||
"No avatar stored yet for " + jid + " - using JID as ID");
|
||||
id = jid;
|
||||
}
|
||||
id = MD5.hexdigest(id.trim().toLowerCase());
|
||||
|
||||
// Default to using gravatar.
|
||||
var urlPref = 'https://www.gravatar.com/avatar/';
|
||||
var urlSuf = "?d=wavatar&size=100";
|
||||
|
||||
if (random && interfaceConfig.RANDOM_AVATAR_URL_PREFIX) {
|
||||
urlPref = interfaceConfig.RANDOM_AVATAR_URL_PREFIX;
|
||||
urlSuf = interfaceConfig.RANDOM_AVATAR_URL_SUFFIX;
|
||||
}
|
||||
|
||||
return urlPref + id + urlSuf;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
module.exports = Avatar;
|
||||
module.exports = Avatar;
|
||||
|
|
|
@ -32,7 +32,7 @@ function updateNumberOfParticipants(delta) {
|
|||
function createAvatar(jid) {
|
||||
var avatar = document.createElement('img');
|
||||
avatar.className = "icon-avatar avatar";
|
||||
avatar.src = Avatar.getContactListUrl(jid);
|
||||
avatar.src = Avatar.getAvatarUrl(jid);
|
||||
|
||||
return avatar;
|
||||
}
|
||||
|
@ -181,11 +181,11 @@ var ContactList = {
|
|||
contactName.html(displayName);
|
||||
},
|
||||
|
||||
userAvatarChanged: function (resourceJid, contactListUrl) {
|
||||
userAvatarChanged: function (resourceJid, avatarUrl) {
|
||||
// set the avatar in the contact list
|
||||
var contact = $('#' + resourceJid + '>img');
|
||||
if (contact && contact.length > 0) {
|
||||
contact.get(0).src = contactListUrl;
|
||||
contact.get(0).src = avatarUrl;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
var PanelToggler = require("../side_pannels/SidePanelToggler");
|
||||
var UIUtil = require("../util/UIUtil");
|
||||
var AnalyticsAdapter = require("../../statistics/AnalyticsAdapter");
|
||||
var UIEvents = require("../../../service/UI/UIEvents");
|
||||
|
||||
var eventEmitter = null;
|
||||
|
||||
var buttonHandlers = {
|
||||
"bottom_toolbar_contact_list": function () {
|
||||
|
@ -27,7 +30,8 @@ var defaultBottomToolbarButtons = {
|
|||
|
||||
|
||||
var BottomToolbar = (function (my) {
|
||||
my.init = function () {
|
||||
my.init = function (emitter) {
|
||||
eventEmitter = emitter;
|
||||
UIUtil.hideDisabledButtons(defaultBottomToolbarButtons);
|
||||
|
||||
for(var k in buttonHandlers)
|
||||
|
@ -45,6 +49,9 @@ var BottomToolbar = (function (my) {
|
|||
my.toggleFilmStrip = function() {
|
||||
var filmstrip = $("#remoteVideos");
|
||||
filmstrip.toggleClass("hidden");
|
||||
|
||||
eventEmitter.emit( UIEvents.FILM_STRIP_TOGGLED,
|
||||
filmstrip.hasClass("hidden"));
|
||||
};
|
||||
|
||||
$(document).bind("remotevideo.resized", function (event, width, height) {
|
||||
|
|
|
@ -151,12 +151,28 @@ function hangup() {
|
|||
}
|
||||
};
|
||||
|
||||
if (Feedback.feedbackScore > 0) {
|
||||
Feedback.openFeedbackWindow();
|
||||
conferenceDispose();
|
||||
if (Feedback.isEnabled())
|
||||
{
|
||||
// If the user has already entered feedback, we'll show the window and
|
||||
// immidiately start the conference dispose timeout.
|
||||
if (Feedback.feedbackScore > 0) {
|
||||
Feedback.openFeedbackWindow();
|
||||
conferenceDispose();
|
||||
|
||||
}
|
||||
// Otherwise we'll wait for user's feedback.
|
||||
else
|
||||
Feedback.openFeedbackWindow(conferenceDispose);
|
||||
}
|
||||
else {
|
||||
conferenceDispose();
|
||||
|
||||
// If the feedback functionality isn't enabled we show a thank you
|
||||
// dialog.
|
||||
APP.UI.messageHandler.openMessageDialog(null, null, null,
|
||||
APP.translation.translateString("dialog.thankYou",
|
||||
{appName:interfaceConfig.APP_NAME}));
|
||||
}
|
||||
else
|
||||
Feedback.openFeedbackWindow(conferenceDispose);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -380,7 +396,7 @@ var Toolbar = (function (my) {
|
|||
* Disables and enables some of the buttons.
|
||||
*/
|
||||
my.setupButtonsFromConfig = function () {
|
||||
if (UIUtil.isButtonEnabled('prezi')) {
|
||||
if (!UIUtil.isButtonEnabled('prezi')) {
|
||||
$("#toolbar_button_prezi").css({display: "none"});
|
||||
}
|
||||
};
|
||||
|
@ -632,13 +648,23 @@ var Toolbar = (function (my) {
|
|||
}
|
||||
};
|
||||
|
||||
// checks whether recording is enabled and whether we have params to start automatically recording
|
||||
// checks whether recording is enabled and whether we have params
|
||||
// to start automatically recording
|
||||
my.checkAutoRecord = function () {
|
||||
if (UIUtil.isButtonEnabled('recording') && config.autoRecord) {
|
||||
toggleRecording(config.autoRecordToken);
|
||||
}
|
||||
};
|
||||
|
||||
// checks whether desktop sharing is enabled and whether
|
||||
// we have params to start automatically sharing
|
||||
my.checkAutoEnableDesktopSharing = function () {
|
||||
if (UIUtil.isButtonEnabled('desktop')
|
||||
&& config.autoEnableDesktopSharing) {
|
||||
APP.desktopsharing.toggleScreenSharing();
|
||||
}
|
||||
};
|
||||
|
||||
// Shows or hides SIP calls button
|
||||
my.showSipCallButton = function (show) {
|
||||
if (APP.xmpp.isSipGatewayEnabled() && UIUtil.isButtonEnabled('sip') && show) {
|
||||
|
|
|
@ -90,6 +90,11 @@ ConnectionIndicator.prototype.generateText = function () {
|
|||
if(this.resolution && this.jid) {
|
||||
var keys = Object.keys(this.resolution);
|
||||
for(var ssrc in this.resolution) {
|
||||
// skip resolutions for ssrc that don't have this info
|
||||
// like receive-only ssrc for FF
|
||||
if(this.resolution[ssrc]
|
||||
&& this.resolution[ssrc].height != -1
|
||||
&& this.resolution[ssrc].width != -1)
|
||||
resolutionValue = this.resolution[ssrc];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -115,7 +115,10 @@ function getDesktopVideoSize(videoWidth,
|
|||
var availableWidth = Math.max(videoWidth, videoSpaceWidth);
|
||||
var availableHeight = Math.max(videoHeight, videoSpaceHeight);
|
||||
|
||||
videoSpaceHeight -= $('#remoteVideos').outerHeight();
|
||||
var filmstrip = $("#remoteVideos");
|
||||
|
||||
if (!filmstrip.hasClass("hidden"))
|
||||
videoSpaceHeight -= filmstrip.outerHeight();
|
||||
|
||||
if (availableWidth / aspectRatio >= videoSpaceHeight)
|
||||
{
|
||||
|
@ -239,7 +242,7 @@ function getCameraVideoSize(videoWidth,
|
|||
function updateActiveSpeakerAvatarSrc() {
|
||||
var avatar = $("#activeSpeakerAvatar")[0];
|
||||
var jid = currentSmallVideo.peerJid;
|
||||
var url = Avatar.getActiveSpeakerUrl(jid);
|
||||
var url = Avatar.getAvatarUrl(jid);
|
||||
if (avatar.src === url)
|
||||
return;
|
||||
if (jid) {
|
||||
|
@ -268,13 +271,7 @@ function changeVideo(isVisible) {
|
|||
|
||||
largeVideoElement.style.transform = flipX ? "scaleX(-1)" : "none";
|
||||
|
||||
var isDesktop = currentSmallVideo.getVideoType() === 'screen';
|
||||
// Change the way we'll be measuring and positioning large video
|
||||
|
||||
getVideoSize = isDesktop ? getDesktopVideoSize : getCameraVideoSize;
|
||||
getVideoPosition = isDesktop ? getDesktopVideoPosition :
|
||||
getCameraVideoPosition;
|
||||
|
||||
LargeVideo.updateVideoSizeAndPosition(currentSmallVideo.getVideoType());
|
||||
|
||||
// Only if the large video is currently visible.
|
||||
if (isVisible) {
|
||||
|
@ -451,10 +448,8 @@ var LargeVideo = {
|
|||
return;
|
||||
if (LargeVideo.isCurrentlyOnLarge(resourceJid))
|
||||
{
|
||||
var isDesktop = newVideoType === 'screen';
|
||||
getVideoSize = isDesktop ? getDesktopVideoSize : getCameraVideoSize;
|
||||
getVideoPosition = isDesktop ? getDesktopVideoPosition
|
||||
: getCameraVideoPosition;
|
||||
LargeVideo.updateVideoSizeAndPosition(newVideoType);
|
||||
|
||||
this.position(null, null, null, null, true);
|
||||
}
|
||||
},
|
||||
|
@ -496,17 +491,23 @@ var LargeVideo = {
|
|||
},
|
||||
/**
|
||||
* Resizes the large html elements.
|
||||
* @param animate boolean property that indicates whether the resize should be animated or not.
|
||||
* @param isChatVisible boolean property that indicates whether the chat area is displayed or not.
|
||||
* If that parameter is null the method will check the chat pannel visibility.
|
||||
* @param completeFunction a function to be called when the video space is resized
|
||||
* @returns {*[]} array with the current width and height values of the largeVideo html element.
|
||||
*
|
||||
* @param animate boolean property that indicates whether the resize should
|
||||
* be animated or not.
|
||||
* @param isSideBarVisible boolean property that indicates whether the chat
|
||||
* area is displayed or not.
|
||||
* If that parameter is null the method will check the chat panel
|
||||
* visibility.
|
||||
* @param completeFunction a function to be called when the video space is
|
||||
* resized
|
||||
* @returns {*[]} array with the current width and height values of the
|
||||
* largeVideo html element.
|
||||
*/
|
||||
resize: function (animate, isVisible, completeFunction) {
|
||||
resize: function (animate, isSideBarVisible, completeFunction) {
|
||||
if(!isEnabled)
|
||||
return;
|
||||
var availableHeight = window.innerHeight;
|
||||
var availableWidth = UIUtil.getAvailableVideoWidth(isVisible);
|
||||
var availableWidth = UIUtil.getAvailableVideoWidth(isSideBarVisible);
|
||||
|
||||
if (availableWidth < 0 || availableHeight < 0) return;
|
||||
|
||||
|
@ -514,7 +515,8 @@ var LargeVideo = {
|
|||
var top = availableHeight / 2 - avatarSize / 4 * 3;
|
||||
$('#activeSpeaker').css('top', top);
|
||||
|
||||
this.VideoLayout.resizeVideoSpace(animate, isVisible, completeFunction);
|
||||
this.VideoLayout
|
||||
.resizeVideoSpace(animate, isSideBarVisible, completeFunction);
|
||||
if(animate) {
|
||||
$('#largeVideoContainer').animate({
|
||||
width: availableWidth,
|
||||
|
@ -530,12 +532,36 @@ var LargeVideo = {
|
|||
}
|
||||
return [availableWidth, availableHeight];
|
||||
},
|
||||
resizeVideoAreaAnimated: function (isVisible, completeFunction) {
|
||||
/**
|
||||
* Resizes the large video.
|
||||
*
|
||||
* @param isSideBarVisible indicating if the side bar is visible
|
||||
* @param completeFunction the callback function to be executed after the
|
||||
* resize
|
||||
*/
|
||||
resizeVideoAreaAnimated: function (isSideBarVisible, completeFunction) {
|
||||
if(!isEnabled)
|
||||
return;
|
||||
var size = this.resize(true, isVisible, completeFunction);
|
||||
var size = this.resize(true, isSideBarVisible, completeFunction);
|
||||
this.position(null, null, size[0], size[1], true);
|
||||
},
|
||||
/**
|
||||
* Updates the video size and position.
|
||||
*
|
||||
* @param videoType the video type indicating if the stream is of type
|
||||
* desktop or web cam
|
||||
*/
|
||||
updateVideoSizeAndPosition: function (videoType) {
|
||||
if (!videoType)
|
||||
videoType = currentSmallVideo.getVideoType();
|
||||
|
||||
var isDesktop = videoType === 'screen';
|
||||
|
||||
// Change the way we'll be measuring and positioning large video
|
||||
getVideoSize = isDesktop ? getDesktopVideoSize : getCameraVideoSize;
|
||||
getVideoPosition = isDesktop ? getDesktopVideoPosition :
|
||||
getCameraVideoPosition;
|
||||
},
|
||||
getResourceJid: function () {
|
||||
return currentSmallVideo ? currentSmallVideo.getResourceJid() : null;
|
||||
},
|
||||
|
|
|
@ -334,6 +334,41 @@ RemoteVideo.prototype.updateRemoteVideoMenu = function (isMuted) {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the Indicator for dominant speaker.
|
||||
*
|
||||
* @param isSpeaker indicates the current indicator state
|
||||
*/
|
||||
RemoteVideo.prototype.updateDominantSpeakerIndicator = function (isSpeaker) {
|
||||
|
||||
if (!this.container) {
|
||||
console.warn( "Unable to set dominant speaker indicator - "
|
||||
+ this.videoSpanId + " does not exist");
|
||||
return;
|
||||
}
|
||||
|
||||
var indicatorSpan
|
||||
= $('#' + this.videoSpanId + '>span.dominantspeakerindicator');
|
||||
|
||||
// If we do not have an indicator for this video.
|
||||
if (indicatorSpan.length <= 0) {
|
||||
indicatorSpan = document.createElement('span');
|
||||
|
||||
indicatorSpan.innerHTML
|
||||
= "<i id='speakerindicatoricon' class='fa fa-bullhorn'></i>";
|
||||
indicatorSpan.className = 'dominantspeakerindicator';
|
||||
|
||||
$('#' + this.videoSpanId)[0].appendChild(indicatorSpan);
|
||||
|
||||
// adds a tooltip
|
||||
UIUtils.setTooltip(indicatorSpan, "speaker", "left");
|
||||
APP.translation.translateElement($(indicatorSpan));
|
||||
}
|
||||
|
||||
$(indicatorSpan).css("visibility", isSpeaker ? "visible" : "hidden");
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the display name for the given video span id.
|
||||
*/
|
||||
|
|
|
@ -320,9 +320,26 @@ SmallVideo.prototype.selectVideoElement = function () {
|
|||
if (!RTCBrowserType.isTemasysPluginUsed()) {
|
||||
return $('#' + this.videoSpanId).find(videoElem);
|
||||
} else {
|
||||
return $('#' + this.videoSpanId +
|
||||
(this.isLocal ? '>>' : '>') +
|
||||
videoElem + '>param[value="video"]').parent();
|
||||
var matching = $('#' + this.videoSpanId +
|
||||
(this.isLocal ? '>>' : '>') +
|
||||
videoElem + '>param[value="video"]');
|
||||
if (matching.length < 2) {
|
||||
return matching.parent();
|
||||
}
|
||||
|
||||
// there are 2 video objects from FF
|
||||
// object with id which ends with '_default' (like 'remoteVideo_default')
|
||||
// doesn't contain video, so we ignore it
|
||||
for (var i = 0; i < matching.length; i += 1) {
|
||||
var el = matching[i].parentNode;
|
||||
|
||||
// check id suffix
|
||||
if (el.id.substr(-8) !== '_default') {
|
||||
return $(el);
|
||||
}
|
||||
}
|
||||
|
||||
return $([]);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -352,7 +369,7 @@ SmallVideo.prototype.showAvatar = function (show) {
|
|||
if (!this.hasAvatar) {
|
||||
if (this.peerJid) {
|
||||
// Init avatar
|
||||
this.avatarChanged(Avatar.getThumbUrl(this.peerJid));
|
||||
this.avatarChanged(Avatar.getAvatarUrl(this.peerJid));
|
||||
} else {
|
||||
console.error("Unable to init avatar - no peerjid", this);
|
||||
return;
|
||||
|
@ -406,4 +423,4 @@ SmallVideo.prototype.avatarChanged = function (thumbUrl) {
|
|||
}
|
||||
};
|
||||
|
||||
module.exports = SmallVideo;
|
||||
module.exports = SmallVideo;
|
||||
|
|
|
@ -191,6 +191,15 @@ var VideoLayout = (function (my) {
|
|||
return LargeVideo.getResourceJid();
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the type of the remote video.
|
||||
* @param jid the jid for the remote video
|
||||
* @returns the video type video or screen.
|
||||
*/
|
||||
my.getRemoteVideoType = function (jid) {
|
||||
return remoteVideoTypes[jid];
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when large video update is finished
|
||||
* @param currentSmallVideo small video currently displayed on large video
|
||||
|
@ -214,7 +223,8 @@ var VideoLayout = (function (my) {
|
|||
my.handleVideoThumbClicked = function(noPinnedEndpointChangedEvent,
|
||||
resourceJid) {
|
||||
if(focusedVideoResourceJid) {
|
||||
var oldSmallVideo = VideoLayout.getSmallVideo(focusedVideoResourceJid);
|
||||
var oldSmallVideo
|
||||
= VideoLayout.getSmallVideo(focusedVideoResourceJid);
|
||||
if (oldSmallVideo && !interfaceConfig.filmStripOnly)
|
||||
oldSmallVideo.focus(false);
|
||||
}
|
||||
|
@ -400,7 +410,8 @@ var VideoLayout = (function (my) {
|
|||
|
||||
if(animate) {
|
||||
$('#remoteVideos').animate({
|
||||
height: height + 2 // adds 2 px because of small video 1px border
|
||||
// adds 2 px because of small video 1px border
|
||||
height: height + 2
|
||||
},
|
||||
{
|
||||
queue: false,
|
||||
|
@ -425,7 +436,8 @@ var VideoLayout = (function (my) {
|
|||
} else {
|
||||
// size videos so that while keeping AR and max height, we have a
|
||||
// nice fit
|
||||
$('#remoteVideos').height(height + 2);// adds 2 px because of small video 1px border
|
||||
// adds 2 px because of small video 1px border
|
||||
$('#remoteVideos').height(height + 2);
|
||||
$('#remoteVideos>span').width(width);
|
||||
$('#remoteVideos>span').height(height);
|
||||
|
||||
|
@ -439,10 +451,10 @@ var VideoLayout = (function (my) {
|
|||
* @param videoSpaceWidth the width of the video space
|
||||
*/
|
||||
my.calculateThumbnailSize = function (videoSpaceWidth) {
|
||||
// Calculate the available height, which is the inner window height minus
|
||||
// 39px for the header minus 2px for the delimiter lines on the top and
|
||||
// bottom of the large video, minus the 36px space inside the remoteVideos
|
||||
// container used for highlighting shadow.
|
||||
// Calculate the available height, which is the inner window height
|
||||
// minus 39px for the header minus 2px for the delimiter lines on the
|
||||
// top and bottom of the large video, minus the 36px space inside the
|
||||
// remoteVideos container used for highlighting shadow.
|
||||
var availableHeight = 100;
|
||||
|
||||
var numvids = $('#remoteVideos>span:visible').length;
|
||||
|
@ -458,7 +470,11 @@ var VideoLayout = (function (my) {
|
|||
var availableWidth = availableWinWidth / numvids;
|
||||
var aspectRatio = 16.0 / 9.0;
|
||||
var maxHeight = Math.min(160, availableHeight);
|
||||
availableHeight = Math.min(maxHeight, availableWidth / aspectRatio, window.innerHeight - 18);
|
||||
availableHeight
|
||||
= Math.min( maxHeight,
|
||||
availableWidth / aspectRatio,
|
||||
window.innerHeight - 18);
|
||||
|
||||
if (availableHeight < availableWidth / aspectRatio) {
|
||||
availableWidth = Math.floor(availableHeight * aspectRatio);
|
||||
}
|
||||
|
@ -598,16 +614,14 @@ var VideoLayout = (function (my) {
|
|||
var members = APP.xmpp.getMembers();
|
||||
// Update the current dominant speaker.
|
||||
if (resourceJid !== currentDominantSpeaker) {
|
||||
var currentJID = APP.xmpp.findJidFromResource(currentDominantSpeaker);
|
||||
var newJID = APP.xmpp.findJidFromResource(resourceJid);
|
||||
if (currentDominantSpeaker && (!members || !members[currentJID] ||
|
||||
!members[currentJID].displayName) && remoteVideo) {
|
||||
remoteVideo.setDisplayName(null);
|
||||
}
|
||||
if (resourceJid && (!members || !members[newJID] ||
|
||||
!members[newJID].displayName) && remoteVideo) {
|
||||
remoteVideo.setDisplayName(null,
|
||||
interfaceConfig.DEFAULT_DOMINANT_SPEAKER_DISPLAY_NAME);
|
||||
if (remoteVideo) {
|
||||
remoteVideo.updateDominantSpeakerIndicator(true);
|
||||
// let's remove the indications from the remote video if any
|
||||
var oldSpeakerRemoteVideo
|
||||
= remoteVideos[currentDominantSpeaker];
|
||||
if (oldSpeakerRemoteVideo) {
|
||||
oldSpeakerRemoteVideo.updateDominantSpeakerIndicator(false);
|
||||
}
|
||||
}
|
||||
currentDominantSpeaker = resourceJid;
|
||||
} else {
|
||||
|
@ -683,6 +697,16 @@ var VideoLayout = (function (my) {
|
|||
$('#remoteVideos>span').each(function( index, element ) {
|
||||
var resourceJid = VideoLayout.getPeerContainerResourceJid(element);
|
||||
|
||||
// We do not want to process any logic for our own(local) video
|
||||
// because the local participant is never in the lastN set.
|
||||
// The code of this function might detect that the local participant
|
||||
// has been dropped out of the lastN set and will update the large
|
||||
// video
|
||||
// Detected from avatar tests, where lastN event override
|
||||
// local video pinning
|
||||
if(resourceJid == APP.xmpp.myResource())
|
||||
return;
|
||||
|
||||
var isReceived = true;
|
||||
if (resourceJid &&
|
||||
lastNEndpoints.indexOf(resourceJid) < 0 &&
|
||||
|
@ -882,6 +906,17 @@ var VideoLayout = (function (my) {
|
|||
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the video size and position when the film strip is toggled.
|
||||
*
|
||||
* @param isToggled indicates if the film strip is toggled or not. True
|
||||
* would mean that the film strip is hidden, false would mean it's shown
|
||||
*/
|
||||
my.onFilmStripToggled = function(isToggled) {
|
||||
LargeVideo.updateVideoSizeAndPosition();
|
||||
LargeVideo.position(null, null, null, null, true);
|
||||
};
|
||||
|
||||
my.showMore = function (jid) {
|
||||
if (jid === 'local') {
|
||||
localVideoThumbnail.connectionIndicator.showMore();
|
||||
|
@ -914,21 +949,27 @@ var VideoLayout = (function (my) {
|
|||
};
|
||||
|
||||
/**
|
||||
* Resizes the video area
|
||||
* Resizes the video area.
|
||||
*
|
||||
* @param isSideBarVisible indicates if the side bar is currently visible
|
||||
* @param callback a function to be called when the video space is
|
||||
* resized.
|
||||
*/
|
||||
my.resizeVideoArea = function(isVisible, callback) {
|
||||
LargeVideo.resizeVideoAreaAnimated(isVisible, callback);
|
||||
my.resizeVideoArea = function(isSideBarVisible, callback) {
|
||||
LargeVideo.resizeVideoAreaAnimated(isSideBarVisible, callback);
|
||||
VideoLayout.resizeThumbnails(true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Resizes the #videospace html element
|
||||
* @param animate boolean property that indicates whether the resize should be animated or not.
|
||||
* @param isChatVisible boolean property that indicates whether the chat area is displayed or not.
|
||||
* If that parameter is null the method will check the chat pannel visibility.
|
||||
* @param completeFunction a function to be called when the video space is resized
|
||||
* @param animate boolean property that indicates whether the resize should
|
||||
* be animated or not.
|
||||
* @param isChatVisible boolean property that indicates whether the chat
|
||||
* area is displayed or not.
|
||||
* If that parameter is null the method will check the chat panel
|
||||
* visibility.
|
||||
* @param completeFunction a function to be called when the video space
|
||||
* is resized.
|
||||
*/
|
||||
my.resizeVideoSpace = function (animate, isChatVisible, completeFunction) {
|
||||
var availableHeight = window.innerHeight;
|
||||
|
@ -964,14 +1005,14 @@ var VideoLayout = (function (my) {
|
|||
}
|
||||
};
|
||||
|
||||
my.userAvatarChanged = function(resourceJid, thumbUrl) {
|
||||
my.userAvatarChanged = function(resourceJid, avatarUrl) {
|
||||
var smallVideo = VideoLayout.getSmallVideo(resourceJid);
|
||||
if(smallVideo)
|
||||
smallVideo.avatarChanged(thumbUrl);
|
||||
smallVideo.avatarChanged(avatarUrl);
|
||||
else
|
||||
console.warn(
|
||||
"Missed avatar update - no small video yet for " + resourceJid);
|
||||
LargeVideo.updateAvatar(resourceJid, thumbUrl);
|
||||
LargeVideo.updateAvatar(resourceJid, avatarUrl);
|
||||
};
|
||||
|
||||
my.createEtherpadIframe = function(src, onloadHandler)
|
||||
|
@ -998,7 +1039,8 @@ var VideoLayout = (function (my) {
|
|||
LargeVideo.enableVideoProblemFilter(true);
|
||||
var reconnectingKey = "connection.RECONNECTING";
|
||||
$('#videoConnectionMessage').attr("data-i18n", reconnectingKey);
|
||||
$('#videoConnectionMessage').text(APP.translation.translateString(reconnectingKey));
|
||||
$('#videoConnectionMessage')
|
||||
.text(APP.translation.translateString(reconnectingKey));
|
||||
$('#videoConnectionMessage').css({display: "block"});
|
||||
};
|
||||
|
||||
|
|
|
@ -5,20 +5,35 @@ var jsSHA = require('jssha');
|
|||
var io = require('socket.io-client');
|
||||
var callStats = null;
|
||||
|
||||
// getUserMedia calls happen before CallStats init
|
||||
// so if there are any getUserMedia errors, we store them in this array
|
||||
/**
|
||||
* @const
|
||||
* @see http://www.callstats.io/api/#enumeration-of-wrtcfuncnames
|
||||
*/
|
||||
var wrtcFuncNames = {
|
||||
createOffer: "createOffer",
|
||||
createAnswer: "createAnswer",
|
||||
setLocalDescription: "setLocalDescription",
|
||||
setRemoteDescription: "setRemoteDescription",
|
||||
addIceCandidate: "addIceCandidate",
|
||||
getUserMedia: "getUserMedia"
|
||||
};
|
||||
|
||||
// some errors may happen before CallStats init
|
||||
// in this case we accumulate them in this array
|
||||
// and send them to callstats on init
|
||||
var pendingUserMediaErrors = [];
|
||||
var pendingErrors = [];
|
||||
|
||||
function initCallback (err, msg) {
|
||||
console.log("Initializing Status: err="+err+" msg="+msg);
|
||||
console.log("CallStats Status: err=" + err + " msg=" + msg);
|
||||
}
|
||||
|
||||
var callStatsIntegrationEnabled = config.callStatsID && config.callStatsSecret;
|
||||
|
||||
var CallStats = {
|
||||
init: function (jingleSession) {
|
||||
|
||||
if(!config.callStatsID || !config.callStatsSecret || callStats !== null)
|
||||
if(!callStatsIntegrationEnabled || callStats !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
callStats = new callstats($, io, jsSHA);
|
||||
|
||||
|
@ -44,12 +59,24 @@ var CallStats = {
|
|||
this.confID,
|
||||
this.pcCallback.bind(this));
|
||||
|
||||
// notify callstats about getUserMedia failures if there were any
|
||||
if (pendingUserMediaErrors.length) {
|
||||
pendingUserMediaErrors.forEach(this.sendGetUserMediaFailed, this);
|
||||
pendingUserMediaErrors.length = 0;
|
||||
// notify callstats about failures if there were any
|
||||
if (pendingErrors.length) {
|
||||
pendingErrors.forEach(function (error) {
|
||||
this._reportError(error.type, error.error, error.pc);
|
||||
}, this);
|
||||
pendingErrors.length = 0;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Returns true if the callstats integration is enabled, otherwise returns
|
||||
* false.
|
||||
*
|
||||
* @returns true if the callstats integration is enabled, otherwise returns
|
||||
* false.
|
||||
*/
|
||||
isEnabled: function() {
|
||||
return callStatsIntegrationEnabled;
|
||||
},
|
||||
pcCallback: function (err, msg) {
|
||||
if (!callStats) {
|
||||
return;
|
||||
|
@ -108,6 +135,26 @@ var CallStats = {
|
|||
callStats.sendUserFeedback(
|
||||
this.confID, feedbackJSON);
|
||||
},
|
||||
/**
|
||||
* Reports an error to callstats.
|
||||
*
|
||||
* @param type the type of the error, which will be one of the wrtcFuncNames
|
||||
* @param e the error
|
||||
* @param pc the peerconnection
|
||||
* @private
|
||||
*/
|
||||
_reportError: function (type, e, pc) {
|
||||
if (callStats) {
|
||||
callStats.reportError(pc, this.confID, type, e);
|
||||
} else if (callStatsIntegrationEnabled) {
|
||||
pendingErrors.push({
|
||||
type: type,
|
||||
error: e,
|
||||
pc: pc
|
||||
});
|
||||
}
|
||||
// else just ignore it
|
||||
},
|
||||
|
||||
/**
|
||||
* Notifies CallStats that getUserMedia failed.
|
||||
|
@ -115,78 +162,57 @@ var CallStats = {
|
|||
* @param {Error} e error to send
|
||||
*/
|
||||
sendGetUserMediaFailed: function (e) {
|
||||
if(!callStats) {
|
||||
pendingUserMediaErrors.push(e);
|
||||
return;
|
||||
}
|
||||
callStats.reportError(this.peerconnection, this.confID,
|
||||
callStats.webRTCFunctions.getUserMedia, e);
|
||||
this._reportError(wrtcFuncNames.getUserMedia, e, null);
|
||||
},
|
||||
|
||||
/**
|
||||
* Notifies CallStats that peer connection failed to create offer.
|
||||
*
|
||||
* @param {Error} e error to send
|
||||
* @param {RTCPeerConnection} pc connection on which failure occured.
|
||||
*/
|
||||
sendCreateOfferFailed: function (e) {
|
||||
if(!callStats) {
|
||||
return;
|
||||
}
|
||||
callStats.reportError(this.peerconnection, this.confID,
|
||||
callStats.webRTCFunctions.createOffer, e);
|
||||
sendCreateOfferFailed: function (e, pc) {
|
||||
this._reportError(wrtcFuncNames.createOffer, e, pc);
|
||||
},
|
||||
|
||||
/**
|
||||
* Notifies CallStats that peer connection failed to create answer.
|
||||
*
|
||||
* @param {Error} e error to send
|
||||
* @param {RTCPeerConnection} pc connection on which failure occured.
|
||||
*/
|
||||
sendCreateAnswerFailed: function (e) {
|
||||
if(!callStats) {
|
||||
return;
|
||||
}
|
||||
callStats.reportError(this.peerconnection, this.confID,
|
||||
callStats.webRTCFunctions.createAnswer, e);
|
||||
sendCreateAnswerFailed: function (e, pc) {
|
||||
this._reportError(wrtcFuncNames.createAnswer, e, pc);
|
||||
},
|
||||
|
||||
/**
|
||||
* Notifies CallStats that peer connection failed to set local description.
|
||||
*
|
||||
* @param {Error} e error to send
|
||||
* @param {RTCPeerConnection} pc connection on which failure occured.
|
||||
*/
|
||||
sendSetLocalDescFailed: function (e) {
|
||||
if(!callStats) {
|
||||
return;
|
||||
}
|
||||
callStats.reportError(this.peerconnection, this.confID,
|
||||
callStats.webRTCFunctions.setLocalDescription, e);
|
||||
sendSetLocalDescFailed: function (e, pc) {
|
||||
this._reportError(wrtcFuncNames.setLocalDescription, e, pc);
|
||||
},
|
||||
|
||||
/**
|
||||
* Notifies CallStats that peer connection failed to set remote description.
|
||||
*
|
||||
* @param {Error} e error to send
|
||||
* @param {RTCPeerConnection} pc connection on which failure occured.
|
||||
*/
|
||||
sendSetRemoteDescFailed: function (e) {
|
||||
if(!callStats) {
|
||||
return;
|
||||
}
|
||||
callStats.reportError(
|
||||
this.peerconnection, this.confID,
|
||||
callStats.webRTCFunctions.setRemoteDescription, e);
|
||||
sendSetRemoteDescFailed: function (e, pc) {
|
||||
this._reportError(wrtcFuncNames.setRemoteDescription, e, pc);
|
||||
},
|
||||
|
||||
/**
|
||||
* Notifies CallStats that peer connection failed to add ICE candidate.
|
||||
*
|
||||
* @param {Error} e error to send
|
||||
* @param {RTCPeerConnection} pc connection on which failure occured.
|
||||
*/
|
||||
sendAddIceCandidateFailed: function (e) {
|
||||
if(!callStats) {
|
||||
return;
|
||||
}
|
||||
callStats.reportError(this.peerconnection, this.confID,
|
||||
callStats.webRTCFunctions.addIceCandidate, e);
|
||||
sendAddIceCandidateFailed: function (e, pc) {
|
||||
this._reportError(wrtcFuncNames.addIceCandidate, e, pc);
|
||||
}
|
||||
};
|
||||
module.exports = CallStats;
|
||||
|
|
|
@ -113,25 +113,46 @@ var statistics = {
|
|||
APP.RTC.addListener(RTCEvents.GET_USER_MEDIA_FAILED, function (e) {
|
||||
CallStats.sendGetUserMediaFailed(e);
|
||||
});
|
||||
APP.xmpp.addListener(RTCEvents.CREATE_OFFER_FAILED, function (e) {
|
||||
CallStats.sendCreateOfferFailed(e);
|
||||
APP.xmpp.addListener(RTCEvents.CREATE_OFFER_FAILED, function (e, pc) {
|
||||
CallStats.sendCreateOfferFailed(e, pc);
|
||||
});
|
||||
APP.xmpp.addListener(RTCEvents.CREATE_ANSWER_FAILED, function (e) {
|
||||
CallStats.sendCreateAnswerFailed(e);
|
||||
APP.xmpp.addListener(RTCEvents.CREATE_ANSWER_FAILED, function (e, pc) {
|
||||
CallStats.sendCreateAnswerFailed(e, pc);
|
||||
});
|
||||
APP.xmpp.addListener(
|
||||
RTCEvents.SET_LOCAL_DESCRIPTION_FAILED,
|
||||
function (e) {
|
||||
CallStats.sendSetLocalDescFailed(e);
|
||||
});
|
||||
function (e, pc) {
|
||||
CallStats.sendSetLocalDescFailed(e, pc);
|
||||
}
|
||||
);
|
||||
APP.xmpp.addListener(
|
||||
RTCEvents.SET_REMOTE_DESCRIPTION_FAILED,
|
||||
function (e) {
|
||||
CallStats.sendSetRemoteDescFailed(e);
|
||||
});
|
||||
APP.xmpp.addListener(RTCEvents.ADD_ICE_CANDIDATE_FAILED, function (e) {
|
||||
CallStats.sendAddIceCandidateFailed(e);
|
||||
});
|
||||
function (e, pc) {
|
||||
CallStats.sendSetRemoteDescFailed(e, pc);
|
||||
}
|
||||
);
|
||||
APP.xmpp.addListener(
|
||||
RTCEvents.ADD_ICE_CANDIDATE_FAILED,
|
||||
function (e, pc) {
|
||||
CallStats.sendAddIceCandidateFailed(e, pc);
|
||||
}
|
||||
);
|
||||
},
|
||||
/**
|
||||
* Obtains audio level reported in the stats for specified peer.
|
||||
* @param peerJid full MUC jid of the user for whom we want to obtain last
|
||||
* audio level.
|
||||
* @param ssrc the SSRC of audio stream for which we want to obtain audio
|
||||
* level.
|
||||
* @returns {*} a float form 0 to 1 that represents current audio level or
|
||||
* <tt>null</tt> if for any reason the value is not available
|
||||
* at this time.
|
||||
*/
|
||||
getPeerSSRCAudioLevel: function (peerJid, ssrc) {
|
||||
|
||||
var peerStats = rtpStats.jid2stats[peerJid];
|
||||
|
||||
return peerStats ? peerStats.ssrc2AudioLevel[ssrc] : null;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ var pluralNouns = [
|
|||
"Priests", "Rats", "Reptiles", "Reptilians", "Rhinos", "Seagulls", "Sheep",
|
||||
"Siblings", "Snakes", "Spaghetti", "Spiders", "Squid", "Squirrels",
|
||||
"Stars", "Students", "Teachers", "Tigers", "Tomatoes", "Trees", "Vampires",
|
||||
"Vegetables", "Viruses", "Vulcans", "Warewolves", "Weasels", "Whales",
|
||||
"Vegetables", "Viruses", "Vulcans", "Weasels", "Werewolves", "Whales",
|
||||
"Witches", "Wizards", "Wolves", "Workers", "Worms", "Zebras"
|
||||
];
|
||||
//var places = [
|
||||
|
|
|
@ -40,7 +40,11 @@ function JingleSessionPC(me, sid, connection, service, eventEmitter) {
|
|||
this.switchstreams = false;
|
||||
|
||||
this.wait = true;
|
||||
this.localStreamsSSRC = null;
|
||||
/**
|
||||
* A map that stores SSRCs of local streams
|
||||
* @type {{}} maps media type('audio' or 'video') to SSRC number
|
||||
*/
|
||||
this.localStreamsSSRC = {};
|
||||
this.ssrcOwners = {};
|
||||
this.ssrcVideoTypes = {};
|
||||
this.eventEmitter = eventEmitter;
|
||||
|
@ -256,8 +260,7 @@ JingleSessionPC.prototype.accept = function () {
|
|||
// FIXME why do we generate session-accept in 3 different places ?
|
||||
prsdp.toJingle(
|
||||
accept,
|
||||
this.initiator == this.me ? 'initiator' : 'responder',
|
||||
this.localStreamsSSRC);
|
||||
this.initiator == this.me ? 'initiator' : 'responder');
|
||||
var sdp = this.peerconnection.localDescription.sdp;
|
||||
while (SDPUtil.find_line(sdp, 'a=inactive')) {
|
||||
// FIXME: change any inactive to sendrecv or whatever they were originally
|
||||
|
@ -484,8 +487,7 @@ JingleSessionPC.prototype.createdOffer = function (sdp) {
|
|||
sid: this.sid});
|
||||
self.localSDP.toJingle(
|
||||
init,
|
||||
this.initiator == this.me ? 'initiator' : 'responder',
|
||||
this.localStreamsSSRC);
|
||||
this.initiator == this.me ? 'initiator' : 'responder');
|
||||
|
||||
SSRCReplacement.processSessionInit(init);
|
||||
|
||||
|
@ -555,6 +557,15 @@ JingleSessionPC.prototype.getSsrcOwner = function (ssrc) {
|
|||
return this.ssrcOwners[ssrc];
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the SSRC of local audio stream.
|
||||
* @param mediaType 'audio' or 'video' media type
|
||||
* @returns {*} the SSRC number of local audio or video stream.
|
||||
*/
|
||||
JingleSessionPC.prototype.getLocalSSRC = function (mediaType) {
|
||||
return this.localStreamsSSRC[mediaType];
|
||||
};
|
||||
|
||||
JingleSessionPC.prototype.setRemoteDescription = function (elem, desctype) {
|
||||
this.remoteSDP = new SDP('');
|
||||
if (config.webrtcIceTcpDisable) {
|
||||
|
@ -720,7 +731,7 @@ JingleSessionPC.prototype.addIceCandidate = function (elem) {
|
|||
self.peerconnection.addIceCandidate(candidate);
|
||||
} catch (e) {
|
||||
console.error('addIceCandidate failed', e.toString(), line);
|
||||
self.eventEmitter.emit(RTCEvents.ADD_ICE_CANDIDATE_FAILED, err);
|
||||
self.eventEmitter.emit(RTCEvents.ADD_ICE_CANDIDATE_FAILED, err, self.peerconnection);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -1427,13 +1438,22 @@ JingleSessionPC.prototype.setLocalDescription = function () {
|
|||
'ssrc': ssrc.id,
|
||||
'type': media.type
|
||||
});
|
||||
});
|
||||
}
|
||||
else if(self.localStreamsSSRC && self.localStreamsSSRC[media.type])
|
||||
{
|
||||
newssrcs.push({
|
||||
'ssrc': self.localStreamsSSRC[media.type],
|
||||
'type': media.type
|
||||
|
||||
// In FF we have multiple local SSRC per media type, 1 that is
|
||||
// sending and some that are receive only. The
|
||||
// localStramsSSRC['audio'] needs to be set to the one that is
|
||||
// sending! We find it by checking for an msid. Note that
|
||||
// self.localStreamsSSRC is primarily used by the tests atm.
|
||||
var isSending = media.ssrcs.some(function (ssrc$1) {
|
||||
return ssrc$1.id == ssrc.id && ssrc$1.attribute == 'msid';
|
||||
});
|
||||
|
||||
if (!isSending) {
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME allows for only one SSRC per media type
|
||||
self.localStreamsSSRC[media.type] = ssrc.id;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -130,7 +130,9 @@ var storeLocalVideoSSRC = function (jingleIq) {
|
|||
function generateRecvonlySSRC() {
|
||||
//
|
||||
localRecvOnlySSRC =
|
||||
Math.random().toString(10).substring(2, 11);
|
||||
localVideoSSRC ?
|
||||
localVideoSSRC : Math.random().toString(10).substring(2, 11);
|
||||
|
||||
localRecvOnlyCName =
|
||||
Math.random().toString(36).substring(2);
|
||||
console.info(
|
||||
|
@ -175,44 +177,45 @@ var LocalSSRCReplacement = {
|
|||
|
||||
// IF we have local video SSRC stored make sure it is replaced
|
||||
// with old SSRC
|
||||
if (localVideoSSRC) {
|
||||
var newSdp = new SDP(localDescription.sdp);
|
||||
if (newSdp.media[1].indexOf("a=ssrc:") !== -1 &&
|
||||
!newSdp.containsSSRC(localVideoSSRC)) {
|
||||
// Get new video SSRC
|
||||
var map = newSdp.getMediaSsrcMap();
|
||||
var videoPart = map[1];
|
||||
var videoSSRCs = videoPart.ssrcs;
|
||||
var newSSRC = Object.keys(videoSSRCs)[0];
|
||||
var sdp = new SDP(localDescription.sdp);
|
||||
if (sdp.media.length < 2)
|
||||
return;
|
||||
|
||||
console.info(
|
||||
"Replacing new video SSRC: " + newSSRC +
|
||||
" with " + localVideoSSRC);
|
||||
if (localVideoSSRC && sdp.media[1].indexOf("a=ssrc:") !== -1 &&
|
||||
!sdp.containsSSRC(localVideoSSRC)) {
|
||||
|
||||
localDescription.sdp =
|
||||
newSdp.raw.replace(
|
||||
new RegExp('a=ssrc:' + newSSRC, 'g'),
|
||||
'a=ssrc:' + localVideoSSRC);
|
||||
}
|
||||
} else {
|
||||
console.info("Does not contain: "
|
||||
+ localVideoSSRC + "---" + sdp.media[1]);
|
||||
|
||||
// Get new video SSRC
|
||||
var map = sdp.getMediaSsrcMap();
|
||||
var videoPart = map[1];
|
||||
var videoSSRCs = videoPart.ssrcs;
|
||||
var newSSRC = Object.keys(videoSSRCs)[0];
|
||||
|
||||
console.info(
|
||||
"Replacing new video SSRC: " + newSSRC +
|
||||
" with " + localVideoSSRC);
|
||||
|
||||
localDescription.sdp =
|
||||
sdp.raw.replace(
|
||||
new RegExp('a=ssrc:' + newSSRC, 'g'),
|
||||
'a=ssrc:' + localVideoSSRC);
|
||||
}
|
||||
else if (sdp.media[1].indexOf('a=ssrc:') === -1 &&
|
||||
sdp.media[1].indexOf('a=recvonly') !== -1) {
|
||||
// Make sure we have any SSRC for recvonly video stream
|
||||
var sdp = new SDP(localDescription.sdp);
|
||||
|
||||
if (sdp.media[1] && sdp.media[1].indexOf('a=ssrc:') === -1 &&
|
||||
sdp.media[1].indexOf('a=recvonly') !== -1) {
|
||||
|
||||
if (!localRecvOnlySSRC) {
|
||||
generateRecvonlySSRC();
|
||||
}
|
||||
|
||||
console.info('No SSRC in video recvonly stream' +
|
||||
' - adding SSRC: ' + localRecvOnlySSRC);
|
||||
|
||||
sdp.media[1] += 'a=ssrc:' + localRecvOnlySSRC +
|
||||
' cname:' + localRecvOnlyCName + '\r\n';
|
||||
|
||||
localDescription.sdp = sdp.session + sdp.media.join('');
|
||||
if (!localRecvOnlySSRC) {
|
||||
generateRecvonlySSRC();
|
||||
}
|
||||
|
||||
console.info('No SSRC in video recvonly stream' +
|
||||
' - adding SSRC: ' + localRecvOnlySSRC);
|
||||
|
||||
sdp.media[1] += 'a=ssrc:' + localRecvOnlySSRC +
|
||||
' cname:' + localRecvOnlyCName + '\r\n';
|
||||
|
||||
localDescription.sdp = sdp.session + sdp.media.join('');
|
||||
}
|
||||
return localDescription;
|
||||
},
|
||||
|
|
|
@ -57,6 +57,7 @@ SDP.prototype.getMediaSsrcMap = function() {
|
|||
});
|
||||
tmp = SDPUtil.find_lines(self.media[mediaindex], 'a=ssrc-group:');
|
||||
tmp.forEach(function(line){
|
||||
var idx = line.indexOf(' ');
|
||||
var semantics = line.substr(0, idx).substr(13);
|
||||
var ssrcs = line.substr(14 + semantics.length).split(' ');
|
||||
if (ssrcs.length) {
|
||||
|
@ -74,16 +75,18 @@ SDP.prototype.getMediaSsrcMap = function() {
|
|||
* @param ssrc the ssrc to check.
|
||||
* @returns {boolean} <tt>true</tt> if this SDP contains given SSRC.
|
||||
*/
|
||||
SDP.prototype.containsSSRC = function(ssrc) {
|
||||
SDP.prototype.containsSSRC = function (ssrc) {
|
||||
// FIXME this code is really strange - improve it if you can
|
||||
var medias = this.getMediaSsrcMap();
|
||||
Object.keys(medias).forEach(function(mediaindex){
|
||||
var media = medias[mediaindex];
|
||||
//console.log("Check", channel, ssrc);
|
||||
if(Object.keys(media.ssrcs).indexOf(ssrc) != -1){
|
||||
return true;
|
||||
var result = false;
|
||||
Object.keys(medias).forEach(function (mediaindex) {
|
||||
if (result)
|
||||
return;
|
||||
if (medias[mediaindex].ssrcs[ssrc]) {
|
||||
result = true;
|
||||
}
|
||||
});
|
||||
return false;
|
||||
return result;
|
||||
};
|
||||
|
||||
// remove iSAC and CN from SDP
|
||||
|
@ -138,7 +141,7 @@ SDP.prototype.removeMediaLines = function(mediaindex, prefix) {
|
|||
};
|
||||
|
||||
// add content's to a jingle element
|
||||
SDP.prototype.toJingle = function (elem, thecreator, ssrcs) {
|
||||
SDP.prototype.toJingle = function (elem, thecreator) {
|
||||
// console.log("SSRC" + ssrcs["audio"] + " - " + ssrcs["video"]);
|
||||
var i, j, k, mline, ssrc, rtpmap, tmp, lines;
|
||||
// new bundle plan
|
||||
|
@ -165,11 +168,7 @@ SDP.prototype.toJingle = function (elem, thecreator, ssrcs) {
|
|||
if (SDPUtil.find_line(this.media[i], 'a=ssrc:')) {
|
||||
ssrc = SDPUtil.find_line(this.media[i], 'a=ssrc:').substring(7).split(' ')[0]; // take the first
|
||||
} else {
|
||||
if(ssrcs && ssrcs[mline.media]) {
|
||||
ssrc = ssrcs[mline.media];
|
||||
} else {
|
||||
ssrc = false;
|
||||
}
|
||||
ssrc = false;
|
||||
}
|
||||
|
||||
elem.c('content', {creator: thecreator, name: mline.media});
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
/* jshint -W101 */
|
||||
var RTCBrowserType = require("../RTC/RTCBrowserType");
|
||||
|
||||
var SDPUtil = {
|
||||
filter_special_chars: function (text) {
|
||||
return text.replace(/[\\\/\{,\}\+]/g, "");
|
||||
|
@ -311,7 +313,14 @@ var SDPUtil = {
|
|||
line += ' ';
|
||||
line += cand.getAttribute('component');
|
||||
line += ' ';
|
||||
line += cand.getAttribute('protocol'); //.toUpperCase(); // chrome M23 doesn't like this
|
||||
|
||||
var protocol = cand.getAttribute('protocol');
|
||||
// use tcp candidates for FF
|
||||
if (RTCBrowserType.isFirefox() && protocol.toLowerCase() == 'ssltcp') {
|
||||
protocol = 'tcp';
|
||||
}
|
||||
|
||||
line += protocol; //.toUpperCase(); // chrome M23 doesn't like this
|
||||
line += ' ';
|
||||
line += cand.getAttribute('priority');
|
||||
line += ' ';
|
||||
|
@ -338,7 +347,7 @@ var SDPUtil = {
|
|||
}
|
||||
break;
|
||||
}
|
||||
if (cand.getAttribute('protocol').toLowerCase() == 'tcp') {
|
||||
if (protocol.toLowerCase() == 'tcp') {
|
||||
line += 'tcptype';
|
||||
line += ' ';
|
||||
line += cand.getAttribute('tcptype');
|
||||
|
|
|
@ -218,6 +218,8 @@ if (TraceablePeerConnection.prototype.__defineGetter__ !== undefined) {
|
|||
function() {
|
||||
var desc = this.peerconnection.localDescription;
|
||||
|
||||
// FIXME this should probably be after the Unified Plan -> Plan B
|
||||
// transformation.
|
||||
desc = SSRCReplacement.mungeLocalVideoSSRC(desc);
|
||||
|
||||
this.trace('getLocalDescription::preTransform', dumpSDP(desc));
|
||||
|
@ -293,7 +295,7 @@ TraceablePeerConnection.prototype.setLocalDescription
|
|||
},
|
||||
function (err) {
|
||||
self.trace('setLocalDescriptionOnFailure', err);
|
||||
self.eventEmitter.emit(RTCEvents.SET_LOCAL_DESCRIPTION_FAILED, err);
|
||||
self.eventEmitter.emit(RTCEvents.SET_LOCAL_DESCRIPTION_FAILED, err, self.peerconnection);
|
||||
failureCallback(err);
|
||||
}
|
||||
);
|
||||
|
@ -329,7 +331,7 @@ TraceablePeerConnection.prototype.setRemoteDescription
|
|||
},
|
||||
function (err) {
|
||||
self.trace('setRemoteDescriptionOnFailure', err);
|
||||
self.eventEmitter.emit(RTCEvents.SET_REMOTE_DESCRIPTION_FAILED, err);
|
||||
self.eventEmitter.emit(RTCEvents.SET_REMOTE_DESCRIPTION_FAILED, err, self.peerconnection);
|
||||
failureCallback(err);
|
||||
}
|
||||
);
|
||||
|
@ -366,6 +368,7 @@ TraceablePeerConnection.prototype.createOffer
|
|||
}
|
||||
|
||||
offer = SSRCReplacement.mungeLocalVideoSSRC(offer);
|
||||
self.trace('createOfferOnSuccess::mungeLocalVideoSSRC', dumpSDP(offer));
|
||||
|
||||
if (config.enableSimulcast && self.simulcast.isSupported()) {
|
||||
offer = self.simulcast.mungeLocalDescription(offer);
|
||||
|
@ -375,7 +378,7 @@ TraceablePeerConnection.prototype.createOffer
|
|||
},
|
||||
function(err) {
|
||||
self.trace('createOfferOnFailure', err);
|
||||
self.eventEmitter.emit(RTCEvents.CREATE_OFFER_FAILED, err);
|
||||
self.eventEmitter.emit(RTCEvents.CREATE_OFFER_FAILED, err, self.peerconnection);
|
||||
failureCallback(err);
|
||||
},
|
||||
constraints
|
||||
|
@ -397,6 +400,7 @@ TraceablePeerConnection.prototype.createAnswer
|
|||
|
||||
// munge local video SSRC
|
||||
answer = SSRCReplacement.mungeLocalVideoSSRC(answer);
|
||||
self.trace('createAnswerOnSuccess::mungeLocalVideoSSRC', dumpSDP(answer));
|
||||
|
||||
if (config.enableSimulcast && self.simulcast.isSupported()) {
|
||||
answer = self.simulcast.mungeLocalDescription(answer);
|
||||
|
@ -406,7 +410,7 @@ TraceablePeerConnection.prototype.createAnswer
|
|||
},
|
||||
function(err) {
|
||||
self.trace('createAnswerOnFailure', err);
|
||||
self.eventEmitter.emit(RTCEvents.CREATE_ANSWER_FAILED, err);
|
||||
self.eventEmitter.emit(RTCEvents.CREATE_ANSWER_FAILED, err, self.peerconnection);
|
||||
failureCallback(err);
|
||||
},
|
||||
constraints
|
||||
|
|
|
@ -149,6 +149,14 @@ var Moderator = {
|
|||
{ name: 'bridge', value: config.hosts.bridge})
|
||||
.up();
|
||||
}
|
||||
|
||||
if (config.enforcedBridge) {
|
||||
elem.c(
|
||||
'property',
|
||||
{ name: 'enforcedBridge', value: config.enforcedBridge})
|
||||
.up();
|
||||
}
|
||||
|
||||
// Tell the focus we have Jigasi configured
|
||||
if (config.hosts.call_control !== undefined) {
|
||||
elem.c(
|
||||
|
|
|
@ -562,10 +562,6 @@ module.exports = function(XMPP, eventEmitter) {
|
|||
delete this.presMap["startMuted"];
|
||||
}
|
||||
|
||||
if (config.token) {
|
||||
pres.c('token', { xmlns: 'http://jitsi.org/jitmeet/auth-token'}).t(config.token).up();
|
||||
}
|
||||
|
||||
pres.up();
|
||||
this.connection.send(pres);
|
||||
},
|
||||
|
|
|
@ -308,21 +308,16 @@ var XMPP = {
|
|||
configDomain = config.hosts.domain;
|
||||
}
|
||||
var jid = configDomain || window.location.hostname;
|
||||
var password = null;
|
||||
if (config.token) {
|
||||
password = config.token;
|
||||
if (config.id) {
|
||||
jid = config.id + "@" + jid;
|
||||
} else {
|
||||
jid = generateUserName() + "@" + jid;
|
||||
}
|
||||
}
|
||||
connect(jid, password);
|
||||
connect(jid);
|
||||
},
|
||||
createConnection: function () {
|
||||
var bosh = config.bosh || '/http-bind';
|
||||
// adds the room name used to the bosh connection
|
||||
return new Strophe.Connection(bosh + '?ROOM=' + APP.UI.getRoomNode());
|
||||
bosh += '?room=' + APP.UI.getRoomNode();
|
||||
if (config.token) {
|
||||
bosh += "&token=" + config.token;
|
||||
}
|
||||
return new Strophe.Connection(bosh);
|
||||
},
|
||||
getStatusString: function (status) {
|
||||
return Strophe.getStatusString(status);
|
||||
|
@ -596,6 +591,19 @@ var XMPP = {
|
|||
return null;
|
||||
return connection.jingle.activecall.getSsrcOwner(ssrc);
|
||||
},
|
||||
/**
|
||||
* Gets the SSRC of local media stream.
|
||||
* @param mediaType the media type that tells whether we want to get
|
||||
* the SSRC of local audio or video stream.
|
||||
* @returns {*} the SSRC number for local media stream or <tt>null</tt> if
|
||||
* not available.
|
||||
*/
|
||||
getLocalSSRC: function (mediaType) {
|
||||
if (!this.isConferenceInProgress()) {
|
||||
return null;
|
||||
}
|
||||
return connection.jingle.activecall.getLocalSSRC(mediaType);
|
||||
},
|
||||
// Returns true iff we have joined the MUC.
|
||||
isMUCJoined: function () {
|
||||
return connection === null ? false : connection.emuc.joined;
|
||||
|
|
|
@ -27,9 +27,9 @@
|
|||
"jssha": "1.5.0",
|
||||
"pako": "*",
|
||||
"retry": "0.6.1",
|
||||
"sdp-interop": "0.1.10",
|
||||
"sdp-simulcast": "0.1.0",
|
||||
"sdp-transform": "1.4.1",
|
||||
"sdp-interop": "0.1.11",
|
||||
"sdp-simulcast": "0.1.3",
|
||||
"sdp-transform": "1.5.*",
|
||||
"socket.io-client": "1.3.6",
|
||||
"strophe": "^1.2.2",
|
||||
"strophejs-plugins": "^0.0.6",
|
||||
|
|
|
@ -1,42 +1,46 @@
|
|||
-- Token authentication
|
||||
-- Copyright (C) 2015 Atlassian
|
||||
|
||||
local usermanager = require "core.usermanager";
|
||||
local generate_uuid = require "util.uuid".generate;
|
||||
local new_sasl = require "util.sasl".new;
|
||||
|
||||
local log = module._log;
|
||||
local host = module.host;
|
||||
|
||||
local sasl = require "util.sasl";
|
||||
local formdecode = require "util.http".formdecode;
|
||||
local token_util = module:require "token/util";
|
||||
|
||||
-- define auth provider
|
||||
local provider = {};
|
||||
|
||||
--do
|
||||
-- local list;
|
||||
-- for mechanism in pairs(new_sasl(module.host):mechanisms()) do
|
||||
-- list = (not(list) and mechanism) or (list..", "..mechanism);
|
||||
-- end
|
||||
-- if not list then
|
||||
-- module:log("error", "No mechanisms");
|
||||
-- else
|
||||
-- module:log("error", "Mechanisms: %s", list);
|
||||
-- end
|
||||
--end
|
||||
|
||||
local host = module.host;
|
||||
|
||||
local appId = module:get_option_string("app_id");
|
||||
local appSecret = module:get_option_string("app_secret");
|
||||
local tokenLifetime = module:get_option_number("token_lifetime");
|
||||
local allowEmptyToken = module:get_option_boolean("allow_empty_token");
|
||||
|
||||
if allowEmptyToken == true then
|
||||
module:log("warn", "WARNING - empty tokens allowed");
|
||||
end
|
||||
|
||||
if appId == nil then
|
||||
module:log("error", "'app_id' must not be empty");
|
||||
return;
|
||||
end
|
||||
|
||||
if appSecret == nil then
|
||||
module:log("error", "'app_secret' must not be empty");
|
||||
return;
|
||||
end
|
||||
|
||||
-- Extract 'token' param from BOSH URL when session is created
|
||||
module:hook("bosh-session", function(event)
|
||||
local session, request = event.session, event.request;
|
||||
local query = request.url.query;
|
||||
if query ~= nil then
|
||||
session.auth_token = query and formdecode(query).token or nil;
|
||||
end
|
||||
end)
|
||||
|
||||
function provider.test_password(username, password)
|
||||
local result, msg = token_util.verify_password(password, appId, appSecret, tokenLifetime);
|
||||
if result == true then
|
||||
return true;
|
||||
else
|
||||
log("error", "Token auth failed for user %s, reason: %s",username, msg);
|
||||
return nil, msg;
|
||||
end
|
||||
return nil, "Password based auth not supported";
|
||||
end
|
||||
|
||||
function provider.get_password(username)
|
||||
|
@ -51,10 +55,6 @@ function provider.user_exists(username)
|
|||
return nil;
|
||||
end
|
||||
|
||||
function provider.users()
|
||||
return next, hosts[module.host].sessions, nil;
|
||||
end
|
||||
|
||||
function provider.create_user(username, password)
|
||||
return nil;
|
||||
end
|
||||
|
@ -63,13 +63,59 @@ function provider.delete_user(username)
|
|||
return nil;
|
||||
end
|
||||
|
||||
function provider.get_sasl_handler()
|
||||
local testpass_authentication_profile = {
|
||||
plain_test = function(sasl, username, password, realm)
|
||||
return usermanager.test_password(username, realm, password), true;
|
||||
function provider.get_sasl_handler(session)
|
||||
-- JWT token extracted from BOSH URL
|
||||
local token = session.auth_token;
|
||||
|
||||
local function get_username_from_token(self, message)
|
||||
|
||||
if token == nil then
|
||||
if allowEmptyToken == true then
|
||||
return true;
|
||||
else
|
||||
return false, "not-allowed", "token required";
|
||||
end
|
||||
end
|
||||
};
|
||||
return new_sasl(host, testpass_authentication_profile);
|
||||
|
||||
-- here we check if 'room' claim exists
|
||||
local room, roomErr = token_util.get_room_name(token, appSecret);
|
||||
if room == nil then
|
||||
return false, "not-allowed", roomErr;
|
||||
end
|
||||
|
||||
-- now verify the whole token
|
||||
local result, msg
|
||||
= token_util.verify_token(token, appId, appSecret, room);
|
||||
if result == true then
|
||||
-- Binds room name to the session which is later checked on MUC join
|
||||
session.jitsi_meet_room = room;
|
||||
return true
|
||||
else
|
||||
return false, "not-allowed", msg
|
||||
end
|
||||
end
|
||||
|
||||
return new_sasl(host, { anonymous = get_username_from_token });
|
||||
end
|
||||
|
||||
module:provides("auth", provider);
|
||||
|
||||
local function anonymous(self, message)
|
||||
|
||||
local username = generate_uuid();
|
||||
|
||||
-- This calls the handler created in 'provider.get_sasl_handler(session)'
|
||||
local result, err, msg = self.profile.anonymous(self, username, self.realm);
|
||||
|
||||
self.username = username;
|
||||
|
||||
if result == true then
|
||||
return "success"
|
||||
else
|
||||
|
||||
return "failure", err, msg
|
||||
end
|
||||
end
|
||||
|
||||
sasl.registerMechanism("ANONYMOUS", {"anonymous"}, anonymous);
|
||||
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
--- /usr/lib/prosody/modules/mod_bosh.lua 2015-12-16 14:28:34.000000000 -0600
|
||||
+++ /usr/lib/prosody/modules/mod_bosh.lua 2015-12-22 10:45:59.818197967 -0600
|
||||
@@ -294,6 +294,9 @@
|
||||
|
||||
session.log("debug", "BOSH session created for request from %s", session.ip);
|
||||
log("info", "New BOSH session, assigned it sid '%s'", sid);
|
||||
+
|
||||
+ hosts[session.host].events.fire_event(
|
||||
+ "bosh-session", { session = session, request = request });
|
||||
|
||||
-- Send creation response
|
||||
local creating_session = true;
|
|
@ -4,7 +4,6 @@
|
|||
local log = module._log;
|
||||
local host = module.host;
|
||||
local st = require "util.stanza";
|
||||
local token_util = module:require("token/util");
|
||||
local is_admin = require "core.usermanager".is_admin;
|
||||
|
||||
|
||||
|
@ -16,37 +15,70 @@ end
|
|||
|
||||
local parentCtx = module:context(parentHostName);
|
||||
if parentCtx == nil then
|
||||
log("error", "Failed to start - unable to get parent context for host: %s", tostring(parentHostName));
|
||||
log("error",
|
||||
"Failed to start - unable to get parent context for host: %s",
|
||||
tostring(parentHostName));
|
||||
return;
|
||||
end
|
||||
|
||||
local appId = parentCtx:get_option_string("app_id");
|
||||
local appSecret = parentCtx:get_option_string("app_secret");
|
||||
local tokenLifetime = parentCtx:get_option_string("token_lifetime");
|
||||
local allowEmptyToken = parentCtx:get_option_boolean("allow_empty_token");
|
||||
|
||||
log("debug", "%s - starting MUC token verifier app_id: %s app_secret: %s token-lifetime: %s",
|
||||
tostring(host), tostring(appId), tostring(appSecret), tostring(tokenLifetime));
|
||||
log("debug",
|
||||
"%s - starting MUC token verifier app_id: %s app_secret: %s allow empty: %s",
|
||||
tostring(host), tostring(appId), tostring(appSecret),
|
||||
tostring(allowEmptyToken));
|
||||
|
||||
local function verify_user(session, stanza)
|
||||
log("debug", "Session token: %s, session room: %s",
|
||||
tostring(session.auth_token),
|
||||
tostring(session.jitsi_meet_room));
|
||||
|
||||
if allowEmptyToken and session.auth_token == nil then
|
||||
module:log(
|
||||
"debug",
|
||||
"Skipped room token verification - empty tokens are allowed");
|
||||
return nil;
|
||||
end
|
||||
|
||||
local function handle_pre_create(event)
|
||||
local origin, stanza = event.origin, event.stanza;
|
||||
local token = stanza:get_child("token", "http://jitsi.org/jitmeet/auth-token");
|
||||
-- token not required for admin users
|
||||
local user_jid = stanza.attr.from;
|
||||
local user_jid = stanza.attr.from;
|
||||
if is_admin(user_jid) then
|
||||
log("debug", "Token not required from admin user: %s", user_jid);
|
||||
return nil;
|
||||
end
|
||||
log("debug", "Will verify token for user: %s ", user_jid);
|
||||
if token ~= nil then
|
||||
token = token[1];
|
||||
|
||||
local room = string.match(stanza.attr.to, "^(%w+)@");
|
||||
log("debug", "Will verify token for user: %s, room: %s ", user_jid, room);
|
||||
if room == nil then
|
||||
log("error",
|
||||
"Unable to get name of the MUC room ? to: %s", stanza.attr.to);
|
||||
return nil;
|
||||
end
|
||||
local result, msg = token_util.verify_password(token, appId, appSecret, tokenLifetime);
|
||||
if result ~= true then
|
||||
log("debug", "Token verification failed: %s", msg);
|
||||
origin.send(st.error_reply(stanza, "cancel", "not-allowed", msg));
|
||||
|
||||
local token = session.auth_token;
|
||||
local auth_room = session.jitsi_meet_room;
|
||||
if room ~= auth_room then
|
||||
log("error", "Token %s not allowed to join: %s",
|
||||
tostring(token), tostring(auth_room));
|
||||
session.send(
|
||||
st.error_reply(
|
||||
stanza, "cancel", "not-allowed", "Room and token mismatched"));
|
||||
return true;
|
||||
end
|
||||
log("debug", "allowed: %s to enter/create room: %s", user_jid, room);
|
||||
end
|
||||
|
||||
module:hook("muc-room-pre-create", handle_pre_create);
|
||||
module:hook("muc-room-pre-create", function(event)
|
||||
local origin, stanza = event.origin, event.stanza;
|
||||
log("debug", "pre create: %s %s", tostring(origin), tostring(stanza));
|
||||
return verify_user(origin, stanza);
|
||||
end);
|
||||
|
||||
module:hook("muc-occupant-pre-join", function(event)
|
||||
local origin, room, stanza = event.origin, event.room, event.stanza;
|
||||
log("debug", "pre join: %s %s", tostring(room), tostring(stanza));
|
||||
return verify_user(origin, stanza);
|
||||
end);
|
||||
|
||||
|
|
|
@ -1,76 +1,51 @@
|
|||
-- Token authentication
|
||||
-- Copyright (C) 2015 Atlassian
|
||||
|
||||
local hashes = require "util.hashes";
|
||||
local jwt = require "luajwt";
|
||||
|
||||
local _M = {};
|
||||
|
||||
local function calc_hash(password, appId, appSecret)
|
||||
local hash, room, ts = string.match(password, "(%w+)_(%w+)_(%d+)");
|
||||
if hash ~= nil and room ~= nil and ts ~= nil then
|
||||
log("debug", "Hash: '%s' room: '%s', ts: '%s'", hash, room, ts);
|
||||
local toHash = room .. ts .. appId .. appSecret;
|
||||
log("debug", "to be hashed: '%s'", toHash);
|
||||
local hash = hashes.sha256(toHash, true);
|
||||
log("debug", "hash: '%s'", hash);
|
||||
return hash;
|
||||
local function _get_room_name(token, appSecret)
|
||||
local claims, err = jwt.decode(token, appSecret);
|
||||
if claims ~= nil then
|
||||
return claims["room"];
|
||||
else
|
||||
log("error", "Invalid password format: '%s'", password);
|
||||
return nil;
|
||||
return nil, err;
|
||||
end
|
||||
end
|
||||
|
||||
local function extract_hash(password)
|
||||
local hash, room, ts = string.match(password, "(%w+)_(%w+)_(%d+)");
|
||||
return hash;
|
||||
end
|
||||
local function _verify_token(token, appId, appSecret, roomName)
|
||||
|
||||
local function extract_ts(password)
|
||||
local hash, room, ts = string.match(password, "(%w+)_(%w+)_(%d+)");
|
||||
return ts;
|
||||
end
|
||||
|
||||
local function get_utc_timestamp()
|
||||
return os.time(os.date("!*t")) * 1000;
|
||||
end
|
||||
|
||||
local function verify_timestamp(ts, tokenLifetime)
|
||||
return get_utc_timestamp() - ts <= tokenLifetime;
|
||||
end
|
||||
|
||||
local function verify_password_impl(password, appId, appSecret, tokenLifetime)
|
||||
|
||||
if password == nil then
|
||||
return nil, "password is missing";
|
||||
end
|
||||
|
||||
if tokenLifetime == nil then
|
||||
tokenLifetime = 24 * 60 * 60 * 1000;
|
||||
local claims, err = jwt.decode(token, appSecret, true);
|
||||
if claims == nil then
|
||||
return nil, err;
|
||||
end
|
||||
|
||||
local ts = extract_ts(password);
|
||||
if ts == nil then
|
||||
return nil, "timestamp not found in the password";
|
||||
local issClaim = claims["iss"];
|
||||
if issClaim == nil then
|
||||
return nil, "Issuer field is missing";
|
||||
end
|
||||
local os_ts = get_utc_timestamp();
|
||||
log("debug", "System TS: '%s' user TS: %s", tostring(os_ts), tostring(ts));
|
||||
local isValid = verify_timestamp(ts, tokenLifetime);
|
||||
if not isValid then
|
||||
return nil, "token expired";
|
||||
if issClaim ~= appId then
|
||||
return nil, "Invalid application ID('iss' claim)";
|
||||
end
|
||||
|
||||
local realHash = calc_hash(password, appId, appSecret);
|
||||
local givenhash = extract_hash(password);
|
||||
log("debug", "Compare '%s' to '%s'", tostring(realHash), tostring(givenhash));
|
||||
if realHash == givenhash then
|
||||
return true;
|
||||
else
|
||||
return nil, "invalid hash";
|
||||
local roomClaim = claims["room"];
|
||||
if roomClaim == nil then
|
||||
return nil, "Room field is missing";
|
||||
end
|
||||
if roomName ~= nil and roomName ~= roomClaim then
|
||||
return nil, "Invalid room name('room' claim)";
|
||||
end
|
||||
|
||||
return true;
|
||||
end
|
||||
|
||||
function _M.verify_password(password, appId, appSecret, tokenLifetime)
|
||||
return verify_password_impl(password, appId, appSecret, tokenLifetime);
|
||||
function _M.verify_token(token, appId, appSecret, roomName)
|
||||
return _verify_token(token, appId, appSecret, roomName);
|
||||
end
|
||||
|
||||
return _M;
|
||||
function _M.get_room_name(token, appSecret)
|
||||
return _get_room_name(token, appSecret);
|
||||
end
|
||||
|
||||
return _M;
|
|
@ -2,6 +2,11 @@ var UIEvents = {
|
|||
NICKNAME_CHANGED: "UI.nickname_changed",
|
||||
SELECTED_ENDPOINT: "UI.selected_endpoint",
|
||||
PINNED_ENDPOINT: "UI.pinned_endpoint",
|
||||
LARGEVIDEO_INIT: "UI.largevideo_init"
|
||||
LARGEVIDEO_INIT: "UI.largevideo_init",
|
||||
/**
|
||||
* Notifies interested parties when the film strip (remote video's panel)
|
||||
* is hidden (toggled) or shown (un-toggled).
|
||||
*/
|
||||
FILM_STRIP_TOGGLED: "UI.filmstrip_toggled"
|
||||
};
|
||||
module.exports = UIEvents;
|
|
@ -12,33 +12,50 @@
|
|||
|
||||
<div class="supported_browsers">
|
||||
<div class="browser_wrapper">
|
||||
Chrome
|
||||
Chrome 44+
|
||||
<div class="browser">
|
||||
<div class="logo" id="chrome_logo"></div>
|
||||
<a href="http://google.com/chrome"><div class="button">DOWNLOAD</div></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="browser_wrapper">
|
||||
Chromium
|
||||
Chromium 44+
|
||||
<div class="browser">
|
||||
<div class="logo" id="chromium_logo"></div>
|
||||
<a href="http://www.chromium.org/"><div class="button">DOWNLOAD</div></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="browser_wrapper">
|
||||
Opera
|
||||
Opera 32+
|
||||
<div class="browser">
|
||||
<div class="logo" id="opera_logo"></div>
|
||||
<a href="http://www.opera.com"><div class="button">DOWNLOAD</div></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="browser_wrapper">
|
||||
Firefox
|
||||
<div class="browser_text">
|
||||
Firefox and Iceweasel 40+</div>
|
||||
<div class="browser">
|
||||
<div class="logo" id="firefox_logo"></div>
|
||||
<a href="http://www.getfirefox.com/"><div class="button">DOWNLOAD</div></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="browser_wrapper">
|
||||
<div class="browser_text">
|
||||
IE <br /> <span style="font-size: small">(Temasys 0.8.854+)</span></div>
|
||||
<div class="browser">
|
||||
<div class="logo" id="ie_logo"></div>
|
||||
<a href="https://temasys.atlassian.net/wiki/display/TWPP/WebRTC+Plugins"><div class="button">DOWNLOAD</div></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="browser_wrapper">
|
||||
<div class="browser_text">
|
||||
Safari <br /> <span style="font-size: small">(Temasys 0.8.854+)</span></div>
|
||||
<div class="browser">
|
||||
<div class="logo" id="safari_logo"></div>
|
||||
<a href="https://temasys.atlassian.net/wiki/display/TWPP/WebRTC+Plugins"><div class="button">DOWNLOAD</div></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue