Merge branch 'master' into jingle-protocol-changes
23
INSTALL.md
|
@ -230,3 +230,26 @@ org.jitsi.videobridge.NAT_HARVESTER_PUBLIC_ADDRESS=<Public.IP.Address>
|
||||||
|
|
||||||
# Hold your first conference
|
# Hold your first conference
|
||||||
You are now all set and ready to have your first meet by going to http://jitsi.example.com
|
You are now all set and ready to have your first meet by going to http://jitsi.example.com
|
||||||
|
|
||||||
|
|
||||||
|
## Enabling recording
|
||||||
|
Currently recording is only supported for linux-64 and macos. To enable it, add
|
||||||
|
the following properties to sip-communicator.properties:
|
||||||
|
```
|
||||||
|
org.jitsi.videobridge.ENABLE_MEDIA_RECORDING=true
|
||||||
|
org.jitsi.videobridge.MEDIA_RECORDING_PATH=/path/to/recordings/dir
|
||||||
|
org.jitsi.videobridge.MEDIA_RECORDING_TOKEN=secret
|
||||||
|
```
|
||||||
|
|
||||||
|
where /path/to/recordings/dir is the path to a pre-existing directory where recordings
|
||||||
|
will be stored (needs to be writeable by the user running jitsi-videobridge),
|
||||||
|
and "secret" is a string which will be used for authentication.
|
||||||
|
|
||||||
|
Then, edit the Jitsi-Meet config.js file and set:
|
||||||
|
```
|
||||||
|
enableRecording: true
|
||||||
|
```
|
||||||
|
|
||||||
|
Restart jitsi-videobridge and start a new conference (making sure that the page
|
||||||
|
is reloaded with the new config.js) -- the organizer of the conference should
|
||||||
|
now have a "recoriding" button in the floating menu, near the "mute" button.
|
||||||
|
|
515
app.js
|
@ -1,19 +1,30 @@
|
||||||
/* jshint -W117 */
|
/* jshint -W117 */
|
||||||
/* application specific logic */
|
/* application specific logic */
|
||||||
var connection = null;
|
var connection = null;
|
||||||
|
var authenticatedUser = false;
|
||||||
var focus = null;
|
var focus = null;
|
||||||
var activecall = null;
|
var activecall = null;
|
||||||
var RTC = null;
|
var RTC = null;
|
||||||
var nickname = null;
|
var nickname = null;
|
||||||
var sharedKey = '';
|
var sharedKey = '';
|
||||||
|
var recordingToken ='';
|
||||||
var roomUrl = null;
|
var roomUrl = null;
|
||||||
|
var roomName = null;
|
||||||
var ssrc2jid = {};
|
var ssrc2jid = {};
|
||||||
|
var mediaStreams = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The stats collector that process stats data and triggers updates to app.js.
|
* The stats collector that process stats data and triggers updates to app.js.
|
||||||
* @type {StatsCollector}
|
* @type {StatsCollector}
|
||||||
*/
|
*/
|
||||||
var statsCollector = null;
|
var statsCollector = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The stats collector for the local stream.
|
||||||
|
* @type {LocalStatsCollector}
|
||||||
|
*/
|
||||||
|
var localStatsCollector = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates whether ssrc is camera video or desktop stream.
|
* Indicates whether ssrc is camera video or desktop stream.
|
||||||
* FIXME: remove those maps
|
* FIXME: remove those maps
|
||||||
|
@ -58,6 +69,11 @@ function init() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var jid = document.getElementById('jid').value || config.hosts.anonymousdomain || config.hosts.domain || window.location.hostname;
|
||||||
|
connect(jid);
|
||||||
|
}
|
||||||
|
|
||||||
|
function connect(jid, password) {
|
||||||
connection = new Strophe.Connection(document.getElementById('boshURL').value || config.bosh || '/http-bind');
|
connection = new Strophe.Connection(document.getElementById('boshURL').value || config.bosh || '/http-bind');
|
||||||
|
|
||||||
if (nickname) {
|
if (nickname) {
|
||||||
|
@ -74,9 +90,11 @@ function init() {
|
||||||
connection.jingle.pc_constraints.optional.push({googIPv6: true});
|
connection.jingle.pc_constraints.optional.push({googIPv6: true});
|
||||||
}
|
}
|
||||||
|
|
||||||
var jid = document.getElementById('jid').value || config.hosts.domain || window.location.hostname;
|
if(!password)
|
||||||
|
password = document.getElementById('password').value;
|
||||||
|
|
||||||
connection.connect(jid, document.getElementById('password').value, function (status) {
|
var anonymousConnectionFailed = false;
|
||||||
|
connection.connect(jid, password, function (status, msg) {
|
||||||
if (status === Strophe.Status.CONNECTED) {
|
if (status === Strophe.Status.CONNECTED) {
|
||||||
console.log('connected');
|
console.log('connected');
|
||||||
if (config.useStunTurn) {
|
if (config.useStunTurn) {
|
||||||
|
@ -90,6 +108,23 @@ function init() {
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('connect').disabled = true;
|
document.getElementById('connect').disabled = true;
|
||||||
|
|
||||||
|
if(password)
|
||||||
|
authenticatedUser = true;
|
||||||
|
} else if (status === Strophe.Status.CONNFAIL) {
|
||||||
|
if(msg === 'x-strophe-bad-non-anon-jid') {
|
||||||
|
anonymousConnectionFailed = true;
|
||||||
|
}
|
||||||
|
console.log('status', status);
|
||||||
|
} else if (status === Strophe.Status.DISCONNECTED) {
|
||||||
|
if(anonymousConnectionFailed) {
|
||||||
|
// prompt user for username and password
|
||||||
|
$(document).trigger('passwordrequired.main');
|
||||||
|
}
|
||||||
|
} else if (status === Strophe.Status.AUTHFAIL) {
|
||||||
|
// wrong password or username, prompt user
|
||||||
|
$(document).trigger('passwordrequired.main');
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
console.log('status', status);
|
console.log('status', status);
|
||||||
}
|
}
|
||||||
|
@ -123,6 +158,8 @@ function audioStreamReady(stream) {
|
||||||
|
|
||||||
VideoLayout.changeLocalAudio(stream);
|
VideoLayout.changeLocalAudio(stream);
|
||||||
|
|
||||||
|
startLocalRtpStatsCollector(stream);
|
||||||
|
|
||||||
if (RTC.browser !== 'firefox') {
|
if (RTC.browser !== 'firefox') {
|
||||||
getUserMediaWithConstraints(['video'],
|
getUserMediaWithConstraints(['video'],
|
||||||
videoStreamReady,
|
videoStreamReady,
|
||||||
|
@ -173,7 +210,9 @@ function doJoin() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
roomjid = roomnode + '@' + config.hosts.muc;
|
roomName = roomnode + '@' + config.hosts.muc;
|
||||||
|
|
||||||
|
roomjid = roomName;
|
||||||
|
|
||||||
if (config.useNicks) {
|
if (config.useNicks) {
|
||||||
var nick = window.prompt('Your nickname (optional)');
|
var nick = window.prompt('Your nickname (optional)');
|
||||||
|
@ -183,45 +222,52 @@ function doJoin() {
|
||||||
roomjid += '/' + Strophe.getNodeFromJid(connection.jid);
|
roomjid += '/' + Strophe.getNodeFromJid(connection.jid);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
roomjid += '/' + Strophe.getNodeFromJid(connection.jid).substr(0, 8);
|
|
||||||
|
var tmpJid = Strophe.getNodeFromJid(connection.jid);
|
||||||
|
|
||||||
|
if(!authenticatedUser)
|
||||||
|
tmpJid = tmpJid.substr(0, 8);
|
||||||
|
|
||||||
|
roomjid += '/' + tmpJid;
|
||||||
}
|
}
|
||||||
connection.emuc.doJoin(roomjid);
|
connection.emuc.doJoin(roomjid);
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).bind('remotestreamadded.jingle', function (event, data, sid) {
|
function waitForRemoteVideo(selector, ssrc, stream) {
|
||||||
function waitForRemoteVideo(selector, sid, ssrc) {
|
if (selector.removed || !selector.parent().is(":visible")) {
|
||||||
if (selector.removed) {
|
console.warn("Media removed before had started", selector);
|
||||||
console.warn("media removed before had started", selector);
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
var sess = connection.jingle.sessions[sid];
|
|
||||||
if (data.stream.id === 'mixedmslabel') return;
|
|
||||||
var videoTracks = data.stream.getVideoTracks();
|
|
||||||
// console.log("waiting..", videoTracks, selector[0]);
|
|
||||||
|
|
||||||
if (videoTracks.length === 0 || selector[0].currentTime > 0) {
|
|
||||||
RTC.attachMediaStream(selector, data.stream); // FIXME: why do i have to do this for FF?
|
|
||||||
|
|
||||||
// FIXME: add a class that will associate peer Jid, video.src, it's ssrc and video type
|
|
||||||
// in order to get rid of too many maps
|
|
||||||
if (ssrc) {
|
|
||||||
videoSrcToSsrc[sel.attr('src')] = ssrc;
|
|
||||||
} else {
|
|
||||||
console.warn("No ssrc given for video", sel);
|
|
||||||
}
|
|
||||||
|
|
||||||
$(document).trigger('callactive.jingle', [selector, sid]);
|
|
||||||
console.log('waitForremotevideo', sess.peerconnection.iceConnectionState, sess.peerconnection.signalingState);
|
|
||||||
} else {
|
|
||||||
setTimeout(function () { waitForRemoteVideo(selector, sid, ssrc); }, 250);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (stream.id === 'mixedmslabel') return;
|
||||||
|
|
||||||
|
if (selector[0].currentTime > 0) {
|
||||||
|
RTC.attachMediaStream(selector, stream); // FIXME: why do i have to do this for FF?
|
||||||
|
|
||||||
|
// FIXME: add a class that will associate peer Jid, video.src, it's ssrc and video type
|
||||||
|
// in order to get rid of too many maps
|
||||||
|
if (ssrc && selector.attr('src')) {
|
||||||
|
videoSrcToSsrc[selector.attr('src')] = ssrc;
|
||||||
|
} else {
|
||||||
|
console.warn("No ssrc given for video", selector);
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).trigger('videoactive.jingle', [selector]);
|
||||||
|
} else {
|
||||||
|
setTimeout(function () {
|
||||||
|
waitForRemoteVideo(selector, ssrc, stream);
|
||||||
|
}, 250);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).bind('remotestreamadded.jingle', function (event, data, sid) {
|
||||||
var sess = connection.jingle.sessions[sid];
|
var sess = connection.jingle.sessions[sid];
|
||||||
|
|
||||||
var thessrc;
|
var thessrc;
|
||||||
// look up an associated JID for a stream id
|
// look up an associated JID for a stream id
|
||||||
if (data.stream.id.indexOf('mixedmslabel') === -1) {
|
if (data.stream.id.indexOf('mixedmslabel') === -1) {
|
||||||
var ssrclines = SDPUtil.find_lines(sess.peerconnection.remoteDescription.sdp, 'a=ssrc');
|
var ssrclines
|
||||||
|
= SDPUtil.find_lines(sess.peerconnection.remoteDescription.sdp, 'a=ssrc');
|
||||||
ssrclines = ssrclines.filter(function (line) {
|
ssrclines = ssrclines.filter(function (line) {
|
||||||
return line.indexOf('mslabel:' + data.stream.label) !== -1;
|
return line.indexOf('mslabel:' + data.stream.label) !== -1;
|
||||||
});
|
});
|
||||||
|
@ -235,11 +281,14 @@ $(document).bind('remotestreamadded.jingle', function (event, data, sid) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mediaStreams.push(new MediaStream(data, sid, thessrc));
|
||||||
|
|
||||||
var container;
|
var container;
|
||||||
var remotes = document.getElementById('remoteVideos');
|
var remotes = document.getElementById('remoteVideos');
|
||||||
|
|
||||||
if (data.peerjid) {
|
if (data.peerjid) {
|
||||||
VideoLayout.ensurePeerContainerExists(data.peerjid);
|
VideoLayout.ensurePeerContainerExists(data.peerjid);
|
||||||
|
|
||||||
container = document.getElementById(
|
container = document.getElementById(
|
||||||
'participant_' + Strophe.getResourceFromJid(data.peerjid));
|
'participant_' + Strophe.getResourceFromJid(data.peerjid));
|
||||||
} else {
|
} else {
|
||||||
|
@ -252,78 +301,22 @@ $(document).bind('remotestreamadded.jingle', function (event, data, sid) {
|
||||||
}
|
}
|
||||||
// FIXME: for the mixed ms we dont need a video -- currently
|
// FIXME: for the mixed ms we dont need a video -- currently
|
||||||
container = document.createElement('span');
|
container = document.createElement('span');
|
||||||
|
container.id = 'mixedstream';
|
||||||
container.className = 'videocontainer';
|
container.className = 'videocontainer';
|
||||||
remotes.appendChild(container);
|
remotes.appendChild(container);
|
||||||
Util.playSoundNotification('userJoined');
|
Util.playSoundNotification('userJoined');
|
||||||
}
|
}
|
||||||
|
|
||||||
var isVideo = data.stream.getVideoTracks().length > 0;
|
var isVideo = data.stream.getVideoTracks().length > 0;
|
||||||
var vid = isVideo ? document.createElement('video') : document.createElement('audio');
|
|
||||||
var id = (isVideo ? 'remoteVideo_' : 'remoteAudio_') + sid + '_' + data.stream.id;
|
|
||||||
|
|
||||||
vid.id = id;
|
if (container) {
|
||||||
vid.autoplay = true;
|
VideoLayout.addRemoteStreamElement( container,
|
||||||
vid.oncontextmenu = function () { return false; };
|
sid,
|
||||||
|
data.stream,
|
||||||
container.appendChild(vid);
|
data.peerjid,
|
||||||
|
thessrc);
|
||||||
// TODO: make mixedstream display:none via css?
|
|
||||||
if (id.indexOf('mixedmslabel') !== -1) {
|
|
||||||
container.id = 'mixedstream';
|
|
||||||
$(container).hide();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var sel = $('#' + id);
|
|
||||||
sel.hide();
|
|
||||||
RTC.attachMediaStream(sel, data.stream);
|
|
||||||
|
|
||||||
if (isVideo) {
|
|
||||||
waitForRemoteVideo(sel, sid, thessrc);
|
|
||||||
}
|
|
||||||
|
|
||||||
data.stream.onended = function () {
|
|
||||||
console.log('stream ended', this.id);
|
|
||||||
|
|
||||||
// Mark video as removed to cancel waiting loop(if video is removed before has started)
|
|
||||||
sel.removed = true;
|
|
||||||
sel.remove();
|
|
||||||
|
|
||||||
var audioCount = $('#' + container.id + '>audio').length;
|
|
||||||
var videoCount = $('#' + container.id + '>video').length;
|
|
||||||
if (!audioCount && !videoCount) {
|
|
||||||
console.log("Remove whole user", container.id);
|
|
||||||
// Remove whole container
|
|
||||||
container.remove();
|
|
||||||
Util.playSoundNotification('userLeft');
|
|
||||||
VideoLayout.resizeThumbnails();
|
|
||||||
}
|
|
||||||
|
|
||||||
VideoLayout.checkChangeLargeVideo(vid.src);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add click handler
|
|
||||||
sel.click(function () {
|
|
||||||
VideoLayout.handleVideoThumbClicked(vid.src);
|
|
||||||
});
|
|
||||||
// Add hover handler
|
|
||||||
$(container).hover(
|
|
||||||
function() {
|
|
||||||
VideoLayout.showDisplayName(container.id, true);
|
|
||||||
},
|
|
||||||
function() {
|
|
||||||
var videoSrc = null;
|
|
||||||
if ($('#' + container.id + '>video')
|
|
||||||
&& $('#' + container.id + '>video').length > 0) {
|
|
||||||
videoSrc = $('#' + container.id + '>video').get(0).src;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the video has been "pinned" by the user we want to keep the
|
|
||||||
// display name on place.
|
|
||||||
if (focusedVideoSrc !== videoSrc)
|
|
||||||
VideoLayout.showDisplayName(container.id, false);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// an attempt to work around https://github.com/jitsi/jitmeet/issues/32
|
// an attempt to work around https://github.com/jitsi/jitmeet/issues/32
|
||||||
if (isVideo &&
|
if (isVideo &&
|
||||||
data.peerjid && sess.peerjid === data.peerjid &&
|
data.peerjid && sess.peerjid === data.peerjid &&
|
||||||
|
@ -423,21 +416,25 @@ function muteVideo(pc, unmute) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback called by {@link StatsCollector} in intervals supplied to it's
|
* Callback for audio levels changed.
|
||||||
* constructor.
|
* @param jid JID of the user
|
||||||
* @param statsCollector {@link StatsCollector} source of the event.
|
* @param audioLevel the audio level value
|
||||||
*/
|
*/
|
||||||
function statsUpdated(statsCollector)
|
function audioLevelUpdated(jid, audioLevel)
|
||||||
{
|
{
|
||||||
Object.keys(statsCollector.jid2stats).forEach(function (jid)
|
var resourceJid;
|
||||||
|
if(jid === LocalStatsCollector.LOCAL_JID)
|
||||||
{
|
{
|
||||||
var peerStats = statsCollector.jid2stats[jid];
|
resourceJid = AudioLevels.LOCAL_LEVEL;
|
||||||
Object.keys(peerStats.ssrc2AudioLevel).forEach(function (ssrc)
|
if(isAudioMuted())
|
||||||
{
|
return;
|
||||||
console.info(jid + " audio level: " +
|
}
|
||||||
peerStats.ssrc2AudioLevel[ssrc] + " of ssrc: " + ssrc);
|
else
|
||||||
});
|
{
|
||||||
});
|
resourceJid = Strophe.getResourceFromJid(jid);
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioLevels.updateAudioLevel(resourceJid, audioLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -445,15 +442,52 @@ function statsUpdated(statsCollector)
|
||||||
*/
|
*/
|
||||||
function startRtpStatsCollector()
|
function startRtpStatsCollector()
|
||||||
{
|
{
|
||||||
|
stopRTPStatsCollector();
|
||||||
if (config.enableRtpStats)
|
if (config.enableRtpStats)
|
||||||
{
|
{
|
||||||
statsCollector = new StatsCollector(
|
statsCollector = new StatsCollector(
|
||||||
getConferenceHandler().peerconnection, 200, statsUpdated);
|
getConferenceHandler().peerconnection, 200, audioLevelUpdated);
|
||||||
|
|
||||||
statsCollector.start();
|
statsCollector.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the {@link StatsCollector}.
|
||||||
|
*/
|
||||||
|
function stopRTPStatsCollector()
|
||||||
|
{
|
||||||
|
if (statsCollector)
|
||||||
|
{
|
||||||
|
statsCollector.stop();
|
||||||
|
statsCollector = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the {@link LocalStatsCollector} if the feature is enabled in config.js
|
||||||
|
* @param stream the stream that will be used for collecting statistics.
|
||||||
|
*/
|
||||||
|
function startLocalRtpStatsCollector(stream)
|
||||||
|
{
|
||||||
|
if(config.enableRtpStats)
|
||||||
|
{
|
||||||
|
localStatsCollector = new LocalStatsCollector(stream, 100, audioLevelUpdated);
|
||||||
|
localStatsCollector.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the {@link LocalStatsCollector}.
|
||||||
|
*/
|
||||||
|
function stopLocalRtpStatsCollector()
|
||||||
|
{
|
||||||
|
if(localStatsCollector)
|
||||||
|
{
|
||||||
|
localStatsCollector.stop();
|
||||||
|
localStatsCollector = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$(document).bind('callincoming.jingle', function (event, sid) {
|
$(document).bind('callincoming.jingle', function (event, sid) {
|
||||||
var sess = connection.jingle.sessions[sid];
|
var sess = connection.jingle.sessions[sid];
|
||||||
|
|
||||||
|
@ -490,19 +524,6 @@ $(document).bind('conferenceCreated.jingle', function (event, focus)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$(document).bind('callactive.jingle', function (event, videoelem, sid) {
|
|
||||||
if (videoelem.attr('id').indexOf('mixedmslabel') === -1) {
|
|
||||||
// ignore mixedmslabela0 and v0
|
|
||||||
videoelem.show();
|
|
||||||
VideoLayout.resizeThumbnails();
|
|
||||||
|
|
||||||
if (!focusedVideoSrc)
|
|
||||||
VideoLayout.updateLargeVideo(videoelem.attr('src'), 1);
|
|
||||||
|
|
||||||
VideoLayout.showFocusIndicator();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$(document).bind('callterminated.jingle', function (event, sid, jid, reason) {
|
$(document).bind('callterminated.jingle', function (event, sid, jid, reason) {
|
||||||
// Leave the room if my call has been remotely terminated.
|
// Leave the room if my call has been remotely terminated.
|
||||||
if (connection.emuc.joined && focus == null && reason === 'kick') {
|
if (connection.emuc.joined && focus == null && reason === 'kick') {
|
||||||
|
@ -530,8 +551,8 @@ $(document).bind('setLocalDescription.jingle', function (event, sid) {
|
||||||
directions[type] = (
|
directions[type] = (
|
||||||
SDPUtil.find_line(media, 'a=sendrecv') ||
|
SDPUtil.find_line(media, 'a=sendrecv') ||
|
||||||
SDPUtil.find_line(media, 'a=recvonly') ||
|
SDPUtil.find_line(media, 'a=recvonly') ||
|
||||||
SDPUtil.find_line('a=sendonly') ||
|
SDPUtil.find_line(media, 'a=sendonly') ||
|
||||||
SDPUtil.find_line('a=inactive') ||
|
SDPUtil.find_line(media, 'a=inactive') ||
|
||||||
'a=sendrecv').substr(2);
|
'a=sendrecv').substr(2);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -562,6 +583,17 @@ $(document).bind('joined.muc', function (event, jid, info) {
|
||||||
|
|
||||||
if (Object.keys(connection.emuc.members).length < 1) {
|
if (Object.keys(connection.emuc.members).length < 1) {
|
||||||
focus = new ColibriFocus(connection, config.hosts.bridge);
|
focus = new ColibriFocus(connection, config.hosts.bridge);
|
||||||
|
if (nickname !== null) {
|
||||||
|
focus.setEndpointDisplayName(connection.emuc.myroomjid,
|
||||||
|
nickname);
|
||||||
|
}
|
||||||
|
Toolbar.showSipCallButton(true);
|
||||||
|
Toolbar.showRecordingButton(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!focus)
|
||||||
|
{
|
||||||
|
Toolbar.showSipCallButton(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (focus && config.etherpad_base) {
|
if (focus && config.etherpad_base) {
|
||||||
|
@ -570,18 +602,25 @@ $(document).bind('joined.muc', function (event, jid, info) {
|
||||||
|
|
||||||
VideoLayout.showFocusIndicator();
|
VideoLayout.showFocusIndicator();
|
||||||
|
|
||||||
|
// Add myself to the contact list.
|
||||||
|
ContactList.addContact(jid);
|
||||||
|
|
||||||
// Once we've joined the muc show the toolbar
|
// Once we've joined the muc show the toolbar
|
||||||
Toolbar.showToolbar();
|
Toolbar.showToolbar();
|
||||||
|
|
||||||
var displayName = '';
|
var displayName = '';
|
||||||
if (info.displayName)
|
if (info.displayName)
|
||||||
displayName = info.displayName + ' (me)';
|
displayName = info.displayName + ' (me)';
|
||||||
|
else
|
||||||
|
displayName = "Me";
|
||||||
|
|
||||||
VideoLayout.setDisplayName('localVideoContainer', displayName);
|
$(document).trigger('displaynamechanged',
|
||||||
|
['localVideoContainer', displayName]);
|
||||||
});
|
});
|
||||||
|
|
||||||
$(document).bind('entered.muc', function (event, jid, info, pres) {
|
$(document).bind('entered.muc', function (event, jid, info, pres) {
|
||||||
console.log('entered', jid, info);
|
console.log('entered', jid, info);
|
||||||
|
|
||||||
console.log('is focus?' + focus ? 'true' : 'false');
|
console.log('is focus?' + focus ? 'true' : 'false');
|
||||||
|
|
||||||
// Add Peer's container
|
// Add Peer's container
|
||||||
|
@ -592,6 +631,7 @@ $(document).bind('entered.muc', function (event, jid, info, pres) {
|
||||||
if (focus.confid === null) {
|
if (focus.confid === null) {
|
||||||
console.log('make new conference with', jid);
|
console.log('make new conference with', jid);
|
||||||
focus.makeConference(Object.keys(connection.emuc.members));
|
focus.makeConference(Object.keys(connection.emuc.members));
|
||||||
|
Toolbar.showRecordingButton(true);
|
||||||
} else {
|
} else {
|
||||||
console.log('invite', jid, 'into conference');
|
console.log('invite', jid, 'into conference');
|
||||||
focus.addNewParticipant(jid);
|
focus.addNewParticipant(jid);
|
||||||
|
@ -637,17 +677,31 @@ $(document).bind('left.muc', function (event, jid) {
|
||||||
&& !sessionTerminated) {
|
&& !sessionTerminated) {
|
||||||
console.log('welcome to our new focus... myself');
|
console.log('welcome to our new focus... myself');
|
||||||
focus = new ColibriFocus(connection, config.hosts.bridge);
|
focus = new ColibriFocus(connection, config.hosts.bridge);
|
||||||
|
if (nickname !== null) {
|
||||||
|
focus.setEndpointDisplayName(connection.emuc.myroomjid,
|
||||||
|
nickname);
|
||||||
|
}
|
||||||
|
|
||||||
|
Toolbar.showSipCallButton(true);
|
||||||
|
|
||||||
if (Object.keys(connection.emuc.members).length > 0) {
|
if (Object.keys(connection.emuc.members).length > 0) {
|
||||||
focus.makeConference(Object.keys(connection.emuc.members));
|
focus.makeConference(Object.keys(connection.emuc.members));
|
||||||
|
Toolbar.showRecordingButton(true);
|
||||||
}
|
}
|
||||||
$(document).trigger('focusechanged.muc', [focus]);
|
$(document).trigger('focusechanged.muc', [focus]);
|
||||||
}
|
}
|
||||||
else if (focus && Object.keys(connection.emuc.members).length === 0) {
|
else if (focus && Object.keys(connection.emuc.members).length === 0) {
|
||||||
console.log('everyone left');
|
console.log('everyone left');
|
||||||
// FIXME: closing the connection is a hack to avoid some
|
// FIXME: closing the connection is a hack to avoid some
|
||||||
// problemswith reinit
|
// problems with reinit
|
||||||
disposeConference();
|
disposeConference();
|
||||||
focus = new ColibriFocus(connection, config.hosts.bridge);
|
focus = new ColibriFocus(connection, config.hosts.bridge);
|
||||||
|
if (nickname !== null) {
|
||||||
|
focus.setEndpointDisplayName(connection.emuc.myroomjid,
|
||||||
|
nickname);
|
||||||
|
}
|
||||||
|
Toolbar.showSipCallButton(true);
|
||||||
|
Toolbar.showRecordingButton(false);
|
||||||
}
|
}
|
||||||
if (connection.emuc.getPrezi(jid)) {
|
if (connection.emuc.getPrezi(jid)) {
|
||||||
$(document).trigger('presentationremoved.muc',
|
$(document).trigger('presentationremoved.muc',
|
||||||
|
@ -662,7 +716,7 @@ $(document).bind('presence.muc', function (event, jid, info, pres) {
|
||||||
if (ssrc2jid[ssrc] == jid) {
|
if (ssrc2jid[ssrc] == jid) {
|
||||||
delete ssrc2jid[ssrc];
|
delete ssrc2jid[ssrc];
|
||||||
}
|
}
|
||||||
if (ssrc2videoType == jid) {
|
if (ssrc2videoType[ssrc] == jid) {
|
||||||
delete ssrc2videoType[ssrc];
|
delete ssrc2videoType[ssrc];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -685,25 +739,28 @@ $(document).bind('presence.muc', function (event, jid, info, pres) {
|
||||||
case 'recvonly':
|
case 'recvonly':
|
||||||
el.hide();
|
el.hide();
|
||||||
// FIXME: Check if we have to change large video
|
// FIXME: Check if we have to change large video
|
||||||
//VideoLayout.checkChangeLargeVideo(el);
|
//VideoLayout.updateLargeVideo(el);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (info.displayName) {
|
if (info.displayName && info.displayName.length > 0)
|
||||||
if (jid === connection.emuc.myroomjid) {
|
$(document).trigger('displaynamechanged',
|
||||||
VideoLayout.setDisplayName('localVideoContainer',
|
[jid, info.displayName]);
|
||||||
info.displayName + ' (me)');
|
|
||||||
} else {
|
if (focus !== null && info.displayName !== null) {
|
||||||
VideoLayout.ensurePeerContainerExists(jid);
|
focus.setEndpointDisplayName(jid, info.displayName);
|
||||||
VideoLayout.setDisplayName(
|
|
||||||
'participant_' + Strophe.getResourceFromJid(jid),
|
|
||||||
info.displayName);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$(document).bind('presence.status.muc', function (event, jid, info, pres) {
|
||||||
|
|
||||||
|
VideoLayout.setPresenceStatus(
|
||||||
|
'participant_' + Strophe.getResourceFromJid(jid), info.status);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
$(document).bind('passwordrequired.muc', function (event, jid) {
|
$(document).bind('passwordrequired.muc', function (event, jid) {
|
||||||
console.log('on password required', jid);
|
console.log('on password required', jid);
|
||||||
|
|
||||||
|
@ -728,6 +785,31 @@ $(document).bind('passwordrequired.muc', function (event, jid) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$(document).bind('passwordrequired.main', function (event) {
|
||||||
|
console.log('password is required');
|
||||||
|
|
||||||
|
$.prompt('<h2>Password required</h2>' +
|
||||||
|
'<input id="passwordrequired.username" type="text" placeholder="user@domain.net" autofocus>' +
|
||||||
|
'<input id="passwordrequired.password" type="password" placeholder="user password">', {
|
||||||
|
persistent: true,
|
||||||
|
buttons: { "Ok": true, "Cancel": false},
|
||||||
|
defaultButton: 1,
|
||||||
|
loaded: function (event) {
|
||||||
|
document.getElementById('passwordrequired.username').focus();
|
||||||
|
},
|
||||||
|
submit: function (e, v, m, f) {
|
||||||
|
if (v) {
|
||||||
|
var username = document.getElementById('passwordrequired.username');
|
||||||
|
var password = document.getElementById('passwordrequired.password');
|
||||||
|
|
||||||
|
if (username.value !== null && password.value != null) {
|
||||||
|
connect(username.value, password.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if video identified by given src is desktop stream.
|
* Checks if video identified by given src is desktop stream.
|
||||||
* @param videoSrc eg.
|
* @param videoSrc eg.
|
||||||
|
@ -818,6 +900,71 @@ function toggleAudio() {
|
||||||
buttonClick("#mute", "icon-microphone icon-mic-disabled");
|
buttonClick("#mute", "icon-microphone icon-mic-disabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the audio is muted or not.
|
||||||
|
* @returns {boolean} true if audio is muted and false if not.
|
||||||
|
*/
|
||||||
|
function isAudioMuted()
|
||||||
|
{
|
||||||
|
var localAudio = connection.jingle.localAudio;
|
||||||
|
for (var idx = 0; idx < localAudio.getAudioTracks().length; idx++) {
|
||||||
|
if(localAudio.getAudioTracks()[idx].enabled === true)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Starts or stops the recording for the conference.
|
||||||
|
function toggleRecording() {
|
||||||
|
if (focus === null || focus.confid === null) {
|
||||||
|
console.log('non-focus, or conference not yet organized: not enabling recording');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!recordingToken)
|
||||||
|
{
|
||||||
|
$.prompt('<h2>Enter recording token</h2>' +
|
||||||
|
'<input id="recordingToken" type="text" placeholder="token" autofocus>',
|
||||||
|
{
|
||||||
|
persistent: false,
|
||||||
|
buttons: { "Save": true, "Cancel": false},
|
||||||
|
defaultButton: 1,
|
||||||
|
loaded: function (event) {
|
||||||
|
document.getElementById('recordingToken').focus();
|
||||||
|
},
|
||||||
|
submit: function (e, v, m, f) {
|
||||||
|
if (v) {
|
||||||
|
var token = document.getElementById('recordingToken');
|
||||||
|
|
||||||
|
if (token.value) {
|
||||||
|
setRecordingToken(Util.escapeHtml(token.value));
|
||||||
|
toggleRecording();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var oldState = focus.recordingEnabled;
|
||||||
|
Toolbar.toggleRecordingButtonState();
|
||||||
|
focus.setRecording(!oldState,
|
||||||
|
recordingToken,
|
||||||
|
function (state) {
|
||||||
|
console.log("New recording state: ", state);
|
||||||
|
if (state == oldState) //failed to change, reset the token because it might have been wrong
|
||||||
|
{
|
||||||
|
Toolbar.toggleRecordingButtonState();
|
||||||
|
setRecordingToken(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an array of the video horizontal and vertical indents,
|
* Returns an array of the video horizontal and vertical indents,
|
||||||
* so that if fits its parent.
|
* so that if fits its parent.
|
||||||
|
@ -897,6 +1044,33 @@ function getCameraVideoSize(videoWidth,
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
|
|
||||||
|
if(config.enableWelcomePage && window.location.pathname == "/")
|
||||||
|
{
|
||||||
|
$("#videoconference_page").hide();
|
||||||
|
$("#enter_room_button").click(function()
|
||||||
|
{
|
||||||
|
var val = Util.escapeHtml($("#enter_room_field").val());
|
||||||
|
window.location.pathname = "/" + val;
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#enter_room_field").keydown(function (event) {
|
||||||
|
if (event.keyCode === 13) {
|
||||||
|
var val = Util.escapeHtml(this.value);
|
||||||
|
window.location.pathname = "/" + val;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if(!config.isBrand)
|
||||||
|
{
|
||||||
|
$("#brand_logo").hide();
|
||||||
|
$("#brand_header").hide();
|
||||||
|
$("#header_text").hide();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#welcome_page").hide();
|
||||||
Chat.init();
|
Chat.init();
|
||||||
|
|
||||||
$('body').popover({ selector: '[data-toggle=popover]',
|
$('body').popover({ selector: '[data-toggle=popover]',
|
||||||
|
@ -960,10 +1134,10 @@ $(window).bind('beforeunload', function () {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
disposeConference();
|
disposeConference(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
function disposeConference() {
|
function disposeConference(onUnload) {
|
||||||
var handler = getConferenceHandler();
|
var handler = getConferenceHandler();
|
||||||
if (handler && handler.peerconnection) {
|
if (handler && handler.peerconnection) {
|
||||||
// FIXME: probably removing streams is not required and close() should be enough
|
// FIXME: probably removing streams is not required and close() should be enough
|
||||||
|
@ -975,10 +1149,9 @@ function disposeConference() {
|
||||||
}
|
}
|
||||||
handler.peerconnection.close();
|
handler.peerconnection.close();
|
||||||
}
|
}
|
||||||
if (statsCollector)
|
stopRTPStatsCollector();
|
||||||
{
|
if(onUnload) {
|
||||||
statsCollector.stop();
|
stopLocalRtpStatsCollector();
|
||||||
statsCollector = null;
|
|
||||||
}
|
}
|
||||||
focus = null;
|
focus = null;
|
||||||
activecall = null;
|
activecall = null;
|
||||||
|
@ -1055,6 +1228,10 @@ function setSharedKey(sKey) {
|
||||||
sharedKey = sKey;
|
sharedKey = sKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setRecordingToken(token) {
|
||||||
|
recordingToken = token;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the room invite url.
|
* Updates the room invite url.
|
||||||
*/
|
*/
|
||||||
|
@ -1115,6 +1292,28 @@ function setView(viewName) {
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hangUp() {
|
||||||
|
if (connection && connection.connected) {
|
||||||
|
// ensure signout
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: config.bosh,
|
||||||
|
async: false,
|
||||||
|
cache: false,
|
||||||
|
contentType: 'application/xml',
|
||||||
|
data: "<body rid='" + (connection.rid || connection._proto.rid) + "' xmlns='http://jabber.org/protocol/httpbind' sid='" + (connection.sid || connection._proto.sid) + "' type='terminate'><presence xmlns='jabber:client' type='unavailable'/></body>",
|
||||||
|
success: function (data) {
|
||||||
|
console.log('signed out');
|
||||||
|
console.log(data);
|
||||||
|
},
|
||||||
|
error: function (XMLHttpRequest, textStatus, errorThrown) {
|
||||||
|
console.log('signout error', textStatus + ' (' + errorThrown + ')');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
disposeConference(true);
|
||||||
|
}
|
||||||
|
|
||||||
$(document).bind('fatalError.jingle',
|
$(document).bind('fatalError.jingle',
|
||||||
function (event, session, error)
|
function (event, session, error)
|
||||||
{
|
{
|
||||||
|
@ -1124,3 +1323,31 @@ $(document).bind('fatalError.jingle',
|
||||||
"Your browser version is too old. Please update and try again...");
|
"Your browser version is too old. Please update and try again...");
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function callSipButtonClicked()
|
||||||
|
{
|
||||||
|
$.prompt('<h2>Enter SIP number</h2>' +
|
||||||
|
'<input id="sipNumber" type="text" value="" autofocus>',
|
||||||
|
{
|
||||||
|
persistent: false,
|
||||||
|
buttons: { "Dial": true, "Cancel": false},
|
||||||
|
defaultButton: 2,
|
||||||
|
loaded: function (event)
|
||||||
|
{
|
||||||
|
document.getElementById('sipNumber').focus();
|
||||||
|
},
|
||||||
|
submit: function (e, v, m, f)
|
||||||
|
{
|
||||||
|
if (v)
|
||||||
|
{
|
||||||
|
var numberInput = document.getElementById('sipNumber');
|
||||||
|
if (numberInput.value && numberInput.value.length)
|
||||||
|
{
|
||||||
|
connection.rayo.dial(
|
||||||
|
numberInput.value, 'fromnumber', roomName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,217 @@
|
||||||
|
/**
|
||||||
|
* The audio Levels plugin.
|
||||||
|
*/
|
||||||
|
var AudioLevels = (function(my) {
|
||||||
|
var CANVAS_EXTRA = 104;
|
||||||
|
var CANVAS_RADIUS = 7;
|
||||||
|
var SHADOW_COLOR = '#00ccff';
|
||||||
|
var audioLevelCanvasCache = {};
|
||||||
|
|
||||||
|
my.LOCAL_LEVEL = 'local';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the audio level canvas for the given peerJid. If the canvas
|
||||||
|
* didn't exist we create it.
|
||||||
|
*/
|
||||||
|
my.updateAudioLevelCanvas = function (peerJid) {
|
||||||
|
var resourceJid = null;
|
||||||
|
var videoSpanId = null;
|
||||||
|
if (!peerJid)
|
||||||
|
videoSpanId = 'localVideoContainer';
|
||||||
|
else {
|
||||||
|
resourceJid = Strophe.getResourceFromJid(peerJid);
|
||||||
|
|
||||||
|
videoSpanId = 'participant_' + resourceJid;
|
||||||
|
}
|
||||||
|
|
||||||
|
videoSpan = document.getElementById(videoSpanId);
|
||||||
|
|
||||||
|
if (!videoSpan) {
|
||||||
|
if (resourceJid)
|
||||||
|
console.error("No video element for jid", resourceJid);
|
||||||
|
else
|
||||||
|
console.error("No video element for local video.");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var audioLevelCanvas = $('#' + videoSpanId + '>canvas');
|
||||||
|
|
||||||
|
var videoSpaceWidth = $('#remoteVideos').width();
|
||||||
|
var thumbnailSize
|
||||||
|
= VideoLayout.calculateThumbnailSize(videoSpaceWidth);
|
||||||
|
var thumbnailWidth = thumbnailSize[0];
|
||||||
|
var thumbnailHeight = thumbnailSize[1];
|
||||||
|
|
||||||
|
if (!audioLevelCanvas || audioLevelCanvas.length === 0) {
|
||||||
|
|
||||||
|
audioLevelCanvas = document.createElement('canvas');
|
||||||
|
audioLevelCanvas.className = "audiolevel";
|
||||||
|
audioLevelCanvas.style.bottom = "-" + CANVAS_EXTRA/2 + "px";
|
||||||
|
audioLevelCanvas.style.left = "-" + CANVAS_EXTRA/2 + "px";
|
||||||
|
resizeAudioLevelCanvas( audioLevelCanvas,
|
||||||
|
thumbnailWidth,
|
||||||
|
thumbnailHeight);
|
||||||
|
|
||||||
|
videoSpan.appendChild(audioLevelCanvas);
|
||||||
|
} else {
|
||||||
|
audioLevelCanvas = audioLevelCanvas.get(0);
|
||||||
|
|
||||||
|
resizeAudioLevelCanvas( audioLevelCanvas,
|
||||||
|
thumbnailWidth,
|
||||||
|
thumbnailHeight);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the audio level UI for the given resourceJid.
|
||||||
|
*
|
||||||
|
* @param resourceJid the resource jid indicating the video element for
|
||||||
|
* which we draw the audio level
|
||||||
|
* @param audioLevel the newAudio level to render
|
||||||
|
*/
|
||||||
|
my.updateAudioLevel = function (resourceJid, audioLevel) {
|
||||||
|
drawAudioLevelCanvas(resourceJid, audioLevel);
|
||||||
|
|
||||||
|
var videoSpanId = getVideoSpanId(resourceJid);
|
||||||
|
|
||||||
|
var audioLevelCanvas = $('#' + videoSpanId + '>canvas').get(0);
|
||||||
|
|
||||||
|
if (!audioLevelCanvas)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var drawContext = audioLevelCanvas.getContext('2d');
|
||||||
|
|
||||||
|
var canvasCache = audioLevelCanvasCache[resourceJid];
|
||||||
|
|
||||||
|
drawContext.clearRect (0, 0,
|
||||||
|
audioLevelCanvas.width, audioLevelCanvas.height);
|
||||||
|
drawContext.drawImage(canvasCache, 0, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resizes the given audio level canvas to match the given thumbnail size.
|
||||||
|
*/
|
||||||
|
function resizeAudioLevelCanvas(audioLevelCanvas,
|
||||||
|
thumbnailWidth,
|
||||||
|
thumbnailHeight) {
|
||||||
|
audioLevelCanvas.width = thumbnailWidth + CANVAS_EXTRA;
|
||||||
|
audioLevelCanvas.height = thumbnailHeight + CANVAS_EXTRA;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws the audio level canvas into the cached canvas object.
|
||||||
|
*
|
||||||
|
* @param resourceJid the resource jid indicating the video element for
|
||||||
|
* which we draw the audio level
|
||||||
|
* @param audioLevel the newAudio level to render
|
||||||
|
*/
|
||||||
|
function drawAudioLevelCanvas(resourceJid, audioLevel) {
|
||||||
|
if (!audioLevelCanvasCache[resourceJid]) {
|
||||||
|
|
||||||
|
var videoSpanId = getVideoSpanId(resourceJid);
|
||||||
|
|
||||||
|
var audioLevelCanvasOrig = $('#' + videoSpanId + '>canvas').get(0);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FIXME Testing has shown that audioLevelCanvasOrig may not exist.
|
||||||
|
* In such a case, the method CanvasUtil.cloneCanvas may throw an
|
||||||
|
* error. Since audio levels are frequently updated, the errors have
|
||||||
|
* been observed to pile into the console, strain the CPU.
|
||||||
|
*/
|
||||||
|
if (audioLevelCanvasOrig)
|
||||||
|
{
|
||||||
|
audioLevelCanvasCache[resourceJid]
|
||||||
|
= CanvasUtil.cloneCanvas(audioLevelCanvasOrig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var canvas = audioLevelCanvasCache[resourceJid];
|
||||||
|
|
||||||
|
if (!canvas)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var drawContext = canvas.getContext('2d');
|
||||||
|
|
||||||
|
drawContext.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
var shadowLevel = getShadowLevel(audioLevel);
|
||||||
|
|
||||||
|
if (shadowLevel > 0)
|
||||||
|
// drawContext, x, y, w, h, r, shadowColor, shadowLevel
|
||||||
|
CanvasUtil.drawRoundRectGlow( drawContext,
|
||||||
|
CANVAS_EXTRA/2, CANVAS_EXTRA/2,
|
||||||
|
canvas.width - CANVAS_EXTRA,
|
||||||
|
canvas.height - CANVAS_EXTRA,
|
||||||
|
CANVAS_RADIUS,
|
||||||
|
SHADOW_COLOR,
|
||||||
|
shadowLevel);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the shadow/glow level for the given audio level.
|
||||||
|
*
|
||||||
|
* @param audioLevel the audio level from which we determine the shadow
|
||||||
|
* level
|
||||||
|
*/
|
||||||
|
function getShadowLevel (audioLevel) {
|
||||||
|
var shadowLevel = 0;
|
||||||
|
|
||||||
|
if (audioLevel <= 0.3) {
|
||||||
|
shadowLevel = Math.round(CANVAS_EXTRA/2*(audioLevel/0.3));
|
||||||
|
}
|
||||||
|
else if (audioLevel <= 0.6) {
|
||||||
|
shadowLevel = Math.round(CANVAS_EXTRA/2*((audioLevel - 0.3) / 0.3));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
shadowLevel = Math.round(CANVAS_EXTRA/2*((audioLevel - 0.6) / 0.4));
|
||||||
|
}
|
||||||
|
return shadowLevel;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the video span id corresponding to the given resourceJid or local
|
||||||
|
* user.
|
||||||
|
*/
|
||||||
|
function getVideoSpanId(resourceJid) {
|
||||||
|
var videoSpanId = null;
|
||||||
|
if (resourceJid === AudioLevels.LOCAL_LEVEL
|
||||||
|
|| (connection.emuc.myroomjid && resourceJid
|
||||||
|
=== Strophe.getResourceFromJid(connection.emuc.myroomjid)))
|
||||||
|
videoSpanId = 'localVideoContainer';
|
||||||
|
else
|
||||||
|
videoSpanId = 'participant_' + resourceJid;
|
||||||
|
|
||||||
|
return videoSpanId;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that the remote video has been resized.
|
||||||
|
*/
|
||||||
|
$(document).bind('remotevideo.resized', function (event, width, height) {
|
||||||
|
var resized = false;
|
||||||
|
$('#remoteVideos>span>canvas').each(function() {
|
||||||
|
var canvas = $(this).get(0);
|
||||||
|
if (canvas.width !== width + CANVAS_EXTRA) {
|
||||||
|
canvas.width = width + CANVAS_EXTRA;
|
||||||
|
resized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canvas.heigh !== height + CANVAS_EXTRA) {
|
||||||
|
canvas.height = height + CANVAS_EXTRA;
|
||||||
|
resized = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (resized)
|
||||||
|
Object.keys(audioLevelCanvasCache).forEach(function (resourceJid) {
|
||||||
|
audioLevelCanvasCache[resourceJid].width
|
||||||
|
= width + CANVAS_EXTRA;
|
||||||
|
audioLevelCanvasCache[resourceJid].height
|
||||||
|
= height + CANVAS_EXTRA;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return my;
|
||||||
|
|
||||||
|
})(AudioLevels || {});
|
|
@ -0,0 +1,32 @@
|
||||||
|
var BottomToolbar = (function (my) {
|
||||||
|
my.toggleChat = function() {
|
||||||
|
if (ContactList.isVisible()) {
|
||||||
|
buttonClick("#contactListButton", "active");
|
||||||
|
ContactList.toggleContactList();
|
||||||
|
}
|
||||||
|
|
||||||
|
buttonClick("#chatBottomButton", "active");
|
||||||
|
|
||||||
|
Chat.toggleChat();
|
||||||
|
};
|
||||||
|
|
||||||
|
my.toggleContactList = function() {
|
||||||
|
if (Chat.isVisible()) {
|
||||||
|
buttonClick("#chatBottomButton", "active");
|
||||||
|
Chat.toggleChat();
|
||||||
|
}
|
||||||
|
|
||||||
|
buttonClick("#contactListButton", "active");
|
||||||
|
|
||||||
|
ContactList.toggleContactList();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
$(document).bind("remotevideo.resized", function (event, width, height) {
|
||||||
|
var bottom = (height - $('#bottomToolbar').outerHeight())/2 + 18;
|
||||||
|
|
||||||
|
$('#bottomToolbar').css({bottom: bottom + 'px'});
|
||||||
|
});
|
||||||
|
|
||||||
|
return my;
|
||||||
|
}(BottomToolbar || {}));
|
|
@ -0,0 +1,109 @@
|
||||||
|
/**
|
||||||
|
* Utility class for drawing canvas shapes.
|
||||||
|
*/
|
||||||
|
var CanvasUtil = (function(my) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws a round rectangle with a glow. The glowWidth indicates the depth
|
||||||
|
* of the glow.
|
||||||
|
*
|
||||||
|
* @param drawContext the context of the canvas to draw to
|
||||||
|
* @param x the x coordinate of the round rectangle
|
||||||
|
* @param y the y coordinate of the round rectangle
|
||||||
|
* @param w the width of the round rectangle
|
||||||
|
* @param h the height of the round rectangle
|
||||||
|
* @param glowColor the color of the glow
|
||||||
|
* @param glowWidth the width of the glow
|
||||||
|
*/
|
||||||
|
my.drawRoundRectGlow
|
||||||
|
= function(drawContext, x, y, w, h, r, glowColor, glowWidth) {
|
||||||
|
|
||||||
|
// Save the previous state of the context.
|
||||||
|
drawContext.save();
|
||||||
|
|
||||||
|
if (w < 2 * r) r = w / 2;
|
||||||
|
if (h < 2 * r) r = h / 2;
|
||||||
|
|
||||||
|
// Draw a round rectangle.
|
||||||
|
drawContext.beginPath();
|
||||||
|
drawContext.moveTo(x+r, y);
|
||||||
|
drawContext.arcTo(x+w, y, x+w, y+h, r);
|
||||||
|
drawContext.arcTo(x+w, y+h, x, y+h, r);
|
||||||
|
drawContext.arcTo(x, y+h, x, y, r);
|
||||||
|
drawContext.arcTo(x, y, x+w, y, r);
|
||||||
|
drawContext.closePath();
|
||||||
|
|
||||||
|
// Add a shadow around the rectangle
|
||||||
|
drawContext.shadowColor = glowColor;
|
||||||
|
drawContext.shadowBlur = glowWidth;
|
||||||
|
drawContext.shadowOffsetX = 0;
|
||||||
|
drawContext.shadowOffsetY = 0;
|
||||||
|
|
||||||
|
// Fill the shape.
|
||||||
|
drawContext.fill();
|
||||||
|
|
||||||
|
drawContext.save();
|
||||||
|
|
||||||
|
drawContext.restore();
|
||||||
|
|
||||||
|
// 1) Uncomment this line to use Composite Operation, which is doing the
|
||||||
|
// same as the clip function below and is also antialiasing the round
|
||||||
|
// border, but is said to be less fast performance wise.
|
||||||
|
|
||||||
|
// drawContext.globalCompositeOperation='destination-out';
|
||||||
|
|
||||||
|
drawContext.beginPath();
|
||||||
|
drawContext.moveTo(x+r, y);
|
||||||
|
drawContext.arcTo(x+w, y, x+w, y+h, r);
|
||||||
|
drawContext.arcTo(x+w, y+h, x, y+h, r);
|
||||||
|
drawContext.arcTo(x, y+h, x, y, r);
|
||||||
|
drawContext.arcTo(x, y, x+w, y, r);
|
||||||
|
drawContext.closePath();
|
||||||
|
|
||||||
|
// 2) Uncomment this line to use Composite Operation, which is doing the
|
||||||
|
// same as the clip function below and is also antialiasing the round
|
||||||
|
// border, but is said to be less fast performance wise.
|
||||||
|
|
||||||
|
// drawContext.fill();
|
||||||
|
|
||||||
|
// Comment these two lines if choosing to do the same with composite
|
||||||
|
// operation above 1 and 2.
|
||||||
|
drawContext.clip();
|
||||||
|
drawContext.clearRect(0, 0, 277, 200);
|
||||||
|
|
||||||
|
// Restore the previous context state.
|
||||||
|
drawContext.restore();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clones the given canvas.
|
||||||
|
*
|
||||||
|
* @return the new cloned canvas.
|
||||||
|
*/
|
||||||
|
my.cloneCanvas = function (oldCanvas) {
|
||||||
|
/*
|
||||||
|
* FIXME Testing has shown that oldCanvas may not exist. In such a case,
|
||||||
|
* the method CanvasUtil.cloneCanvas may throw an error. Since audio
|
||||||
|
* levels are frequently updated, the errors have been observed to pile
|
||||||
|
* into the console, strain the CPU.
|
||||||
|
*/
|
||||||
|
if (!oldCanvas)
|
||||||
|
return oldCanvas;
|
||||||
|
|
||||||
|
//create a new canvas
|
||||||
|
var newCanvas = document.createElement('canvas');
|
||||||
|
var context = newCanvas.getContext('2d');
|
||||||
|
|
||||||
|
//set dimensions
|
||||||
|
newCanvas.width = oldCanvas.width;
|
||||||
|
newCanvas.height = oldCanvas.height;
|
||||||
|
|
||||||
|
//apply the old canvas to the new one
|
||||||
|
context.drawImage(oldCanvas, 0, 0);
|
||||||
|
|
||||||
|
//return the new canvas
|
||||||
|
return newCanvas;
|
||||||
|
};
|
||||||
|
|
||||||
|
return my;
|
||||||
|
})(CanvasUtil || {});
|
55
chat.js
|
@ -80,7 +80,7 @@ var Chat = (function (my) {
|
||||||
else {
|
else {
|
||||||
divClassName = "remoteuser";
|
divClassName = "remoteuser";
|
||||||
|
|
||||||
if (!$('#chatspace').is(":visible")) {
|
if (!Chat.isVisible()) {
|
||||||
unreadMessages++;
|
unreadMessages++;
|
||||||
Util.playSoundNotification('chatNotification');
|
Util.playSoundNotification('chatNotification');
|
||||||
setVisualNotification(true);
|
setVisualNotification(true);
|
||||||
|
@ -115,8 +115,7 @@ var Chat = (function (my) {
|
||||||
+ '</div>');
|
+ '</div>');
|
||||||
$('#chatconversation').animate(
|
$('#chatconversation').animate(
|
||||||
{ scrollTop: $('#chatconversation')[0].scrollHeight}, 1000);
|
{ scrollTop: $('#chatconversation')[0].scrollHeight}, 1000);
|
||||||
|
};
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the subject to the UI
|
* Sets the subject to the UI
|
||||||
|
@ -135,8 +134,7 @@ var Chat = (function (my) {
|
||||||
{
|
{
|
||||||
$("#subject").css({display: "block"});
|
$("#subject").css({display: "block"});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens / closes the chat area.
|
* Opens / closes the chat area.
|
||||||
|
@ -159,6 +157,10 @@ var Chat = (function (my) {
|
||||||
var horizontalIndent = videoPosition[0];
|
var horizontalIndent = videoPosition[0];
|
||||||
var verticalIndent = videoPosition[1];
|
var verticalIndent = videoPosition[1];
|
||||||
|
|
||||||
|
var thumbnailSize = VideoLayout.calculateThumbnailSize(videospaceWidth);
|
||||||
|
var thumbnailsWidth = thumbnailSize[0];
|
||||||
|
var thumbnailsHeight = thumbnailSize[1];
|
||||||
|
|
||||||
if (chatspace.is(":visible")) {
|
if (chatspace.is(":visible")) {
|
||||||
videospace.animate({right: chatSize[0],
|
videospace.animate({right: chatSize[0],
|
||||||
width: videospaceWidth,
|
width: videospaceWidth,
|
||||||
|
@ -166,6 +168,21 @@ var Chat = (function (my) {
|
||||||
{queue: false,
|
{queue: false,
|
||||||
duration: 500});
|
duration: 500});
|
||||||
|
|
||||||
|
$('#remoteVideos').animate({height: thumbnailsHeight},
|
||||||
|
{queue: false,
|
||||||
|
duration: 500});
|
||||||
|
|
||||||
|
$('#remoteVideos>span').animate({height: thumbnailsHeight,
|
||||||
|
width: thumbnailsWidth},
|
||||||
|
{queue: false,
|
||||||
|
duration: 500,
|
||||||
|
complete: function() {
|
||||||
|
$(document).trigger(
|
||||||
|
"remotevideo.resized",
|
||||||
|
[thumbnailsWidth,
|
||||||
|
thumbnailsHeight]);
|
||||||
|
}});
|
||||||
|
|
||||||
$('#largeVideoContainer').animate({ width: videospaceWidth,
|
$('#largeVideoContainer').animate({ width: videospaceWidth,
|
||||||
height: videospaceHeight},
|
height: videospaceHeight},
|
||||||
{queue: false,
|
{queue: false,
|
||||||
|
@ -187,6 +204,11 @@ var Chat = (function (my) {
|
||||||
duration: 500});
|
duration: 500});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
// Undock the toolbar when the chat is shown and if we're in a
|
||||||
|
// video mode.
|
||||||
|
if (VideoLayout.isLargeVideoVisible())
|
||||||
|
Toolbar.dockToolbar(false);
|
||||||
|
|
||||||
videospace.animate({right: chatSize[0],
|
videospace.animate({right: chatSize[0],
|
||||||
width: videospaceWidth,
|
width: videospaceWidth,
|
||||||
height: videospaceHeight},
|
height: videospaceHeight},
|
||||||
|
@ -198,6 +220,20 @@ var Chat = (function (my) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('#remoteVideos').animate({height: thumbnailsHeight},
|
||||||
|
{queue: false,
|
||||||
|
duration: 500});
|
||||||
|
|
||||||
|
$('#remoteVideos>span').animate({height: thumbnailsHeight,
|
||||||
|
width: thumbnailsWidth},
|
||||||
|
{queue: false,
|
||||||
|
duration: 500,
|
||||||
|
complete: function() {
|
||||||
|
$(document).trigger(
|
||||||
|
"remotevideo.resized",
|
||||||
|
[thumbnailsWidth, thumbnailsHeight]);
|
||||||
|
}});
|
||||||
|
|
||||||
$('#largeVideoContainer').animate({ width: videospaceWidth,
|
$('#largeVideoContainer').animate({ width: videospaceWidth,
|
||||||
height: videospaceHeight},
|
height: videospaceHeight},
|
||||||
{queue: false,
|
{queue: false,
|
||||||
|
@ -265,6 +301,13 @@ var Chat = (function (my) {
|
||||||
return [chatWidth, availableHeight];
|
return [chatWidth, availableHeight];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the chat is currently visible.
|
||||||
|
*/
|
||||||
|
my.isVisible = function () {
|
||||||
|
return $('#chatspace').is(":visible");
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resizes the chat conversation.
|
* Resizes the chat conversation.
|
||||||
*/
|
*/
|
||||||
|
@ -290,7 +333,7 @@ var Chat = (function (my) {
|
||||||
if (unreadMessages) {
|
if (unreadMessages) {
|
||||||
unreadMsgElement.innerHTML = unreadMessages.toString();
|
unreadMsgElement.innerHTML = unreadMessages.toString();
|
||||||
|
|
||||||
Toolbar.showToolbar();
|
Toolbar.dockToolbar(true);
|
||||||
|
|
||||||
var chatButtonElement
|
var chatButtonElement
|
||||||
= document.getElementById('chatButton').parentNode;
|
= document.getElementById('chatButton').parentNode;
|
||||||
|
|
22
config.js
|
@ -1,17 +1,25 @@
|
||||||
var config = {
|
var config = {
|
||||||
hosts: {
|
hosts: {
|
||||||
domain: 'guest.jit.si',
|
domain: 'jitsi-meet.example.com',
|
||||||
muc: 'meet.jit.si', // FIXME: use XEP-0030
|
//anonymousdomain: 'guest.example.com',
|
||||||
bridge: 'jitsi-videobridge.lambada.jitsi.net' // FIXME: use XEP-0030
|
muc: 'conference.jitsi-meet.example.com', // FIXME: use XEP-0030
|
||||||
|
bridge: 'jitsi-videobridge.jitsi-meet.example.com', // FIXME: use XEP-0030
|
||||||
|
//call_control: 'callcontrol.jitsi-meet.example.com'
|
||||||
},
|
},
|
||||||
// getroomnode: function (path) { return 'someprefixpossiblybasedonpath'; },
|
// getroomnode: function (path) { return 'someprefixpossiblybasedonpath'; },
|
||||||
// useStunTurn: true, // use XEP-0215 to fetch STUN and TURN server
|
// useStunTurn: true, // use XEP-0215 to fetch STUN and TURN server
|
||||||
// useIPv6: true, // ipv6 support. use at your own risk
|
// useIPv6: true, // ipv6 support. use at your own risk
|
||||||
useNicks: false,
|
useNicks: false,
|
||||||
bosh: '//lambada.jitsi.net/http-bind', // FIXME: use xep-0156 for that
|
bosh: '//jitsi-meet.example.com/http-bind', // FIXME: use xep-0156 for that
|
||||||
desktopSharing: 'ext', // Desktop sharing method. Can be set to 'ext', 'webrtc' or false to disable.
|
desktopSharing: 'ext', // Desktop sharing method. Can be set to 'ext', 'webrtc' or false to disable.
|
||||||
chromeExtensionId: 'diibjkoicjeejcmhdnailmkgecihlobk', // Id of desktop streamer Chrome extension
|
chromeExtensionId: 'diibjkoicjeejcmhdnailmkgecihlobk', // Id of desktop streamer Chrome extension
|
||||||
minChromeExtVersion: '0.1', // Required version of Chrome extension
|
minChromeExtVersion: '0.1', // Required version of Chrome extension
|
||||||
enableRtpStats: false, // Enables RTP stats processing
|
enableRtpStats: true, // Enables RTP stats processing
|
||||||
openSctp: true //Toggle to enable/disable SCTP channels
|
openSctp: true, // Toggle to enable/disable SCTP channels
|
||||||
};
|
channelLastN: -1, // The default value of the channel attribute last-n.
|
||||||
|
// useRtcpMux: true,
|
||||||
|
// useBundle: true,
|
||||||
|
enableRecording: false,
|
||||||
|
enableWelcomePage: false,
|
||||||
|
isBrand: false
|
||||||
|
};
|
||||||
|
|
|
@ -0,0 +1,235 @@
|
||||||
|
/**
|
||||||
|
* Contact list.
|
||||||
|
*/
|
||||||
|
var ContactList = (function (my) {
|
||||||
|
/**
|
||||||
|
* Indicates if the chat is currently visible.
|
||||||
|
*
|
||||||
|
* @return <tt>true</tt> if the chat is currently visible, <tt>false</tt> -
|
||||||
|
* otherwise
|
||||||
|
*/
|
||||||
|
my.isVisible = function () {
|
||||||
|
return $('#contactlist').is(":visible");
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a contact for the given peerJid if such doesn't yet exist.
|
||||||
|
*
|
||||||
|
* @param peerJid the peerJid corresponding to the contact
|
||||||
|
*/
|
||||||
|
my.ensureAddContact = function(peerJid) {
|
||||||
|
var resourceJid = Strophe.getResourceFromJid(peerJid);
|
||||||
|
|
||||||
|
var contact = $('#contactlist>ul>li[id="' + resourceJid + '"]');
|
||||||
|
|
||||||
|
if (!contact || contact.length <= 0)
|
||||||
|
ContactList.addContact(peerJid);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a contact for the given peer jid.
|
||||||
|
*
|
||||||
|
* @param peerJid the jid of the contact to add
|
||||||
|
*/
|
||||||
|
my.addContact = function(peerJid) {
|
||||||
|
var resourceJid = Strophe.getResourceFromJid(peerJid);
|
||||||
|
|
||||||
|
var contactlist = $('#contactlist>ul');
|
||||||
|
|
||||||
|
var newContact = document.createElement('li');
|
||||||
|
newContact.id = resourceJid;
|
||||||
|
|
||||||
|
newContact.appendChild(createAvatar());
|
||||||
|
newContact.appendChild(createDisplayNameParagraph("Participant"));
|
||||||
|
|
||||||
|
var clElement = contactlist.get(0);
|
||||||
|
|
||||||
|
if (resourceJid === Strophe.getResourceFromJid(connection.emuc.myroomjid)
|
||||||
|
&& $('#contactlist>ul .title')[0].nextSibling.nextSibling)
|
||||||
|
{
|
||||||
|
clElement.insertBefore(newContact,
|
||||||
|
$('#contactlist>ul .title')[0].nextSibling.nextSibling);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
clElement.appendChild(newContact);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a contact for the given peer jid.
|
||||||
|
*
|
||||||
|
* @param peerJid the peerJid corresponding to the contact to remove
|
||||||
|
*/
|
||||||
|
my.removeContact = function(peerJid) {
|
||||||
|
var resourceJid = Strophe.getResourceFromJid(peerJid);
|
||||||
|
|
||||||
|
var contact = $('#contactlist>ul>li[id="' + resourceJid + '"]');
|
||||||
|
|
||||||
|
if (contact && contact.length > 0) {
|
||||||
|
var contactlist = $('#contactlist>ul');
|
||||||
|
|
||||||
|
contactlist.get(0).removeChild(contact.get(0));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens / closes the contact list area.
|
||||||
|
*/
|
||||||
|
my.toggleContactList = function () {
|
||||||
|
var contactlist = $('#contactlist');
|
||||||
|
var videospace = $('#videospace');
|
||||||
|
|
||||||
|
var chatSize = (ContactList.isVisible()) ? [0, 0] : Chat.getChatSize();
|
||||||
|
var videospaceWidth = window.innerWidth - chatSize[0];
|
||||||
|
var videospaceHeight = window.innerHeight;
|
||||||
|
var videoSize
|
||||||
|
= getVideoSize(null, null, videospaceWidth, videospaceHeight);
|
||||||
|
var videoWidth = videoSize[0];
|
||||||
|
var videoHeight = videoSize[1];
|
||||||
|
var videoPosition = getVideoPosition(videoWidth,
|
||||||
|
videoHeight,
|
||||||
|
videospaceWidth,
|
||||||
|
videospaceHeight);
|
||||||
|
var horizontalIndent = videoPosition[0];
|
||||||
|
var verticalIndent = videoPosition[1];
|
||||||
|
|
||||||
|
var thumbnailSize = VideoLayout.calculateThumbnailSize(videospaceWidth);
|
||||||
|
var thumbnailsWidth = thumbnailSize[0];
|
||||||
|
var thumbnailsHeight = thumbnailSize[1];
|
||||||
|
|
||||||
|
if (ContactList.isVisible()) {
|
||||||
|
videospace.animate({right: chatSize[0],
|
||||||
|
width: videospaceWidth,
|
||||||
|
height: videospaceHeight},
|
||||||
|
{queue: false,
|
||||||
|
duration: 500});
|
||||||
|
|
||||||
|
$('#remoteVideos').animate({height: thumbnailsHeight},
|
||||||
|
{queue: false,
|
||||||
|
duration: 500});
|
||||||
|
|
||||||
|
$('#remoteVideos>span').animate({height: thumbnailsHeight,
|
||||||
|
width: thumbnailsWidth},
|
||||||
|
{queue: false,
|
||||||
|
duration: 500,
|
||||||
|
complete: function() {
|
||||||
|
$(document).trigger(
|
||||||
|
"remotevideo.resized",
|
||||||
|
[thumbnailsWidth,
|
||||||
|
thumbnailsHeight]);
|
||||||
|
}});
|
||||||
|
|
||||||
|
$('#largeVideoContainer').animate({ width: videospaceWidth,
|
||||||
|
height: videospaceHeight},
|
||||||
|
{queue: false,
|
||||||
|
duration: 500
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#largeVideo').animate({ width: videoWidth,
|
||||||
|
height: videoHeight,
|
||||||
|
top: verticalIndent,
|
||||||
|
bottom: verticalIndent,
|
||||||
|
left: horizontalIndent,
|
||||||
|
right: horizontalIndent},
|
||||||
|
{ queue: false,
|
||||||
|
duration: 500
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#contactlist').hide("slide", { direction: "right",
|
||||||
|
queue: false,
|
||||||
|
duration: 500});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Undock the toolbar when the chat is shown and if we're in a
|
||||||
|
// video mode.
|
||||||
|
if (VideoLayout.isLargeVideoVisible())
|
||||||
|
Toolbar.dockToolbar(false);
|
||||||
|
|
||||||
|
videospace.animate({right: chatSize[0],
|
||||||
|
width: videospaceWidth,
|
||||||
|
height: videospaceHeight},
|
||||||
|
{queue: false,
|
||||||
|
duration: 500,
|
||||||
|
complete: function () {
|
||||||
|
contactlist.trigger('shown');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#remoteVideos').animate({height: thumbnailsHeight},
|
||||||
|
{queue: false,
|
||||||
|
duration: 500});
|
||||||
|
|
||||||
|
$('#remoteVideos>span').animate({height: thumbnailsHeight,
|
||||||
|
width: thumbnailsWidth},
|
||||||
|
{queue: false,
|
||||||
|
duration: 500,
|
||||||
|
complete: function() {
|
||||||
|
$(document).trigger(
|
||||||
|
"remotevideo.resized",
|
||||||
|
[thumbnailsWidth, thumbnailsHeight]);
|
||||||
|
}});
|
||||||
|
|
||||||
|
$('#largeVideoContainer').animate({ width: videospaceWidth,
|
||||||
|
height: videospaceHeight},
|
||||||
|
{queue: false,
|
||||||
|
duration: 500
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#largeVideo').animate({ width: videoWidth,
|
||||||
|
height: videoHeight,
|
||||||
|
top: verticalIndent,
|
||||||
|
bottom: verticalIndent,
|
||||||
|
left: horizontalIndent,
|
||||||
|
right: horizontalIndent},
|
||||||
|
{queue: false,
|
||||||
|
duration: 500
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#contactlist').show("slide", { direction: "right",
|
||||||
|
queue: false,
|
||||||
|
duration: 500});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the avatar element.
|
||||||
|
*
|
||||||
|
* @return the newly created avatar element
|
||||||
|
*/
|
||||||
|
function createAvatar() {
|
||||||
|
var avatar = document.createElement('i');
|
||||||
|
avatar.className = "icon-avatar avatar";
|
||||||
|
|
||||||
|
return avatar;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the display name paragraph.
|
||||||
|
*
|
||||||
|
* @param displayName the display name to set
|
||||||
|
*/
|
||||||
|
function createDisplayNameParagraph(displayName) {
|
||||||
|
var p = document.createElement('p');
|
||||||
|
p.innerHTML = displayName;
|
||||||
|
|
||||||
|
return p;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that the display name has changed.
|
||||||
|
*/
|
||||||
|
$(document).bind( 'displaynamechanged',
|
||||||
|
function (event, peerJid, displayName) {
|
||||||
|
if (peerJid === 'localVideoContainer')
|
||||||
|
peerJid = connection.emuc.myroomjid;
|
||||||
|
|
||||||
|
var resourceJid = Strophe.getResourceFromJid(peerJid);
|
||||||
|
|
||||||
|
var contactName = $('#contactlist #' + resourceJid + '>p');
|
||||||
|
|
||||||
|
if (contactName && displayName && displayName.length > 0)
|
||||||
|
contactName.html(displayName);
|
||||||
|
});
|
||||||
|
|
||||||
|
return my;
|
||||||
|
}(ContactList || {}));
|
|
@ -0,0 +1,35 @@
|
||||||
|
#contactlist {
|
||||||
|
background-color:rgba(0,0,0,.65);
|
||||||
|
}
|
||||||
|
|
||||||
|
#contactlist>ul {
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#contactlist>ul>li {
|
||||||
|
list-style-type: none;
|
||||||
|
text-align: left;
|
||||||
|
color: #FFF;
|
||||||
|
font-size: 10pt;
|
||||||
|
padding: 8px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#contactlist>ul>li>p {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#contactlist>ul>li.title {
|
||||||
|
color: #00ccff;
|
||||||
|
font-size: 11pt;
|
||||||
|
border-bottom: 1px solid #676767;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
padding: 0px;
|
||||||
|
margin-right: 10px;
|
||||||
|
vertical-align: middle;
|
||||||
|
font-size: 22pt;
|
||||||
|
}
|
22
css/font.css
|
@ -23,7 +23,24 @@
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
.icon-contactList:before {
|
||||||
|
content: "\e615";
|
||||||
|
}
|
||||||
|
.icon-avatar:before {
|
||||||
|
content: "\e616";
|
||||||
|
}
|
||||||
|
.icon-callRetro:before {
|
||||||
|
content: "\e611";
|
||||||
|
}
|
||||||
|
.icon-callModern:before {
|
||||||
|
content: "\e612";
|
||||||
|
}
|
||||||
|
.icon-recDisable:before {
|
||||||
|
content: "\e613";
|
||||||
|
}
|
||||||
|
.icon-recEnable:before {
|
||||||
|
content: "\e614";
|
||||||
|
}
|
||||||
.icon-kick1:before {
|
.icon-kick1:before {
|
||||||
content: "\e60f";
|
content: "\e60f";
|
||||||
}
|
}
|
||||||
|
@ -60,6 +77,9 @@
|
||||||
.icon-share-doc:before {
|
.icon-share-doc:before {
|
||||||
content: "\e605";
|
content: "\e605";
|
||||||
}
|
}
|
||||||
|
.icon-telephone:before {
|
||||||
|
content: "\e611";
|
||||||
|
}
|
||||||
.icon-security-locked:before {
|
.icon-security-locked:before {
|
||||||
content: "\e607";
|
content: "\e607";
|
||||||
}
|
}
|
||||||
|
|
254
css/main.css
|
@ -8,7 +8,8 @@ html, body{
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
#chatspace {
|
#chatspace,
|
||||||
|
#contactlist {
|
||||||
display:none;
|
display:none;
|
||||||
position:absolute;
|
position:absolute;
|
||||||
float: right;
|
float: right;
|
||||||
|
@ -18,12 +19,14 @@ html, body{
|
||||||
width: 20%;
|
width: 20%;
|
||||||
max-width: 200px;
|
max-width: 200px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
/* background-color:#dfebf1;*/
|
|
||||||
background-color:#FFFFFF;
|
|
||||||
border-left:1px solid #424242;
|
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#chatspace {
|
||||||
|
background-color:#FFF;
|
||||||
|
border-left:1px solid #424242;
|
||||||
|
}
|
||||||
|
|
||||||
#chatconversation {
|
#chatconversation {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -131,18 +134,49 @@ html, body{
|
||||||
}
|
}
|
||||||
|
|
||||||
#chatButton {
|
#chatButton {
|
||||||
-webkit-transition: all .5s ease-in-out;;
|
-webkit-transition: all .5s ease-in-out;
|
||||||
-moz-transition: all .5s ease-in-out;;
|
-moz-transition: all .5s ease-in-out;
|
||||||
transition: all .5s ease-in-out;;
|
transition: all .5s ease-in-out;
|
||||||
}
|
}
|
||||||
|
/*#ffde00*/
|
||||||
#chatButton.active {
|
#chatButton.active {
|
||||||
-webkit-text-shadow: 0 0 10px #ffffff;
|
-webkit-text-shadow: -1px 0 10px #00ccff,
|
||||||
-moz-text-shadow: 0 0 10px #ffffff;
|
0 1px 10px #00ccff,
|
||||||
text-shadow: 0 0 10px #ffffff;
|
1px 0 10px #00ccff,
|
||||||
|
0 -1px 10px #00ccff;
|
||||||
|
-moz-text-shadow: 1px 0 10px #00ccff,
|
||||||
|
0 1px 10px #00ccff,
|
||||||
|
1px 0 10px #00ccff,
|
||||||
|
0 -1px 10px #00ccff;
|
||||||
|
text-shadow: -1px 0 10px #00ccff,
|
||||||
|
0 1px 10px #00ccff,
|
||||||
|
1px 0 10px #00ccff,
|
||||||
|
0 -1px 10px #00ccff;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.button:hover {
|
#recordButton {
|
||||||
|
-webkit-transition: all .5s ease-in-out;
|
||||||
|
-moz-transition: all .5s ease-in-out;
|
||||||
|
transition: all .5s ease-in-out;
|
||||||
|
}
|
||||||
|
/*#ffde00*/
|
||||||
|
#recordButton.active {
|
||||||
|
-webkit-text-shadow: -1px 0 10px #00ccff,
|
||||||
|
0 1px 10px #00ccff,
|
||||||
|
1px 0 10px #00ccff,
|
||||||
|
0 -1px 10px #00ccff;
|
||||||
|
-moz-text-shadow: 1px 0 10px #00ccff,
|
||||||
|
0 1px 10px #00ccff,
|
||||||
|
1px 0 10px #00ccff,
|
||||||
|
0 -1px 10px #00ccff;
|
||||||
|
text-shadow: -1px 0 10px #00ccff,
|
||||||
|
0 1px 10px #00ccff,
|
||||||
|
1px 0 10px #00ccff,
|
||||||
|
0 -1px 10px #00ccff;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.button:hover,
|
||||||
|
a.bottomToolbarButton:hover {
|
||||||
top: 0;
|
top: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background: rgba(0, 0, 0, 0.3);
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
@ -165,7 +199,7 @@ a.button:hover {
|
||||||
background: #676767;
|
background: #676767;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type='text'], textarea {
|
input[type='text'], input[type='password'], textarea {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
|
@ -181,7 +215,7 @@ input[type='text'], textarea {
|
||||||
resize: none; /* prevents the user-resizing, adjust to taste */
|
resize: none; /* prevents the user-resizing, adjust to taste */
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type='text'], textarea:focus {
|
input[type='text'], input[type='password'], textarea:focus {
|
||||||
box-shadow: inset 0 0 3px 2px #ACD8F0; /* provides a more style-able
|
box-shadow: inset 0 0 3px 2px #ACD8F0; /* provides a more style-able
|
||||||
replacement to the outline */
|
replacement to the outline */
|
||||||
}
|
}
|
||||||
|
@ -229,3 +263,195 @@ form {
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#enter_room_field {
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 16px;
|
||||||
|
padding: 15px 55px 10px 30px;
|
||||||
|
border: none;
|
||||||
|
-moz-border-radius: 10px;
|
||||||
|
-webkit-border-radius: 10px;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
width: 318px;
|
||||||
|
height: 55px;
|
||||||
|
position:absolute;
|
||||||
|
font-weight: 500;
|
||||||
|
font-family: Helvetica;
|
||||||
|
box-shadow: none;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#enter_room_button {
|
||||||
|
width: 73px;
|
||||||
|
height: 45px;
|
||||||
|
background-color: #16a8fe;
|
||||||
|
moz-border-radius: 15px;
|
||||||
|
-webkit-border-radius: 15px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: 600;
|
||||||
|
border: none;
|
||||||
|
position:absolute;
|
||||||
|
margin-left: 240px;
|
||||||
|
margin-top: 5px;
|
||||||
|
font-size: 19px;
|
||||||
|
font-family: Helvetica;
|
||||||
|
padding-top: 6px;
|
||||||
|
z-index: 2;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#enter_room {
|
||||||
|
margin: 70px auto 0px auto;
|
||||||
|
width:318px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#welcome_page_header
|
||||||
|
{
|
||||||
|
background-image: url(../images/welcome_page/pattern-header.png);
|
||||||
|
height: 290px;
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
#welcome_page_main
|
||||||
|
{
|
||||||
|
background-image:url(../images/welcome_page/pattern-body.png);
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
margin-top: 290px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#jitsi_logo
|
||||||
|
{
|
||||||
|
background-image:url(../images/welcome_page/jitsi-logo.png);
|
||||||
|
width: 186px;
|
||||||
|
height: 74px;
|
||||||
|
position: absolute;
|
||||||
|
top: 15px;
|
||||||
|
left: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#brand_logo
|
||||||
|
{
|
||||||
|
background-image:url(../images/welcome_page/brand-logo.png);
|
||||||
|
width: 215px;
|
||||||
|
height: 55px;
|
||||||
|
position: absolute;
|
||||||
|
top: 15px;
|
||||||
|
right: 30px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#brand_header
|
||||||
|
{
|
||||||
|
background-image:url(../images/welcome_page/header-big.png);
|
||||||
|
position:absolute;
|
||||||
|
width: 583px;
|
||||||
|
height: 274px;
|
||||||
|
left: 340px;
|
||||||
|
top:15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header_text
|
||||||
|
{
|
||||||
|
position: absolute;
|
||||||
|
left: 200px;
|
||||||
|
top: 150px;
|
||||||
|
width: 885px;
|
||||||
|
height: 100px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-family: Helvetica;
|
||||||
|
font-size: 24px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#features
|
||||||
|
{
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature_row
|
||||||
|
{
|
||||||
|
width: 100%;
|
||||||
|
left: 115px;
|
||||||
|
position: relative;
|
||||||
|
float: left;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature_holder
|
||||||
|
{
|
||||||
|
float:left;
|
||||||
|
width: 169px;
|
||||||
|
padding-left: 75px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature_icon
|
||||||
|
{
|
||||||
|
background-image:url(../images/welcome_page/bubble.png);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
width: 169px;
|
||||||
|
height: 169px;
|
||||||
|
font-family: Helvetica;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 22px;
|
||||||
|
/*font-weight: bold;*/
|
||||||
|
text-align: center;
|
||||||
|
display: table-cell;
|
||||||
|
padding: 50px 29px 0px 17px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature_description
|
||||||
|
{
|
||||||
|
width: 169px;
|
||||||
|
font-family: Helvetica;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 16px;
|
||||||
|
padding-top: 30px;
|
||||||
|
line-height: 22px;
|
||||||
|
font-weight: 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bottomToolbar {
|
||||||
|
display:block;
|
||||||
|
position: absolute;
|
||||||
|
right: -1;
|
||||||
|
bottom: 40px;
|
||||||
|
width: 29px;
|
||||||
|
border-top-left-radius: 10px;
|
||||||
|
border-bottom-left-radius: 10px;
|
||||||
|
color: #FFF;
|
||||||
|
border: 1px solid #000;
|
||||||
|
background: rgba(50,50,50,.65);
|
||||||
|
padding-top: 5px;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
z-index: 6; /*+1 from #remoteVideos*/
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottomToolbarButton {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
color: #FFFFFF;
|
||||||
|
top: 0;
|
||||||
|
padding-top: 3px;
|
||||||
|
width: 29px;
|
||||||
|
height: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 10pt;
|
||||||
|
text-align: center;
|
||||||
|
text-shadow: 0px 1px 0px rgba(255,255,255,.3), 0px -1px 0px rgba(0,0,0,.7);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
color: #00ccff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottomToolbar_span>span {
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
font-size: 7pt;
|
||||||
|
color: #ffffff;
|
||||||
|
text-align:center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.jqistates input[type="text"] {
|
.jqistates input[type='text'], input[type='password'] {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
top: 0px;
|
top: 0px;
|
||||||
left: 0px;
|
left: 0px;
|
||||||
right: 0px;
|
right: 0px;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
#remoteVideos {
|
#remoteVideos {
|
||||||
|
@ -14,7 +15,7 @@
|
||||||
padding: 18px;
|
padding: 18px;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 20px;
|
||||||
width:auto;
|
width:auto;
|
||||||
border:1px solid transparent;
|
border:1px solid transparent;
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
|
@ -32,6 +33,7 @@
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
border-radius:8px;
|
border-radius:8px;
|
||||||
border: 2px solid #212425;
|
border: 2px solid #212425;
|
||||||
|
margin-right: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#remoteVideos .videocontainer:hover,
|
#remoteVideos .videocontainer:hover,
|
||||||
|
@ -48,9 +50,21 @@
|
||||||
-webkit-animation-name: greyPulse;
|
-webkit-animation-name: greyPulse;
|
||||||
-webkit-animation-duration: 2s;
|
-webkit-animation-duration: 2s;
|
||||||
-webkit-animation-iteration-count: 1;
|
-webkit-animation-iteration-count: 1;
|
||||||
-webkit-box-shadow: 0 0 18px #388396;
|
}
|
||||||
border: 2px solid #388396;
|
|
||||||
z-index: 3;
|
#remoteVideos .videocontainer:hover {
|
||||||
|
-webkit-box-shadow: inset 0 0 10px #FFFFFF, 0 0 10px #FFFFFF;
|
||||||
|
border: 2px solid #FFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
#remoteVideos .videocontainer.videoContainerFocused {
|
||||||
|
-webkit-box-shadow: inset 0 0 28px #006d91;
|
||||||
|
border: 2px solid #006d91;
|
||||||
|
}
|
||||||
|
|
||||||
|
#remoteVideos .videocontainer.videoContainerFocused:hover {
|
||||||
|
-webkit-box-shadow: inset 0 0 5px #FFFFFF, 0 0 10px #FFFFFF, inset 0 0 60px #006d91;
|
||||||
|
border: 2px solid #FFFFFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
#localVideoWrapper {
|
#localVideoWrapper {
|
||||||
|
@ -94,9 +108,8 @@
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.activespeaker {
|
.dominantspeaker {
|
||||||
-webkit-filter: grayscale(1);
|
background: #000 !important;
|
||||||
filter: grayscale(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#etherpad,
|
#etherpad,
|
||||||
|
@ -158,10 +171,31 @@
|
||||||
border-radius:20px;
|
border-radius:20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.videocontainer>span.status {
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
color: #FFFFFF;
|
||||||
|
background: rgba(0,0,0,.7);
|
||||||
|
text-align: center;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
width: 70%;
|
||||||
|
height: 15%;
|
||||||
|
left: 15%;
|
||||||
|
bottom: 2%;
|
||||||
|
padding: 5px;
|
||||||
|
font-size: 10pt;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
z-index: 2;
|
||||||
|
border-radius:20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#localVideoContainer>span.status:hover,
|
||||||
#localVideoContainer>span.displayname:hover {
|
#localVideoContainer>span.displayname:hover {
|
||||||
cursor: text;
|
cursor: text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.videocontainer>span.status,
|
||||||
.videocontainer>span.displayname {
|
.videocontainer>span.displayname {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
@ -174,6 +208,7 @@
|
||||||
pointer-events: auto !important;
|
pointer-events: auto !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.videocontainer>a.status,
|
||||||
.videocontainer>a.displayname {
|
.videocontainer>a.displayname {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -292,3 +327,14 @@
|
||||||
background-image:url(../images/rightwatermark.png);
|
background-image:url(../images/rightwatermark.png);
|
||||||
background-position: center right;
|
background-position: center right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.audiolevel {
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 0;
|
||||||
|
border-radius:10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mixedstream {
|
||||||
|
display:none !important;
|
||||||
|
}
|
||||||
|
|
|
@ -1,16 +1,22 @@
|
||||||
/* global connection, Strophe, updateLargeVideo, focusedVideoSrc*/
|
/* global connection, Strophe, updateLargeVideo, focusedVideoSrc*/
|
||||||
|
|
||||||
|
// cache datachannels to avoid garbage collection
|
||||||
|
// https://code.google.com/p/chromium/issues/detail?id=405545
|
||||||
|
var _dataChannels = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback triggered by PeerConnection when new data channel is opened
|
* Callback triggered by PeerConnection when new data channel is opened
|
||||||
* on the bridge.
|
* on the bridge.
|
||||||
* @param event the event info object.
|
* @param event the event info object.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onDataChannel(event)
|
function onDataChannel(event)
|
||||||
{
|
{
|
||||||
var dataChannel = event.channel;
|
var dataChannel = event.channel;
|
||||||
|
|
||||||
dataChannel.onopen = function ()
|
dataChannel.onopen = function ()
|
||||||
{
|
{
|
||||||
console.info("Data channel opened by the bridge !!!", dataChannel);
|
console.info("Data channel opened by the Videobridge!", dataChannel);
|
||||||
|
|
||||||
// Code sample for sending string and/or binary data
|
// Code sample for sending string and/or binary data
|
||||||
// Sends String message to the bridge
|
// Sends String message to the bridge
|
||||||
|
@ -26,30 +32,61 @@ function onDataChannel(event)
|
||||||
|
|
||||||
dataChannel.onmessage = function (event)
|
dataChannel.onmessage = function (event)
|
||||||
{
|
{
|
||||||
var msgData = event.data;
|
var data = event.data;
|
||||||
console.info("Got Data Channel Message:", msgData, dataChannel);
|
// JSON
|
||||||
|
var obj;
|
||||||
|
|
||||||
// Active speaker event
|
try
|
||||||
if (msgData.indexOf('activeSpeaker') === 0 && !focusedVideoSrc)
|
|
||||||
{
|
{
|
||||||
// Endpoint ID from the bridge
|
obj = JSON.parse(data);
|
||||||
var endpointId = msgData.split(":")[1];
|
}
|
||||||
console.info("New active speaker: " + endpointId);
|
catch (e)
|
||||||
|
{
|
||||||
|
console.error(
|
||||||
|
"Failed to parse data channel message as JSON: ",
|
||||||
|
data,
|
||||||
|
dataChannel);
|
||||||
|
}
|
||||||
|
if (('undefined' !== typeof(obj)) && (null !== obj))
|
||||||
|
{
|
||||||
|
var colibriClass = obj.colibriClass;
|
||||||
|
|
||||||
var container = document.getElementById(
|
if ("DominantSpeakerEndpointChangeEvent" === colibriClass)
|
||||||
'participant_' + endpointId);
|
|
||||||
|
|
||||||
// Local video will not have container found, but that's ok
|
|
||||||
// since we don't want to switch to local video
|
|
||||||
|
|
||||||
if (container)
|
|
||||||
{
|
{
|
||||||
var video = container.getElementsByTagName("video");
|
// Endpoint ID from the Videobridge.
|
||||||
if (video.length)
|
var dominantSpeakerEndpoint = obj.dominantSpeakerEndpoint;
|
||||||
{
|
|
||||||
VideoLayout.updateLargeVideo(video[0].src);
|
console.info(
|
||||||
VideoLayout.enableActiveSpeaker(endpointId, true);
|
"Data channel new dominant speaker event: ",
|
||||||
}
|
dominantSpeakerEndpoint);
|
||||||
|
$(document).trigger(
|
||||||
|
'dominantspeakerchanged',
|
||||||
|
[dominantSpeakerEndpoint]);
|
||||||
|
}
|
||||||
|
else if ("LastNEndpointsChangeEvent" === colibriClass)
|
||||||
|
{
|
||||||
|
// The new/latest list of last-n endpoint IDs.
|
||||||
|
var lastNEndpoints = obj.lastNEndpoints;
|
||||||
|
/*
|
||||||
|
* The list of endpoint IDs which are entering the list of
|
||||||
|
* last-n at this time i.e. were not in the old list of last-n
|
||||||
|
* endpoint IDs.
|
||||||
|
*/
|
||||||
|
var endpointsEnteringLastN = obj.endpointsEnteringLastN;
|
||||||
|
|
||||||
|
var stream = obj.stream;
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
"Data channel new last-n event: ",
|
||||||
|
lastNEndpoints, endpointsEnteringLastN, obj);
|
||||||
|
|
||||||
|
$(document).trigger(
|
||||||
|
'lastnchanged',
|
||||||
|
[lastNEndpoints, endpointsEnteringLastN, stream]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
console.debug("Data channel JSON-formatted message: ", obj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -57,7 +94,11 @@ function onDataChannel(event)
|
||||||
dataChannel.onclose = function ()
|
dataChannel.onclose = function ()
|
||||||
{
|
{
|
||||||
console.info("The Data Channel closed", dataChannel);
|
console.info("The Data Channel closed", dataChannel);
|
||||||
|
var idx = _dataChannels.indexOf(dataChannel);
|
||||||
|
if (idx > -1)
|
||||||
|
_dataChannels = _dataChannels.splice(idx, 1);
|
||||||
};
|
};
|
||||||
|
_dataChannels.push(dataChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -90,4 +131,5 @@ function bindDataChannelListener(peerConnection)
|
||||||
var msgData = event.data;
|
var msgData = event.data;
|
||||||
console.info("Got My Data Channel Message:", msgData, dataChannel);
|
console.info("Got My Data Channel Message:", msgData, dataChannel);
|
||||||
};*/
|
};*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
jitsi-meet (1.0.1-1) unstable; urgency=low
|
||||||
|
|
||||||
|
* Initial release
|
||||||
|
* Jitsi Meet github snapshot from 2014-07-01
|
||||||
|
|
||||||
|
-- Yasen Pramatarov <yasen@bluejimp.com> Tue, 01 Jul 2014 16:31:41 +0300
|
|
@ -0,0 +1 @@
|
||||||
|
8
|
|
@ -0,0 +1,33 @@
|
||||||
|
Source: jitsi-meet
|
||||||
|
Section: net
|
||||||
|
Priority: extra
|
||||||
|
Maintainer: Jitsi Team <dev@jitsi.org>
|
||||||
|
Uploaders: Emil Ivov <emcho@jitsi.org>, Damian Minkov <damencho@jitsi.org>
|
||||||
|
Build-Depends: debhelper (>= 8.0.0)
|
||||||
|
Standards-Version: 3.9.3
|
||||||
|
Homepage: https://jitsi.org/meet
|
||||||
|
|
||||||
|
Package: jitsi-meet
|
||||||
|
Architecture: all
|
||||||
|
Pre-Depends: adduser, openssl, jitsi-videobridge
|
||||||
|
Depends: ${misc:Depends}, nginx, jitsi-meet-prosody
|
||||||
|
Description: WebRTC JavaScript video conferences
|
||||||
|
Jitsi Meet is a WebRTC JavaScript application that uses Jitsi
|
||||||
|
Videobridge to provide high quality, scalable video conferences.
|
||||||
|
.
|
||||||
|
It is a web interface to Jitsi Videobridge for audio and video
|
||||||
|
forwarding and relaying, configured to work with nginx
|
||||||
|
|
||||||
|
Package: jitsi-meet-prosody
|
||||||
|
Architecture: all
|
||||||
|
Pre-Depends: adduser, openssl, prosody-trunk, jitsi-videobridge
|
||||||
|
Depends: ${misc:Depends}, nginx, prosody-modules-otalk, lua-sec
|
||||||
|
Description: Prosody configuration for Jitsi Meet
|
||||||
|
Jitsi Meet is a WebRTC JavaScript application that uses Jitsi
|
||||||
|
Videobridge to provide high quality, scalable video conferences.
|
||||||
|
.
|
||||||
|
It is a web interface to Jitsi Videobridge for audio and video
|
||||||
|
forwarding and relaying, configured to work with nginx
|
||||||
|
.
|
||||||
|
This package contains configuration for Prosody to be used with
|
||||||
|
Jitsi Meet.
|
|
@ -0,0 +1,7 @@
|
||||||
|
Prosody configuration for Jitsi Meet for Debian
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
Jitsi Meet is a WebRTC video conferencing application. This package contains
|
||||||
|
configuration of prosody which are needed for Jitsi Meet to work.
|
||||||
|
|
||||||
|
-- Yasen Pramatarov <yasen@bluejimp.com> Mon, 30 Jun 2014 23:05:18 +0100
|
|
@ -0,0 +1,31 @@
|
||||||
|
Format: http://dep.debian.net/deps/dep5
|
||||||
|
Upstream-Name: Jitsi Meet
|
||||||
|
Upstream-Contact: Emil Ivov <emcho@jitsi.org>
|
||||||
|
Source: https://github.com/jitsi/jitsi-meet
|
||||||
|
|
||||||
|
Files: *
|
||||||
|
Copyright: 2013-2014 Jitsi
|
||||||
|
License: MIT
|
||||||
|
|
||||||
|
License: MIT
|
||||||
|
The MIT License (MIT)
|
||||||
|
.
|
||||||
|
Copyright (c) 2013 ESTOS GmbH
|
||||||
|
Copyright (c) 2013 BlueJimp SARL
|
||||||
|
.
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
.
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
.
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1 @@
|
||||||
|
debian/usr/share/doc/jitsi-meet-prosody/prosody.cfg.lua-jvb.example.gz
|
|
@ -0,0 +1 @@
|
||||||
|
debian/usr/share/* usr/share/
|
|
@ -0,0 +1,59 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# postinst script for jitsi-meet-prosody
|
||||||
|
#
|
||||||
|
# see: dh_installdeb(1)
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# summary of how this script can be called:
|
||||||
|
# * <postinst> `configure' <most-recently-configured-version>
|
||||||
|
# * <old-postinst> `abort-upgrade' <new version>
|
||||||
|
# * <conflictor's-postinst> `abort-remove' `in-favour' <package>
|
||||||
|
# <new-version>
|
||||||
|
# * <postinst> `abort-remove'
|
||||||
|
# * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
|
||||||
|
# <failed-install-package> <version> `removing'
|
||||||
|
# <conflicting-package> <version>
|
||||||
|
# for details, see http://www.debian.org/doc/debian-policy/ or
|
||||||
|
# the debian-policy package
|
||||||
|
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
configure)
|
||||||
|
|
||||||
|
. /etc/default/jitsi-videobridge
|
||||||
|
|
||||||
|
if [ -x /etc/prosody/prosody.cfg.lua ]; then
|
||||||
|
mv /etc/prosody/prosody.cfg.lua /etc/prosody/prosody.cfg.lua.orig
|
||||||
|
fi
|
||||||
|
gunzip -c /usr/share/doc/jitsi-meet-prosody/prosody.cfg.lua-jvb.example.gz > /etc/prosody/prosody.cfg.lua
|
||||||
|
sed -i "s/jitmeet.example.com/$JVB_HOSTNAME/g" /etc/prosody/prosody.cfg.lua
|
||||||
|
sed -i "s/jitmeetSecret/$JVB_SECRET/g" /etc/prosody/prosody.cfg.lua
|
||||||
|
if [ ! -f /var/lib/prosody/$JVB_HOSTNAME.crt ]; then
|
||||||
|
HOST="$( (hostname -s; echo localhost) | head -n 1)"
|
||||||
|
DOMAIN="$( (hostname -d; echo localdomain) | head -n 1)"
|
||||||
|
openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 -subj \
|
||||||
|
"/O=$DOMAIN/OU=$HOST/CN=$JVB_HOSTNAME/emailAddress=webmaster@$HOST.$DOMAIN" \
|
||||||
|
-keyout /var/lib/prosody/$JVB_HOSTNAME.key \
|
||||||
|
-out /var/lib/prosody/$JVB_HOSTNAME.crt
|
||||||
|
fi
|
||||||
|
ln -sf /var/lib/prosody/$JVB_HOSTNAME.key /etc/prosody/certs/$JVB_HOSTNAME.key
|
||||||
|
ln -sf /var/lib/prosody/$JVB_HOSTNAME.crt /etc/prosody/certs/$JVB_HOSTNAME.crt
|
||||||
|
invoke-rc.d prosody restart
|
||||||
|
;;
|
||||||
|
|
||||||
|
abort-upgrade|abort-remove|abort-deconfigure)
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
echo "postinst called with unknown argument \`$1'" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# dh_installdeb will replace this with shell code automatically
|
||||||
|
# generated by other debhelper scripts.
|
||||||
|
|
||||||
|
#DEBHELPER#
|
||||||
|
|
||||||
|
exit 0
|
|
@ -0,0 +1,48 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# postrm script for jitsi-meet-prosody
|
||||||
|
#
|
||||||
|
# see: dh_installdeb(1)
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# summary of how this script can be called:
|
||||||
|
# * <postrm> `remove'
|
||||||
|
# * <postrm> `purge'
|
||||||
|
# * <old-postrm> `upgrade' <new-version>
|
||||||
|
# * <new-postrm> `failed-upgrade' <old-version>
|
||||||
|
# * <new-postrm> `abort-install'
|
||||||
|
# * <new-postrm> `abort-install' <old-version>
|
||||||
|
# * <new-postrm> `abort-upgrade' <old-version>
|
||||||
|
# * <disappearer's-postrm> `disappear' <overwriter>
|
||||||
|
# <overwriter-version>
|
||||||
|
# for details, see http://www.debian.org/doc/debian-policy/ or
|
||||||
|
# the debian-policy package
|
||||||
|
|
||||||
|
# Load debconf
|
||||||
|
. /usr/share/debconf/confmodule
|
||||||
|
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
purge|remove)
|
||||||
|
if [ -x "/etc/init.d/prosody" ]; then
|
||||||
|
invoke-rc.d nginx reload
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
echo "postrm called with unknown argument \`$1'" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# dh_installdeb will replace this with shell code automatically
|
||||||
|
# generated by other debhelper scripts.
|
||||||
|
|
||||||
|
#DEBHELPER#
|
||||||
|
|
||||||
|
db_stop
|
||||||
|
|
||||||
|
exit 0
|
|
@ -0,0 +1,35 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# preinst script for jitsi-meet-prosody
|
||||||
|
#
|
||||||
|
# see: dh_installdeb(1)
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# summary of how this script can be called:
|
||||||
|
# * <new-preinst> `install'
|
||||||
|
# * <new-preinst> `install' <old-version>
|
||||||
|
# * <new-preinst> `upgrade' <old-version>
|
||||||
|
# * <old-preinst> `abort-upgrade' <new-version>
|
||||||
|
# for details, see http://www.debian.org/doc/debian-policy/ or
|
||||||
|
# the debian-policy package
|
||||||
|
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
install|upgrade)
|
||||||
|
;;
|
||||||
|
|
||||||
|
abort-upgrade)
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
echo "preinst called with unknown argument \`$1'" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# dh_installdeb will replace this with shell code automatically
|
||||||
|
# generated by other debhelper scripts.
|
||||||
|
|
||||||
|
#DEBHELPER#
|
||||||
|
|
||||||
|
exit 0
|
|
@ -0,0 +1,36 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# prerm script for jitsi-meet-prosody
|
||||||
|
#
|
||||||
|
# see: dh_installdeb(1)
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# summary of how this script can be called:
|
||||||
|
# * <prerm> `remove'
|
||||||
|
# * <old-prerm> `upgrade' <new-version>
|
||||||
|
# * <new-prerm> `failed-upgrade' <old-version>
|
||||||
|
# * <conflictor's-prerm> `remove' `in-favour' <package> <new-version>
|
||||||
|
# * <deconfigured's-prerm> `deconfigure' `in-favour'
|
||||||
|
# <package-being-installed> <version> `removing'
|
||||||
|
# <conflicting-package> <version>
|
||||||
|
# for details, see http://www.debian.org/doc/debian-policy/ or
|
||||||
|
# the debian-policy package
|
||||||
|
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
remove|purge)
|
||||||
|
;;
|
||||||
|
|
||||||
|
upgrade|deconfigure)
|
||||||
|
;;
|
||||||
|
|
||||||
|
failed-upgrade)
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
echo "prerm called with unknown argument \`$1'" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit 0
|
|
@ -0,0 +1 @@
|
||||||
|
misc:Depends=
|
|
@ -0,0 +1,8 @@
|
||||||
|
Jitsi Meet for Debian
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
This is a WebRTC frontend of the video conferencing tool Jitsi Meet. It depends on the
|
||||||
|
jitsi-videobridge package, which is a SFU (Selective Forwarding Unit) and both packages
|
||||||
|
are designed to work together.
|
||||||
|
|
||||||
|
-- Yasen Pramatarov <yasen@bluejimp.com> Mon, 30 Jun 2014 23:05:18 +0100
|
|
@ -0,0 +1,6 @@
|
||||||
|
jitsi-meet for Debian
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
The jitsi-meet package is built from the sources of Jitsi Meet.
|
||||||
|
|
||||||
|
Jitsi Meet is downloaded from https://github.com/jitsi/jitsi-meet and the git files are removed. you can recreate the source with 'git clone https://github.com/jitsi/jitsi-meet.git'.
|
|
@ -0,0 +1,31 @@
|
||||||
|
Format: http://dep.debian.net/deps/dep5
|
||||||
|
Upstream-Name: Jitsi Meet
|
||||||
|
Upstream-Contact: Emil Ivov <emcho@jitsi.org>
|
||||||
|
Source: https://github.com/jitsi/jitsi-meet
|
||||||
|
|
||||||
|
Files: *
|
||||||
|
Copyright: 2013-2014 Jitsi
|
||||||
|
License: MIT
|
||||||
|
|
||||||
|
License: MIT
|
||||||
|
The MIT License (MIT)
|
||||||
|
.
|
||||||
|
Copyright (c) 2013 ESTOS GmbH
|
||||||
|
Copyright (c) 2013 BlueJimp SARL
|
||||||
|
.
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
.
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
.
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,3 @@
|
||||||
|
README.md
|
||||||
|
debian/usr/share/doc/jitsi-meet/README
|
||||||
|
debian/usr/share/doc/jitsi-meet/jitsi-meet.example
|
|
@ -0,0 +1,2 @@
|
||||||
|
* usr/share/jitsi-meet/
|
||||||
|
debian/usr/share/* usr/share/
|
|
@ -0,0 +1,64 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# postinst script for jitsi-meet
|
||||||
|
#
|
||||||
|
# see: dh_installdeb(1)
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# summary of how this script can be called:
|
||||||
|
# * <postinst> `configure' <most-recently-configured-version>
|
||||||
|
# * <old-postinst> `abort-upgrade' <new version>
|
||||||
|
# * <conflictor's-postinst> `abort-remove' `in-favour' <package>
|
||||||
|
# <new-version>
|
||||||
|
# * <postinst> `abort-remove'
|
||||||
|
# * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
|
||||||
|
# <failed-install-package> <version> `removing'
|
||||||
|
# <conflicting-package> <version>
|
||||||
|
# for details, see http://www.debian.org/doc/debian-policy/ or
|
||||||
|
# the debian-policy package
|
||||||
|
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
configure)
|
||||||
|
|
||||||
|
# nginx conf
|
||||||
|
. /etc/default/jitsi-videobridge
|
||||||
|
cp /usr/share/doc/jitsi-meet/jitsi-meet.example /etc/nginx/sites-available/$JVB_HOSTNAME.conf
|
||||||
|
if [ ! -f /etc/nginx/sites-enabled/$JVB_HOSTNAME.conf ]; then
|
||||||
|
ln -s /etc/nginx/sites-available/$JVB_HOSTNAME.conf /etc/nginx/sites-enabled/$JVB_HOSTNAME.conf
|
||||||
|
fi
|
||||||
|
sed -i "s/jitsi-meet.example.com/$JVB_HOSTNAME/g" /etc/nginx/sites-available/$JVB_HOSTNAME.conf
|
||||||
|
# FIXME do we need the default?
|
||||||
|
if [ ! -f /etc/nginx/sites-enabled/default ]; then
|
||||||
|
ln -s /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default
|
||||||
|
fi
|
||||||
|
if grep "# server_names_hash_bucket_size 64" /etc/nginx/nginx.conf > /dev/null; then
|
||||||
|
sed -i "s/#\ server_names_hash_bucket_size\ 64/\ server_names_hash_bucket_size\ 64/" /etc/nginx/nginx.conf
|
||||||
|
fi
|
||||||
|
|
||||||
|
# jitsi meet
|
||||||
|
chown -R www-data:www-data /usr/share/jitsi-meet/
|
||||||
|
sed -i "s/jitsi-meet.example.com/$JVB_HOSTNAME/g" /usr/share/jitsi-meet/config.js
|
||||||
|
# enable turn
|
||||||
|
if grep "// useStunTurn: true," /usr/share/jitsi-meet/config.js > /dev/null; then
|
||||||
|
sed -i "s/\/\/\ \ useStunTurn:\ true,/\ \ \ \ useStunTurn:\ true,/" /usr/share/jitsi-meet/config.js
|
||||||
|
fi
|
||||||
|
invoke-rc.d nginx restart
|
||||||
|
|
||||||
|
;;
|
||||||
|
|
||||||
|
abort-upgrade|abort-remove|abort-deconfigure)
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
echo "postinst called with unknown argument \`$1'" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# dh_installdeb will replace this with shell code automatically
|
||||||
|
# generated by other debhelper scripts.
|
||||||
|
|
||||||
|
#DEBHELPER#
|
||||||
|
|
||||||
|
exit 0
|
|
@ -0,0 +1,48 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# postrm script for jitsi-meet
|
||||||
|
#
|
||||||
|
# see: dh_installdeb(1)
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# summary of how this script can be called:
|
||||||
|
# * <postrm> `remove'
|
||||||
|
# * <postrm> `purge'
|
||||||
|
# * <old-postrm> `upgrade' <new-version>
|
||||||
|
# * <new-postrm> `failed-upgrade' <old-version>
|
||||||
|
# * <new-postrm> `abort-install'
|
||||||
|
# * <new-postrm> `abort-install' <old-version>
|
||||||
|
# * <new-postrm> `abort-upgrade' <old-version>
|
||||||
|
# * <disappearer's-postrm> `disappear' <overwriter>
|
||||||
|
# <overwriter-version>
|
||||||
|
# for details, see http://www.debian.org/doc/debian-policy/ or
|
||||||
|
# the debian-policy package
|
||||||
|
|
||||||
|
# Load debconf
|
||||||
|
. /usr/share/debconf/confmodule
|
||||||
|
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
purge|remove)
|
||||||
|
if [ -x "/etc/init.d/nginx" ]; then
|
||||||
|
invoke-rc.d nginx reload
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
echo "postrm called with unknown argument \`$1'" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# dh_installdeb will replace this with shell code automatically
|
||||||
|
# generated by other debhelper scripts.
|
||||||
|
|
||||||
|
#DEBHELPER#
|
||||||
|
|
||||||
|
db_stop
|
||||||
|
|
||||||
|
exit 0
|
|
@ -0,0 +1,35 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# preinst script for jitsi-meet
|
||||||
|
#
|
||||||
|
# see: dh_installdeb(1)
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# summary of how this script can be called:
|
||||||
|
# * <new-preinst> `install'
|
||||||
|
# * <new-preinst> `install' <old-version>
|
||||||
|
# * <new-preinst> `upgrade' <old-version>
|
||||||
|
# * <old-preinst> `abort-upgrade' <new-version>
|
||||||
|
# for details, see http://www.debian.org/doc/debian-policy/ or
|
||||||
|
# the debian-policy package
|
||||||
|
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
install|upgrade)
|
||||||
|
;;
|
||||||
|
|
||||||
|
abort-upgrade)
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
echo "preinst called with unknown argument \`$1'" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# dh_installdeb will replace this with shell code automatically
|
||||||
|
# generated by other debhelper scripts.
|
||||||
|
|
||||||
|
#DEBHELPER#
|
||||||
|
|
||||||
|
exit 0
|
|
@ -0,0 +1,36 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# prerm script for jitsi-meet
|
||||||
|
#
|
||||||
|
# see: dh_installdeb(1)
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# summary of how this script can be called:
|
||||||
|
# * <prerm> `remove'
|
||||||
|
# * <old-prerm> `upgrade' <new-version>
|
||||||
|
# * <new-prerm> `failed-upgrade' <old-version>
|
||||||
|
# * <conflictor's-prerm> `remove' `in-favour' <package> <new-version>
|
||||||
|
# * <deconfigured's-prerm> `deconfigure' `in-favour'
|
||||||
|
# <package-being-installed> <version> `removing'
|
||||||
|
# <conflicting-package> <version>
|
||||||
|
# for details, see http://www.debian.org/doc/debian-policy/ or
|
||||||
|
# the debian-policy package
|
||||||
|
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
remove|purge)
|
||||||
|
;;
|
||||||
|
|
||||||
|
upgrade|deconfigure)
|
||||||
|
;;
|
||||||
|
|
||||||
|
failed-upgrade)
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
echo "prerm called with unknown argument \`$1'" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit 0
|
|
@ -0,0 +1,16 @@
|
||||||
|
#!/usr/bin/make -f
|
||||||
|
# -*- makefile -*-
|
||||||
|
# Sample debian/rules that uses debhelper.
|
||||||
|
# This file was originally written by Joey Hess and Craig Small.
|
||||||
|
# As a special exception, when this file is copied by dh-make into a
|
||||||
|
# dh-make output file, you may use that output file without restriction.
|
||||||
|
# This special exception was added by Craig Small in version 0.37 of dh-make.
|
||||||
|
|
||||||
|
# Uncomment this to turn on verbose mode.
|
||||||
|
#export DH_VERBOSE=1
|
||||||
|
|
||||||
|
%:
|
||||||
|
dh $@
|
||||||
|
|
||||||
|
override_dh_install-indep:
|
||||||
|
dh_install -Xdebian -Xdoc -XINSTALL.md -XLICENSE -XREADME.md usr/share/jitsi-meet/
|
|
@ -0,0 +1 @@
|
||||||
|
3.0 (quilt)
|
|
@ -0,0 +1,19 @@
|
||||||
|
debian/usr/share/jitsi-meet/favicon.ico
|
||||||
|
debian/usr/share/jitsi-meet/fonts/jitsi.eot
|
||||||
|
debian/usr/share/jitsi-meet/fonts/jitsi.woff
|
||||||
|
debian/usr/share/jitsi-meet/fonts/jitsi.ttf
|
||||||
|
debian/usr/share/jitsi-meet/sounds/left.wav
|
||||||
|
debian/usr/share/jitsi-meet/sounds/incomingMessage.wav
|
||||||
|
debian/usr/share/jitsi-meet/sounds/joined.wav
|
||||||
|
debian/usr/share/jitsi-meet/images/estoslogo.png
|
||||||
|
debian/usr/share/jitsi-meet/images/chromelogo.png
|
||||||
|
debian/usr/share/jitsi-meet/images/jitsilogo.png
|
||||||
|
debian/usr/share/jitsi-meet/images/watermark.png
|
||||||
|
debian/usr/share/jitsi-meet/images/avatarprezi.png
|
||||||
|
debian/usr/share/jitsi-meet/images/chromepointer.png
|
||||||
|
debian/usr/share/jitsi-meet/images/avatar1.png
|
||||||
|
debian/usr/share/jitsi-meet/images/popupPointer.png
|
||||||
|
debian/usr/share/jitsi-meet/images/favicon.ico
|
||||||
|
debian/usr/share/doc/jitsi-meet/changelog.Debian.gz
|
||||||
|
debian/usr/share/doc/jitsi-meet-prosody/changelog.Debian.gz
|
||||||
|
debian/usr/share/doc/jitsi-meet-prosody/prosody.cfg.lua-jvb.example.gz
|
|
@ -0,0 +1 @@
|
||||||
|
Prosody configuration for Jitsi Meet
|
|
@ -0,0 +1,31 @@
|
||||||
|
Format: http://dep.debian.net/deps/dep5
|
||||||
|
Upstream-Name: Jitsi Meet
|
||||||
|
Upstream-Contact: Emil Ivov <emcho@jitsi.org>
|
||||||
|
Source: https://github.com/jitsi/jitsi-meet
|
||||||
|
|
||||||
|
Files: *
|
||||||
|
Copyright: 2013-2014 Jitsi
|
||||||
|
License: MIT
|
||||||
|
|
||||||
|
License: MIT
|
||||||
|
The MIT License (MIT)
|
||||||
|
.
|
||||||
|
Copyright (c) 2013 ESTOS GmbH
|
||||||
|
Copyright (c) 2013 BlueJimp SARL
|
||||||
|
.
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
.
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
.
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,13 @@
|
||||||
|
Jitsi Meet
|
||||||
|
|
||||||
|
====
|
||||||
|
|
||||||
|
A WebRTC-powered multi-user videochat. For a live demo, check out either
|
||||||
|
https://meet.estos.de/ or https://meet.jit.si/.
|
||||||
|
|
||||||
|
Built using colibri.js[0] and strophe.jingle[1], powered by the jitsi-videobridge[2]
|
||||||
|
|
||||||
|
|
||||||
|
[0] https://github.com/ESTOS/colibri.js
|
||||||
|
[1] https://github.com/ESTOS/strophe.jingle
|
||||||
|
[3] https://github.com/jitsi/jitsi-videobridge
|
|
@ -0,0 +1,31 @@
|
||||||
|
Format: http://dep.debian.net/deps/dep5
|
||||||
|
Upstream-Name: Jitsi Meet
|
||||||
|
Upstream-Contact: Emil Ivov <emcho@jitsi.org>
|
||||||
|
Source: https://github.com/jitsi/jitsi-meet
|
||||||
|
|
||||||
|
Files: *
|
||||||
|
Copyright: 2013-2014 Jitsi
|
||||||
|
License: MIT
|
||||||
|
|
||||||
|
License: MIT
|
||||||
|
The MIT License (MIT)
|
||||||
|
.
|
||||||
|
Copyright (c) 2013 ESTOS GmbH
|
||||||
|
Copyright (c) 2013 BlueJimp SARL
|
||||||
|
.
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
.
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
.
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,36 @@
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name jitsi-meet.example.com;
|
||||||
|
return 301 https://$host$request_uri;
|
||||||
|
}
|
||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
server_name jitsi-meet.example.com;
|
||||||
|
|
||||||
|
ssl_certificate /var/lib/prosody/jitsi-meet.example.com.crt;
|
||||||
|
ssl_certificate_key /var/lib/prosody/jitsi-meet.example.com.key;
|
||||||
|
|
||||||
|
root /usr/share/jitsi-meet;
|
||||||
|
index index.html index.htm;
|
||||||
|
|
||||||
|
location ~ ^/([a-zA-Z0-9]+)$ {
|
||||||
|
rewrite ^/(.*)$ / break;
|
||||||
|
}
|
||||||
|
|
||||||
|
# BOSH
|
||||||
|
location /http-bind {
|
||||||
|
proxy_pass http://localhost:5280/http-bind;
|
||||||
|
proxy_set_header X-Forwarded-For $remote_addr;
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
}
|
||||||
|
|
||||||
|
# xmpp websockets
|
||||||
|
location /xmpp-websocket {
|
||||||
|
proxy_pass http://localhost:5280;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
tcp_nodelay on;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
# Jitsi Meet quick install
|
||||||
|
|
||||||
|
This documents decribes the needed steps for quick Jitsi Meet installation on a Debian based GNU/Linux system.
|
||||||
|
|
||||||
|
N.B.: All commands are supposed to be run by root. If you are logged in as a regular user with sudo rights, please prepend ___sudo___ to each of the commands.
|
||||||
|
|
||||||
|
## Basic Jitsi Meet install
|
||||||
|
|
||||||
|
### Add the repository
|
||||||
|
|
||||||
|
```sh
|
||||||
|
add-apt-repository 'deb http://download.jitsi.org/nightly/deb unstable/'
|
||||||
|
wget -qO - https://download.jitsi.org/nightly/deb/unstable/archive.key | apt-key add -
|
||||||
|
```
|
||||||
|
|
||||||
|
add-apt-repository is in the default Ubuntu install and is available for both Ubuntu and Debian, but if it's not present, either install it with
|
||||||
|
|
||||||
|
```sh
|
||||||
|
apt-get -y install software-properties-common
|
||||||
|
add-apt-repository 'deb http://download.jitsi.org/nightly/deb unstable/'
|
||||||
|
wget -qO - https://download.jitsi.org/nightly/deb/unstable/archive.key | apt-key add -
|
||||||
|
```
|
||||||
|
|
||||||
|
or add the repository by hand with
|
||||||
|
|
||||||
|
```sh
|
||||||
|
echo 'deb http://download.jitsi.org/nightly/deb unstable/' >> /etc/apt/sources.list
|
||||||
|
wget -qO - https://download.jitsi.org/nightly/deb/unstable/archive.key | apt-key add -
|
||||||
|
```
|
||||||
|
|
||||||
|
### Update the package lists
|
||||||
|
|
||||||
|
```sh
|
||||||
|
apt-get update
|
||||||
|
```
|
||||||
|
|
||||||
|
### Install Jitsi Meet
|
||||||
|
|
||||||
|
```sh
|
||||||
|
apt-get -y install jitsi-meet
|
||||||
|
```
|
||||||
|
|
||||||
|
During the installation you'll be asked to enter the hostname of the Jitsi Meet instance. If you have a FQDN hostname for the instance already set ip in DNS, enter it there. If you don't have a resolvable hostname, you can enter the IP address of the machine (if it is static or doesn't change).
|
||||||
|
|
||||||
|
This hostname (or IP address) will be used for virtualhost configuration inside the Jitsi Meet and also you and your correspondents will be using it to access the web conferences.
|
||||||
|
|
||||||
|
### Open a conference
|
||||||
|
|
||||||
|
Launch a web broswer (Chrome, Chromium or latest Opera) and enter in the URL bar the hostname (or IP address) you used in the previous step.
|
||||||
|
|
||||||
|
Confirm that you trust the self-signed certificate of the newly installed Jitsi Meet.
|
||||||
|
|
||||||
|
Enjoy!
|
||||||
|
|
||||||
|
## Adding sip-gateway to Jitsi Meet
|
||||||
|
|
||||||
|
### Install Jigasi
|
||||||
|
|
||||||
|
```sh
|
||||||
|
apt-get -o Dpkg::Options::="--force-overwrite" -y install jigasi
|
||||||
|
```
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
```sh
|
||||||
|
wget https://download.jitsi.org/jigasi_1.0-1_amd64.deb
|
||||||
|
dpkg -i --force-overwrite jigasi_1.0-1_amd64.deb
|
||||||
|
```
|
||||||
|
|
||||||
|
You need to pass "--force-overwrite" option to dpkg, because the jigasi package patches some of the files in the jitsi-meet package in order to enable the SIP support in Jitsi Meet.
|
||||||
|
|
||||||
|
During the installation you'll be asked to enter your SIP account and password. This account will be used to invite the other SIP participants.
|
||||||
|
|
||||||
|
### Reload Jitsi Meet
|
||||||
|
|
||||||
|
Launch again a browser with the Jitsi Meet URL and you'll see a telephone icon on the right end of the toolbar. Use it to invite SIP accounts to join the current conference.
|
||||||
|
|
||||||
|
Enjoy!
|
||||||
|
|
||||||
|
## Troubleshoot
|
||||||
|
|
||||||
|
If the SIP gateway doesn't work on first try, restart it.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
/etc/init.d/jigasi restart
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deinstall
|
||||||
|
|
||||||
|
```sh
|
||||||
|
apt-get purge jigasi jitsi-meet jitsi-videobridge
|
||||||
|
```
|
||||||
|
|
||||||
|
Somethimes the following packages will fail to uninstall properly:
|
||||||
|
|
||||||
|
- jigasi
|
||||||
|
- jitsi-videobridge
|
||||||
|
|
||||||
|
When this happens, just run the deinstall command a second time and it should be ok.
|
||||||
|
|
||||||
|
The reason for failure is that not allways the daemons are stopped right away, there is a timeout before the actual stop. And if the unistall script goes on before the services' stop, there is an error.
|
||||||
|
|
||||||
|
The second run of the deinstall command fixes this, as by then the jigasi or jvb daemons are already stopped.
|
After Width: | Height: | Size: 3.6 KiB |
BIN
fonts/jitsi.eot
|
@ -15,13 +15,19 @@
|
||||||
<glyph unicode="" d="M0.759 320.807h138.767v159.899c0 0-39.017-4.051-88.090-55.817-49.069-51.764-50.676-104.082-50.676-104.082zM341.64 480.706h-169.842v-192.298l-171.040 0.125-0.757 1.734v-255.251c0-36.923 30.7-66.99 68.424-66.99h273.217c37.757 0 68.456 30.068 68.456 66.99v378.702c-0.002 36.921-30.699 66.988-68.457 66.988zM345.927 72.582h-286.424v46.394h286.423v-46.394zM345.927 169.401h-286.424v46.392h286.423v-46.392z" horiz-adv-x="410" />
|
<glyph unicode="" d="M0.759 320.807h138.767v159.899c0 0-39.017-4.051-88.090-55.817-49.069-51.764-50.676-104.082-50.676-104.082zM341.64 480.706h-169.842v-192.298l-171.040 0.125-0.757 1.734v-255.251c0-36.923 30.7-66.99 68.424-66.99h273.217c37.757 0 68.456 30.068 68.456 66.99v378.702c-0.002 36.921-30.699 66.988-68.457 66.988zM345.927 72.582h-286.424v46.394h286.423v-46.394zM345.927 169.401h-286.424v46.392h286.423v-46.392z" horiz-adv-x="410" />
|
||||||
<glyph unicode="" d="M476.95 481.193h-409.887c-36.483 0-66.209-30.356-66.209-67.672v-270.084c0-37.284 29.727-67.639 66.209-67.639h17.912v-106.445l172.483 106.445h219.493c36.482 0 66.208 30.355 66.208 67.639v270.084c0.001 37.316-29.725 67.672-66.207 67.672zM247.214 146.677l-97.885-62v62h-79.092v263.626h403.539l0.062-263.626h-226.625z" horiz-adv-x="545" />
|
<glyph unicode="" d="M476.95 481.193h-409.887c-36.483 0-66.209-30.356-66.209-67.672v-270.084c0-37.284 29.727-67.639 66.209-67.639h17.912v-106.445l172.483 106.445h219.493c36.482 0 66.208 30.355 66.208 67.639v270.084c0.001 37.316-29.725 67.672-66.207 67.672zM247.214 146.677l-97.885-62v62h-79.092v263.626h403.539l0.062-263.626h-226.625z" horiz-adv-x="545" />
|
||||||
<glyph unicode="" d="M354.757 310.047v22.227c0 81.545-66.331 147.875-147.875 147.875-81.546 0-147.876-66.329-147.876-147.875v-22.227c-33.113-3.697-59.007-32.458-59.007-67.304v-205.811c0-37.315 29.741-67.683 66.236-67.683h281.291c36.529 0 66.267 30.368 66.267 67.683v205.811c0 34.848-25.896 63.609-59.037 67.304zM206.882 415.769c46.022 0 83.493-37.472 83.493-83.494v-21.816h-166.989v21.816c0 46.022 37.441 83.494 83.495 83.494z" horiz-adv-x="414" />
|
<glyph unicode="" d="M354.757 310.047v22.227c0 81.545-66.331 147.875-147.875 147.875-81.546 0-147.876-66.329-147.876-147.875v-22.227c-33.113-3.697-59.007-32.458-59.007-67.304v-205.811c0-37.315 29.741-67.683 66.236-67.683h281.291c36.529 0 66.267 30.368 66.267 67.683v205.811c0 34.848-25.896 63.609-59.037 67.304zM206.882 415.769c46.022 0 83.493-37.472 83.493-83.494v-21.816h-166.989v21.816c0 46.022 37.441 83.494 83.495 83.494z" horiz-adv-x="414" />
|
||||||
<glyph unicode="" d="M613.039 358.427l-90.297-88.124v109.103c0 37.441-29.829 67.911-66.474 67.911h-318.101c-36.644 0-66.469-30.47-66.469-67.911v-305.329c0-37.425 29.826-67.894 66.469-67.894h318.101c36.645 0 66.474 30.469 66.474 67.894v86.562l96.954-77.070c24.451-17.791 48.463 2.608 48.463 20.292v242.327c0.001 17.682-28.015 35.754-55.122 12.24zM301.315 99.154c-70.723 0-128.065 57.342-128.065 128.066s57.341 128.065 128.065 128.065c70.724 0 128.067-57.341 128.067-128.065 0-70.725-57.344-128.066-128.067-128.066zM296.894 299.788c-40.335 0-73.037-32.704-73.037-73.036 0-40.335 32.703-73.040 73.037-73.040 40.331 0 73.036 32.707 73.036 73.040 0 40.332-32.705 73.036-73.036 73.036zM296.894 260.049c-14.916 0-27.014-12.116-27.014-27.013 0-4.423-3.594-8.004-8.005-8.004-4.44 0-8.002 3.58-8.002 8.004 0 23.716 19.291 43.024 43.022 43.024 4.409 0 8.002-3.58 8.002-8.005-0.002-4.426-3.596-8.005-8.002-8.005z" horiz-adv-x="667" />
|
<glyph unicode="" d="M613.039 358.427l-90.297-88.124v109.103c0 37.441-29.829 67.911-66.474 67.911h-318.101c-36.644 0-66.469-30.47-66.469-67.911v-305.329c0-37.425 29.826-67.894 66.469-67.894h318.101c36.645 0 66.474 30.469 66.474 67.894v86.562l96.954-77.070c24.451-17.791 48.463 2.608 48.463 20.292v242.327c0.001 17.682-28.015 35.754-55.122 12.24zM301.315 99.154c-70.723 0-128.065 57.342-128.065 128.066s57.341 128.065 128.065 128.065c70.724 0 128.067-57.341 128.067-128.065s-57.344-128.066-128.067-128.066zM296.894 299.788c-40.335 0-73.037-32.704-73.037-73.036 0-40.335 32.703-73.040 73.037-73.040 40.331 0 73.036 32.707 73.036 73.040 0 40.332-32.705 73.036-73.036 73.036zM296.894 260.049c-14.916 0-27.014-12.116-27.014-27.013 0-4.423-3.594-8.004-8.005-8.004-4.44 0-8.002 3.58-8.002 8.004 0 23.716 19.291 43.024 43.022 43.024 4.409 0 8.002-3.58 8.002-8.005-0.002-4.426-3.596-8.005-8.002-8.005z" horiz-adv-x="667" />
|
||||||
<glyph unicode="" d="M611.967 358.573l-90.149-87.978v108.924c0 3.831-0.333 7.574-0.951 11.216l36.847 32.673c13.174 11.705 14.42 31.9 2.684 45.12-11.737 13.203-31.902 14.436-45.136 2.7l-504.51-447.356c-13.204-11.705-14.421-31.903-2.699-45.104 6.303-7.118 15.091-10.769 23.925-10.769 7.538 0 15.107 2.652 21.195 8.050l47.92 42.49c10.498-7.313 23.13-11.616 36.796-11.616h317.596c36.55 0 66.33 30.404 66.33 67.769v86.434l96.83-76.978c24.408-17.73 48.383 2.624 48.383 20.292v241.914c0.001 17.653-27.966 35.693-55.062 12.22zM300.757 99.724c-29.405 0-56.283 10.108-77.763 26.899l41.465 36.767c10.431-5.832 22.425-9.193 35.235-9.193 40.267 0 72.916 32.649 72.916 72.918 0 9.71-1.948 18.928-5.428 27.357l43.423 38.501c11.424-19.13 18.006-41.484 18.006-65.391 0-70.607-57.246-127.855-127.853-127.855zM172.899 227.58c0 70.608 57.248 127.857 127.858 127.857 10.75 0 21.038-1.717 30.993-4.214l108.379 96.096h-302.237c-36.569 0-66.349-30.419-66.349-67.799v-259.037l102.947 91.272c-0.654 5.243-1.592 10.426-1.592 15.826z" horiz-adv-x="667" />
|
<glyph unicode="" d="M611.967 358.573l-90.149-87.978v108.924c0 3.831-0.333 7.574-0.951 11.216l36.847 32.673c13.174 11.705 14.42 31.9 2.684 45.12-11.737 13.203-31.902 14.436-45.136 2.7l-504.51-447.356c-13.204-11.705-14.421-31.903-2.699-45.104 6.303-7.118 15.091-10.769 23.925-10.769 7.538 0 15.107 2.652 21.195 8.050l47.92 42.49c10.498-7.313 23.13-11.616 36.796-11.616h317.596c36.55 0 66.33 30.404 66.33 67.769v86.434l96.83-76.978c24.408-17.73 48.383 2.624 48.383 20.292v241.914c0.001 17.653-27.966 35.693-55.062 12.22zM300.757 99.724c-29.405 0-56.283 10.108-77.763 26.899l41.465 36.767c10.431-5.832 22.425-9.193 35.235-9.193 40.267 0 72.916 32.649 72.916 72.918 0 9.71-1.948 18.928-5.428 27.357l43.423 38.501c11.424-19.13 18.006-41.484 18.006-65.391 0-70.607-57.246-127.855-127.853-127.855zM172.899 227.58c0 70.608 57.248 127.857 127.858 127.857 10.75 0 21.038-1.717 30.993-4.214l108.379 96.096h-302.237c-36.569 0-66.349-30.419-66.349-67.799v-259.037l102.947 91.272c-0.654 5.243-1.592 10.426-1.592 15.826z" horiz-adv-x="667" />
|
||||||
<glyph unicode="" d="M560.562 469.433c-11.74 13.207-31.942 14.425-45.148 2.686l-504.653-447.452c-13.207-11.709-14.426-31.908-2.717-45.116 6.306-7.122 15.112-10.774 23.947-10.774 7.525 0 15.112 2.654 21.201 8.054l128.536 113.967c16.613-11.432 34.994-19.839 54.305-24.856-35.096-17.827-59.386-53.858-59.386-95.947h215.936c0 42.868-25.164 79.558-61.382 97.039 27.475 7.245 52.921 19.983 73.748 38.748 25.944 23.356 56.856 65.757 56.856 135.165v65.162c0 9.406-3.962 17.883-10.293 23.899l106.332 94.279c13.21 11.738 14.424 31.908 2.717 45.147zM395.767 240.946c0-78.993-58.825-114.961-113.495-114.961-17.607 0-34.329 3.608-49.142 10.393l27.868 24.708c7.366-2.295 15.178-3.566 23.305-3.566 44.678 0 80.992 36.344 80.992 80.99v15.050l30.474 27.021v-39.635zM365.295 396.933c0 44.649-36.313 80.992-80.992 80.992-44.649 0-80.992-36.344-80.992-80.992v-158.425c0-0.125 0-0.249 0-0.374l161.984 143.625v15.174zM175.398 213.345c-1.623 8.741-2.559 17.891-2.559 27.601v65.161c0 18.203-14.8 33.002-33.003 33.002-18.233 0-33.002-14.798-33.002-33.002v-65.161c0-28.599 5.558-53.513 14.549-75.466l54.015 47.865z" horiz-adv-x="569" />
|
<glyph unicode="" d="M560.562 469.433c-11.74 13.207-31.942 14.425-45.148 2.686l-504.653-447.452c-13.207-11.709-14.426-31.908-2.717-45.116 6.306-7.122 15.112-10.774 23.947-10.774 7.525 0 15.112 2.654 21.201 8.054l128.536 113.967c16.613-11.432 34.994-19.839 54.305-24.856-35.096-17.827-59.386-53.858-59.386-95.947h215.936c0 42.868-25.164 79.558-61.382 97.039 27.475 7.245 52.921 19.983 73.748 38.748 25.944 23.356 56.856 65.757 56.856 135.165v65.162c0 9.406-3.962 17.883-10.293 23.899l106.332 94.279c13.21 11.738 14.424 31.908 2.717 45.147zM395.767 240.946c0-78.993-58.825-114.961-113.495-114.961-17.607 0-34.329 3.608-49.142 10.393l27.868 24.708c7.366-2.295 15.178-3.566 23.305-3.566 44.678 0 80.992 36.344 80.992 80.99v15.050l30.474 27.021v-39.635zM365.295 396.933c0 44.649-36.313 80.992-80.992 80.992-44.649 0-80.992-36.344-80.992-80.992v-158.425c0-0.125 0-0.249 0-0.374l161.984 143.625v15.174zM175.398 213.345c-1.623 8.741-2.559 17.891-2.559 27.601v65.161c0 18.203-14.8 33.002-33.003 33.002-18.233 0-33.002-14.798-33.002-33.002v-65.161c0-28.599 5.558-53.513 14.549-75.466l54.015 47.865z" horiz-adv-x="569" />
|
||||||
<glyph unicode="" d="M429.207 339.972c-18.298 0-33.123-14.826-33.123-33.091v-65.362c0-79.211-58.991-115.298-113.817-115.298-29.337 0-56.309 9.935-75.93 27.98-22.115 20.409-33.848 50.601-33.848 87.32v65.363c0 18.265-14.827 33.091-33.091 33.091-18.265 0-33.091-14.826-33.091-33.091v-65.363c0-97.917 59.747-157.382 129.589-175.52-35.204-17.855-59.588-54.007-59.588-96.216h216.559c0 42.996-25.204 79.81-61.514 97.286 27.539 7.32 53.060 20.063 73.943 38.895 26.025 23.438 57.004 65.963 57.004 135.553v65.363c0 18.265-14.795 33.091-33.092 33.091zM284.286 157.86c-44.794 0-81.23 36.466-81.23 81.26v158.832c0 44.795 36.435 81.23 81.23 81.23 44.796 0 81.262-36.435 81.262-81.23v-158.832c0.002-44.796-36.464-81.26-81.262-81.26z" horiz-adv-x="569" />
|
<glyph unicode="" d="M429.207 339.972c-18.298 0-33.123-14.826-33.123-33.091v-65.362c0-79.211-58.991-115.298-113.817-115.298-29.337 0-56.309 9.935-75.93 27.98-22.115 20.409-33.848 50.601-33.848 87.32v65.363c0 18.265-14.827 33.091-33.091 33.091s-33.091-14.826-33.091-33.091v-65.363c0-97.917 59.747-157.382 129.589-175.52-35.204-17.855-59.588-54.007-59.588-96.216h216.559c0 42.996-25.204 79.81-61.514 97.286 27.539 7.32 53.060 20.063 73.943 38.895 26.025 23.438 57.004 65.963 57.004 135.553v65.363c0 18.265-14.795 33.091-33.092 33.091zM284.286 157.86c-44.794 0-81.23 36.466-81.23 81.26v158.832c0 44.795 36.435 81.23 81.23 81.23 44.796 0 81.262-36.435 81.262-81.23v-158.832c0.002-44.796-36.464-81.26-81.262-81.26z" horiz-adv-x="569" />
|
||||||
<glyph unicode="" d="M256.178 480c-141.228 0-256.178-114.919-256.178-256.239 0-141.195 114.95-256.113 256.178-256.113 141.257 0 256.207 114.919 256.207 256.113 0 141.32-114.95 256.239-256.207 256.239zM256.178 7.428c-119.272 0-216.335 97.063-216.335 216.333 0 119.398 97.063 216.429 216.335 216.429 119.3 0 216.428-97.031 216.428-216.429 0-119.27-97.127-216.333-216.428-216.333zM256.272 427.481c-112.377 0-203.754-91.375-203.754-203.657 0-112.281 91.375-203.657 203.754-203.657 112.219 0 203.594 91.377 203.594 203.658-0.002 112.283-91.375 203.658-203.594 203.658zM256.272 63.661c-88.358 0-160.226 71.902-160.226 160.162 0 88.262 71.868 160.162 160.226 160.162 88.262 0 160.098-71.901 160.098-160.162 0-88.26-71.837-160.162-160.098-160.162zM141.925 281.394l-0.477-0.699v-117.207l0.477-0.699c7.879-11.53 18.237-22.271 30.85-31.899l4.481-3.401v189.171l-4.481-3.368c-12.55-9.595-22.907-20.272-30.85-31.899zM207.819 332.865l-1.81-0.667v-220.18l1.81-0.699c9.341-3.527 19.444-5.97 30.883-7.466l3.112-0.381v237.207l-3.082-0.381c-11.119-1.398-21.508-3.876-30.913-7.435zM273.683 340.299l-3.082 0.381v-237.208l3.082 0.381c11.151 1.397 21.538 3.906 30.882 7.432l1.842 0.7v220.244l-1.842 0.667c-9.406 3.526-19.762 6.005-30.882 7.403zM370.49 281.394c-7.846 11.501-18.236 22.24-30.849 31.899l-4.447 3.43v-189.234l4.447 3.401c12.675 9.69 23.066 20.431 30.849 31.93l0.445 0.699v117.176l-0.445 0.7z" horiz-adv-x="513" />
|
<glyph unicode="" d="M256.178 480c-141.228 0-256.178-114.919-256.178-256.239 0-141.195 114.95-256.113 256.178-256.113 141.257 0 256.207 114.919 256.207 256.113 0 141.32-114.95 256.239-256.207 256.239zM256.178 7.428c-119.272 0-216.335 97.063-216.335 216.333 0 119.398 97.063 216.429 216.335 216.429 119.3 0 216.428-97.031 216.428-216.429 0-119.27-97.127-216.333-216.428-216.333zM256.272 427.481c-112.377 0-203.754-91.375-203.754-203.657s91.375-203.657 203.754-203.657c112.219 0 203.594 91.377 203.594 203.658-0.002 112.283-91.375 203.658-203.594 203.658zM256.272 63.661c-88.358 0-160.226 71.902-160.226 160.162 0 88.262 71.868 160.162 160.226 160.162 88.262 0 160.098-71.901 160.098-160.162 0-88.26-71.837-160.162-160.098-160.162zM141.925 281.394l-0.477-0.699v-117.207l0.477-0.699c7.879-11.53 18.237-22.271 30.85-31.899l4.481-3.401v189.171l-4.481-3.368c-12.55-9.595-22.907-20.272-30.85-31.899zM207.819 332.865l-1.81-0.667v-220.18l1.81-0.699c9.341-3.527 19.444-5.97 30.883-7.466l3.112-0.381v237.207l-3.082-0.381c-11.119-1.398-21.508-3.876-30.913-7.435zM273.683 340.299l-3.082 0.381v-237.208l3.082 0.381c11.151 1.397 21.538 3.906 30.882 7.432l1.842 0.7v220.244l-1.842 0.667c-9.406 3.526-19.762 6.005-30.882 7.403zM370.49 281.394c-7.846 11.501-18.236 22.24-30.849 31.899l-4.447 3.43v-189.234l4.447 3.401c12.675 9.69 23.066 20.431 30.849 31.93l0.445 0.699v117.176l-0.445 0.7z" horiz-adv-x="513" />
|
||||||
<glyph unicode="" d="M476.183 480.067h-410.238c-36.514 0-66.266-30.38-66.266-67.728v-376.179c0-37.33 29.752-67.712 66.266-67.712h410.24c36.545 0 66.298 30.383 66.298 67.712v376.179c-0.001 37.347-29.754 67.728-66.299 67.728zM473.067 39.401h-403.947v369.731h403.917l0.029-369.731zM284.871 255.938l45.886 48.433-38.652 38.654 158.197 42.52-42.49-158.195-37.678 37.647-45.917-48.433zM257.382 192.281l-45.883-48.433 38.65-38.652-158.194-42.522 42.489 158.194 37.678-37.645 45.917 48.435z" horiz-adv-x="545" />
|
<glyph unicode="" d="M476.183 480.067h-410.238c-36.514 0-66.266-30.38-66.266-67.728v-376.179c0-37.33 29.752-67.712 66.266-67.712h410.24c36.545 0 66.298 30.383 66.298 67.712v376.179c-0.001 37.347-29.754 67.728-66.299 67.728zM473.067 39.401h-403.947v369.731h403.917l0.029-369.731zM284.871 255.938l45.886 48.433-38.652 38.654 158.197 42.52-42.49-158.195-37.678 37.647-45.917-48.433zM257.382 192.281l-45.883-48.433 38.65-38.652-158.194-42.522 42.489 158.194 37.678-37.645 45.917 48.435z" horiz-adv-x="545" />
|
||||||
<glyph unicode="" d="M476.613 479.59h-410.332c-36.523 0-66.281-30.388-66.281-67.744v-376.262c0-37.324 29.759-67.71 66.281-67.71h410.33c36.553 0 66.312 30.388 66.312 67.711v376.262c0.001 37.356-29.758 67.744-66.311 67.744zM473.497 38.824h-404.039v369.798h404.009l0.031-369.798zM457.769 353.35l-45.897-48.445 38.663-38.661-158.232-42.515 42.5 158.232 37.687-37.67 45.926 48.445zM85.313 94.111l45.897 48.442-38.661 38.663 158.232 42.514-42.499-158.23-37.686 37.671-45.928-48.445z" horiz-adv-x="545" />
|
<glyph unicode="" d="M476.613 479.59h-410.332c-36.523 0-66.281-30.388-66.281-67.744v-376.262c0-37.324 29.759-67.71 66.281-67.71h410.33c36.553 0 66.312 30.388 66.312 67.711v376.262c0.001 37.356-29.758 67.744-66.311 67.744zM473.497 38.824h-404.039v369.798h404.009l0.031-369.798zM457.769 353.35l-45.897-48.445 38.663-38.661-158.232-42.515 42.5 158.232 37.687-37.67 45.926 48.445zM85.313 94.111l45.897 48.442-38.661 38.663 158.232 42.514-42.499-158.23-37.686 37.671-45.928-48.445z" horiz-adv-x="545" />
|
||||||
<glyph unicode="" d="M256.518 480c141.785-0.094 256.207-114.737 256.018-256.332-0.188-141.878-114.483-256.114-256.271-256.144-141.595-0.034-256.456 114.737-256.267 256.050 0.187 142.319 114.483 256.551 256.518 256.426zM256.142 405.365c-100.395-0.063-181.478-81.146-181.416-181.507 0-100.553 80.894-181.541 181.416-181.667 100.582-0.156 181.791 81.147 181.728 181.886-0.125 100.426-81.209 181.351-181.727 181.287zM298.696 223.794h-0.252l-0.063 0.063h0.315l57.808 57.933c0 0-39.123 39.252-41.077 41.077l-58.877-58.091-59.507 59.098-41.012-41.139 44.407-44.409 13.337-14.531h0.251l0.126-0.127h-0.378l-57.744-57.903c0 0 39.125-39.282 41.012-41.106l58.88 58.123 59.504-59.13 41.077 41.203-44.408 44.41-13.4 14.531z" horiz-adv-x="513" />
|
<glyph unicode="" d="M256.518 480c141.785-0.094 256.207-114.737 256.018-256.332-0.188-141.878-114.483-256.114-256.271-256.144-141.595-0.034-256.456 114.737-256.267 256.050 0.187 142.319 114.483 256.551 256.518 256.426zM256.142 405.365c-100.395-0.063-181.478-81.146-181.416-181.507 0-100.553 80.894-181.541 181.416-181.667 100.582-0.156 181.791 81.147 181.728 181.886-0.125 100.426-81.209 181.351-181.727 181.287zM298.696 223.794h-0.252l-0.063 0.063h0.315l57.808 57.933c0 0-39.123 39.252-41.077 41.077l-58.877-58.091-59.507 59.098-41.012-41.139 44.407-44.409 13.337-14.531h0.251l0.126-0.127h-0.378l-57.744-57.903c0 0 39.125-39.282 41.012-41.106l58.88 58.123 59.504-59.13 41.077 41.203-44.408 44.41-13.4 14.531z" horiz-adv-x="513" />
|
||||||
<glyph unicode="" d="M33.245-31.998h513.97zM599.298 243.993c-67.851 67.947-135.707 135.83-203.683 203.62-3.122 3.045-6.934 5.857-10.933 7.327-15.618 5.7-31.74-6.404-31.864-23.837-0.127-33.832-0.063-67.666-0.063-101.483 0-2.14 0-4.31 0-6.982-3.062 0-5.435 0-7.81 0-53.123 0-106.167 0.031-159.243 0-17.589-0.016-27.43-9.855-27.43-27.461-0.030-46.389-0.030-92.75 0-139.173 0-17.9 9.841-27.74 27.805-27.74 52.887-0.031 105.712 0 158.555 0 2.438 0 4.811 0 8.123 0 0-2.688 0-4.687 0-6.654 0-33.397 0.125-66.851 0-100.248 0-11.529 4.623-20.12 15.121-24.774 10.558-4.685 19.68-1.405 27.615 6.469 67.978 68.039 136.016 136.017 203.994 204.087 11.745 11.716 11.62 25.056-0.187 36.847zM266.298 22.365c-1.313 9.81-11.028 16.058-13.964 17.713-4.405 2.593-9.185 2.906-12.841 2.906l-3.185-0.063-113.963 0.094c-28.021 0.062-49.234 21.087-49.296 48.858-0.094 88.563-0.094 177.161 0.032 265.755 0.029 26.991 21.335 48.265 48.484 48.406l117.961 0.031c16.713 0.015 25.647 8.060 27.24 24.618 0.5 6.482 0.405 13.026 0.219 19.586-0.564 19.869-9.685 28.74-29.553 28.756l-108.933-0.060c-7.747 0-15.463-0.281-23.18-1.172-57.76-6.577-103.778-56.825-104.841-114.399-0.656-31.944-0.498-63.902-0.342-95.859l0.093-30.101h-0.189c0 0-0.060-113.977 0.033-144.906 0.124-67.851 50.764-120.398 117.772-122.24 16.588-0.438 33.209-0.594 49.827-0.594h0.032l74.006 0.377c14.684 0 23.741 8.685 24.865 23.772 0.877 11.029 0.814 20.087-0.279 28.522z" horiz-adv-x="608" />
|
<glyph unicode="" d="M33.245-31.998h513.97zM599.298 243.993c-67.851 67.947-135.707 135.83-203.683 203.62-3.122 3.045-6.934 5.857-10.933 7.327-15.618 5.7-31.74-6.404-31.864-23.837-0.127-33.832-0.063-67.666-0.063-101.483 0-2.14 0-4.31 0-6.982-3.062 0-5.435 0-7.81 0-53.123 0-106.167 0.031-159.243 0-17.589-0.016-27.43-9.855-27.43-27.461-0.030-46.389-0.030-92.75 0-139.173 0-17.9 9.841-27.74 27.805-27.74 52.887-0.031 105.712 0 158.555 0 2.438 0 4.811 0 8.123 0 0-2.688 0-4.687 0-6.654 0-33.397 0.125-66.851 0-100.248 0-11.529 4.623-20.12 15.121-24.774 10.558-4.685 19.68-1.405 27.615 6.469 67.978 68.039 136.016 136.017 203.994 204.087 11.745 11.716 11.62 25.056-0.187 36.847zM266.298 22.365c-1.313 9.81-11.028 16.058-13.964 17.713-4.405 2.593-9.185 2.906-12.841 2.906l-3.185-0.063-113.963 0.094c-28.021 0.062-49.234 21.087-49.296 48.858-0.094 88.563-0.094 177.161 0.032 265.755 0.029 26.991 21.335 48.265 48.484 48.406l117.961 0.031c16.713 0.015 25.647 8.060 27.24 24.618 0.5 6.482 0.405 13.026 0.219 19.586-0.564 19.869-9.685 28.74-29.553 28.756l-108.933-0.060c-7.747 0-15.463-0.281-23.18-1.172-57.76-6.577-103.778-56.825-104.841-114.399-0.656-31.944-0.498-63.902-0.342-95.859l0.093-30.101h-0.189c0 0-0.060-113.977 0.033-144.906 0.124-67.851 50.764-120.398 117.772-122.24 16.588-0.438 33.209-0.594 49.827-0.594h0.032l74.006 0.377c14.684 0 23.741 8.685 24.865 23.772 0.877 11.029 0.814 20.087-0.279 28.522z" horiz-adv-x="608" />
|
||||||
|
<glyph unicode="" d="M419.667 193.243c0-39.599-32.128-71.731-71.743-71.731-39.587 0-71.716 32.131-71.716 71.731 0 39.613 32.129 71.716 71.716 71.716 39.615-0.002 71.743-32.102 71.743-71.716zM686.385 353.243c-3.298 19.729-14.748 32.084-35.303 34.638-11.894 1.459-19.128 7.819-22.363 19.52-4.853 17.76-16.839 29.496-33.905 35.38-12.404 4.298-25.15 8.231-38.093 10.245-34.827 5.455-70.255 7.962-104.763 14.971-34.765 7.089-69.526 11.671-104.447 12.036-34.923-0.365-69.685-4.947-104.447-12.036-34.511-7.008-69.938-9.514-104.763-14.97-12.942-2.014-25.692-5.948-38.094-10.245-17.065-5.884-29.052-17.62-33.907-35.38-3.234-11.702-10.467-18.061-22.361-19.52-20.552-2.554-32.003-14.91-35.3-34.639-3.394-20.205-5.869-40.601-8.405-60.942-1.364-11.055 3.203-16.288 15.193-16.224 60.419 0.349 120.846 0.349 181.298 0.048 12.023-0.064 16.558 4.694 16.716 16.669 0.381 28.685-2.315 55.947-23.568 78.309-3.52 3.695-5.424 12.64-3.363 16.923 2.031 4.25 20.428 8.358 22.996 8.358 21.664-0.095 21.569-0.111 24.709-21.712 0.602-4.14 1.269-9.23 3.901-11.926 15.16-15.431 10.626-33.353 3.617-48.817-15.195-33.57-30.768-67.413-50.434-98.435-36.729-57.916-52.205-80.372-99.34-130.422-29.337-31.147-34.286-50.844-34.286-68.733 0-35.495 20.519-48.372 68.574-48.372 90.807 0 130.454 0.158 221.264 0.158 90.807 0 130.452-0.158 221.264-0.158 48.051 0 68.571 12.876 68.571 48.372 0 17.889-4.949 37.587-34.287 68.733-47.132 50.050-62.611 72.506-99.339 130.422-19.666 31.021-35.238 64.865-50.43 98.435-7.010 15.464-11.546 33.385 3.617 48.817 2.632 2.697 3.298 7.787 3.901 11.926 3.14 21.601 3.045 21.617 24.706 21.712 2.57 0 20.966-4.108 22.996-8.358 2.063-4.283 0.159-13.227-3.361-16.923-21.251-22.362-23.949-49.625-23.568-78.309 0.158-11.974 4.694-16.733 16.715-16.669 60.454 0.302 120.877 0.302 181.3-0.048 11.987-0.063 16.554 5.171 15.192 16.224-2.538 20.343-5.009 40.737-8.403 60.942zM479.995 191.752c0-73.157-59.312-132.468-132.485-132.468s-132.487 59.31-132.487 132.468c0 73.159 59.312 132.468 132.487 132.468 73.171 0 132.485-59.31 132.485-132.468z" horiz-adv-x="695" />
|
||||||
|
<glyph unicode="" d="M155.131 15.215c0-26.065-21.103-47.215-47.2-47.215h-60.703c-26.098 0-47.229 21.15-47.229 47.215v417.835c0 26.079 21.133 47.229 47.229 47.229h60.701c26.097 0 47.2-21.15 47.2-47.229v-417.835zM538.559 480.28h-280.993c-36.459 0-66.165-30.337-66.165-67.626v-377.058c0-37.259 29.706-67.596 66.165-67.596h280.993c36.49 0 66.197 30.337 66.197 67.596v377.058c0 37.29-29.707 67.626-66.197 67.626zM264.915 413.453h266.327l0.031-71.649h-266.358v71.649zM321.627 25.814h-56.776v56.776h56.776v-56.776zM321.627 128.374h-56.776v56.777h56.776v-56.777zM321.691 231.878h-56.776v56.777h56.776v-56.777zM426.45 25.814h-56.776v56.776h56.776v-56.776zM426.45 128.374h-56.776v56.777h56.776v-56.777zM426.514 231.878h-56.778v56.777h56.778v-56.777zM531.274 25.814h-56.778v56.776h56.778v-56.776zM531.274 128.374h-56.778v56.777h56.778v-56.777zM531.335 231.878h-56.777v56.777h56.777v-56.777z" horiz-adv-x="605" />
|
||||||
|
<glyph unicode="" d="M561.722 469.507c-11.797 13.24-32.066 14.495-45.37 2.697l-504.135-446.718c-13.305-11.734-14.495-32.065-2.73-45.338 6.337-7.153 15.154-10.825 24.063-10.825 7.562 0 15.186 2.7 21.272 8.098l65.023 57.61c45.371-40.922 105.284-66.082 171.237-66.050 141.408 0.031 255.457 113.985 255.644 255.486 0.063 54.967-17.341 105.683-46.75 147.36l59.044 52.313c13.241 11.763 14.499 32.065 2.702 45.368zM472.211 224.909c0.064-100.461-80.948-181.601-181.255-181.476-43.78 0.062-83.786 15.575-115.031 41.284l165.638 146.755v-36.588c0.536-30.497 16.348-46.090 47.472-46.846 30.998 0.756 46.843 16.382 47.565 46.878v20.548h-36.113v-23.75c0.125-2.321-0.282-5.303-1.255-8.974-0.625-1.63-1.724-3.010-3.262-4.046-1.599-1.286-3.923-1.914-6.934-1.914-5.272 0.155-8.566 2.134-9.913 5.961-0.534 1.756-0.974 3.452-1.254 5.082-0.127 1.443-0.188 2.766-0.188 3.893v71.755l21.238 18.817c0.108-0.216 0.226-0.425 0.315-0.651 0.974-3.233 1.381-6.399 1.255-9.538v-18.386h36.113v15.060c-0.123 15.623-4.543 27.35-13.181 35.224l20.356 18.034c17.894-28.028 28.401-61.293 28.433-97.122zM119.897 165.735c-6.306 18.512-9.913 38.279-9.913 58.957-0.095 100.118 80.792 181.005 180.973 181.067 28.426 0 55.156-6.651 79.067-18.199l58.923 52.211c-39.722 25.476-86.879 40.409-137.646 40.474-141.689 0.091-255.677-113.865-255.894-255.838-0.063-39.783 9.318-77.34 25.569-110.941l58.924 52.269zM194.288 313.010h-48.757v-124.529l36.115 32.035v0.344h0.407l58.86 52.209c0 0.282 0.061 0.47 0.061 0.755 0.376 26.949-15.184 40.034-46.687 39.185zM202.98 280.759c0.971-1.384 1.537-3.235 1.661-5.556 0.156-2.226 0.219-4.8 0.219-7.623 0.125-5.458-0.345-9.915-1.475-13.493-1.476-3.797-5.492-5.678-12.079-5.678h-9.662v37.022h7.655c3.921 0 6.933-0.341 9.036-1.095 2.198-0.787 3.734-1.976 4.645-3.577z" horiz-adv-x="570" />
|
||||||
|
<glyph unicode="" d="M290.639 480.854c142.428-0.095 257.404-115.258 257.213-257.498-0.189-142.524-115.036-257.277-257.435-257.308-142.27-0.031-257.656 115.259-257.466 257.211 0.219 142.968 115.005 257.719 257.688 257.595zM290.289 405.878c-100.882-0.061-182.333-81.516-182.239-182.332 0-101.009 81.262-182.368 182.239-182.492 101.009-0.158 182.587 81.515 182.524 182.712-0.126 100.884-81.578 182.175-182.524 182.112zM143.849 312.453h49.098c31.721 0.884 47.392-12.259 47.013-39.431 0.127-9.541-1.106-17.441-3.728-23.761-3.002-6.254-9.353-10.994-19.083-14.090v-0.41c14.186-3.13 21.516-11.787 21.99-25.973v-28.844c0-5.623 0.127-11.406 0.379-17.378 0.41-6.002 1.517-10.49 3.348-13.522h-35.923c-1.863 3.032-3.064 7.519-3.57 13.522-0.506 5.971-0.727 11.755-0.569 17.378v26.161c0 4.801-1.107 8.276-3.286 10.49-2.338 2.053-6.351 3.095-12.006 3.095h-7.299v-70.645h-36.365v163.41zM180.214 247.43h9.732c6.636 0 10.679 1.897 12.166 5.688 1.138 3.602 1.611 8.152 1.484 13.585 0 2.908-0.063 5.434-0.221 7.709-0.126 2.337-0.696 4.202-1.676 5.623-0.916 1.579-2.463 2.781-4.676 3.57-2.117 0.727-5.149 1.106-9.1 1.106h-7.709v-37.282zM249.186 312.453h81.041v-31.343h-44.675v-32.794h39.051v-31.343h-39.051v-36.555h46.411v-31.375h-82.779v163.409zM341.253 268c0.158 15.891 4.708 27.771 13.712 35.703 8.72 7.645 20.093 11.468 34.091 11.468 14.123 0 25.56-3.823 34.312-11.5 8.91-7.899 13.46-19.811 13.586-35.704v-15.166h-36.365v18.516c0.127 3.096-0.285 6.319-1.264 9.604-0.632 1.579-1.738 2.969-3.286 4.107-1.611 0.757-3.949 1.202-6.982 1.202-5.308-0.158-8.625-1.928-9.983-5.309-1.106-3.286-1.611-6.508-1.454-9.604v-80.978c0-1.137 0.063-2.433 0.19-3.886 0.284-1.705 0.727-3.412 1.264-5.116 1.358-3.887 4.676-5.878 9.983-6.003 3.034 0 5.372 0.63 6.982 1.896 1.549 1.075 2.654 2.433 3.286 4.108 0.98 3.665 1.391 6.665 1.264 9.003v23.918h36.365v-20.664c-0.726-30.774-16.682-46.507-47.897-47.235-31.342 0.727-47.265 16.461-47.803 47.171v74.469z" horiz-adv-x="571" />
|
||||||
|
<glyph unicode="" d="M508.412 2.883c-1.026 7.687-2.666 15.269-3.93 22.923-4.167 25.229-16.503 43.252-41.031 53.961-39.187 17.099-77.551 36.060-116.055 54.697-27.843 13.512-26.204 44.26-17.048 57.207 5.945 8.44 11.172 17.286 11.788 28.426 0.222 4.113 4.151 9.495 7.909 11.647 13.035 7.518 19.081 19.782 25.010 32.491 1.555 3.348 3.69 6.594 6.133 9.361 4.236 4.834 6.132 9.618 3.039 15.921-0.717 1.485 0.666 4.167 1.4 6.183 2.152 6.013 5.142 11.838 6.56 18.022 1.778 7.669 2.699 15.612 3.126 23.487 0.187 3.262-3.022 6.764-2.681 9.975 1.741 15.956-7.279 28.101-12.37 41.988-6.233 17.099-18.464 27.81-29.26 40.553-2.033 2.392-2.613 6.526-2.786 9.943-0.36 7.294-3.366 10.898-11.002 9.906-3.055-0.394-6.386-1.248-9.205-0.496-2.478 0.667-6.203 3.144-6.338 5.056-0.769 9.668-4.132 11.258-14.008 9.618-6.182-1.025-14.228 4.577-20.292 8.78-5.072 3.521-9.445 5.023-15.341 3.588-2.457-0.598-5.772-0.495-7.858 0.717-2.221 1.332-4.387 2.119-6.559 2.562v0.374c-0.478-0.016-0.991-0.102-1.469-0.154-0.477 0.051-0.956 0.137-1.434 0.154v-0.375c-2.185-0.444-4.375-1.231-6.578-2.562-2.066-1.213-5.381-1.316-7.84-0.718-5.911 1.434-10.285-0.068-15.342-3.588-6.079-4.202-14.108-9.805-20.292-8.781-9.873 1.641-13.255 0.052-14.024-9.618-0.154-1.912-3.843-4.389-6.338-5.056-2.834-0.752-6.149 0.102-9.223 0.495-7.618 0.992-10.625-2.613-10.985-9.906-0.169-3.416-0.751-7.551-2.784-9.943-10.794-12.743-23.025-23.454-29.278-40.553-5.058-13.886-14.094-26.031-12.335-41.987 0.343-3.211-2.872-6.714-2.7-9.975 0.445-7.875 1.368-15.818 3.127-23.487 1.418-6.184 4.407-12.010 6.576-18.022 0.719-2.016 2.121-4.698 1.384-6.183-3.091-6.303-1.179-11.087 3.058-15.921 2.427-2.767 4.56-6.013 6.115-9.361 5.929-12.709 11.974-24.974 25.007-32.491 3.76-2.152 7.689-7.534 7.929-11.647 0.596-11.14 5.825-19.986 11.785-28.426 9.141-12.947 10.573-43.369-17.081-57.207-38.228-19.132-76.871-37.6-116.021-54.697-24.564-10.709-36.863-28.731-41.032-53.961-1.263-7.656-2.939-15.238-3.929-22.923-1.505-11.464-3.912-34.883-3.912-34.883h512.306c-0.001 0-2.39 23.419-3.894 34.883z" horiz-adv-x="513" />
|
||||||
|
<glyph unicode="" d="M513.087 224.534c0-141.673-114.855-256.526-256.554-256.526-141.674 0-256.534 114.851-256.534 256.526 0 141.692 114.861 256.553 256.534 256.553 141.7 0 256.554-114.861 256.554-256.553zM256.534-31.993c67.863 0 129.556 26.356 175.437 69.37-4.858 5.825-11.276 10.557-19.557 14.171-29.467 12.873-58.313 27.128-87.267 41.128-20.935 10.161-19.702 33.293-12.82 43.029 4.471 6.346 8.402 12.999 8.864 21.373 0.166 3.084 3.12 7.142 5.945 8.761 9.802 5.652 14.349 14.873 18.802 24.43 1.17 2.515 2.777 4.945 4.615 7.038 3.185 3.622 4.612 7.218 2.286 11.971-0.543 1.104 0.502 3.12 1.053 4.637 1.619 4.534 3.866 8.901 4.93 13.558 1.335 5.774 2.029 11.74 2.351 17.661 0.14 2.451-2.272 5.092-2.017 7.493 1.31 12.011-5.471 21.136-9.299 31.579-4.688 12.857-13.885 20.91-22.002 30.485-1.529 1.812-1.964 4.919-2.094 7.476-0.269 5.49-2.53 8.207-8.272 7.462-2.299-0.3-4.805-0.943-6.921-0.378-1.864 0.494-4.663 2.362-4.767 3.802-0.577 7.269-3.106 8.465-10.533 7.238-4.648-0.772-10.697 3.429-15.257 6.601-3.816 2.646-7.104 3.777-11.534 2.69-1.849-0.45-4.341-0.373-5.908 0.547-1.671 0.988-3.303 1.592-4.933 1.919v0.276c-0.36-0.007-0.745-0.065-1.104-0.108-0.361 0.044-0.72 0.103-1.078 0.108v-0.276c-1.645-0.327-3.287-0.931-4.945-1.919-1.556-0.918-4.046-0.996-5.899-0.547-4.443 1.087-7.724-0.044-11.532-2.69-4.578-3.173-10.611-7.373-15.259-6.601-7.431 1.226-9.97 0.031-10.547-7.238-0.109-1.439-2.897-3.308-4.758-3.802-2.139-0.565-4.624 0.077-6.944 0.378-5.728 0.745-7.994-1.971-8.258-7.462-0.131-2.555-0.565-5.665-2.095-7.476-8.111-9.575-17.308-17.629-22.009-30.485-3.814-10.443-10.602-19.568-9.285-31.579 0.256-2.401-2.152-5.042-2.023-7.493 0.327-5.923 1.020-11.888 2.351-17.661 1.065-4.656 3.313-9.024 4.945-13.558 0.547-1.516 1.587-3.531 1.041-4.637-2.325-4.754-0.894-8.351 2.291-11.971 1.837-2.094 3.437-4.523 4.612-7.038 4.45-9.555 8.996-18.779 18.798-24.43 2.827-1.619 5.78-5.676 5.952-8.761 0.457-8.374 4.387-15.027 8.869-21.373 6.873-9.735 7.951-32.623-12.837-43.029-28.76-14.386-57.8-28.255-87.251-41.128-8.285-3.615-14.704-8.347-19.561-14.169 45.88-43.015 107.569-69.372 175.422-69.372z" horiz-adv-x="513" />
|
||||||
</font></defs></svg>
|
</font></defs></svg>
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 24 KiB |
BIN
fonts/jitsi.ttf
BIN
fonts/jitsi.woff
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 7.8 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 9.8 KiB |
297
index.html
|
@ -1,4 +1,4 @@
|
||||||
<html itemscope itemtype="http://schema.org/Product" prefix="og: http://ogp.me/ns#">
|
<html itemscope itemtype="http://schema.org/Product" prefix="og: http://ogp.me/ns#" xmlns="http://www.w3.org/1999/html">
|
||||||
<head>
|
<head>
|
||||||
<title>Jitsi Videobridge meets WebRTC</title>
|
<title>Jitsi Videobridge meets WebRTC</title>
|
||||||
<link rel="icon" type="image/png" href="/images/favicon.ico"/>
|
<link rel="icon" type="image/png" href="/images/favicon.ico"/>
|
||||||
|
@ -9,7 +9,7 @@
|
||||||
<meta itemprop="name" content="Jitsi Meet"/>
|
<meta itemprop="name" content="Jitsi Meet"/>
|
||||||
<meta itemprop="description" content="Join a WebRTC video conference powered by the Jitsi Videobridge"/>
|
<meta itemprop="description" content="Join a WebRTC video conference powered by the Jitsi Videobridge"/>
|
||||||
<meta itemprop="image" content="/images/jitsilogo.png"/>
|
<meta itemprop="image" content="/images/jitsilogo.png"/>
|
||||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
|
<script src="libs/jquery.min.js"></script>
|
||||||
<script src="libs/strophe/strophe.jingle.adapter.js?v=1"></script><!-- strophe.jingle bundles -->
|
<script src="libs/strophe/strophe.jingle.adapter.js?v=1"></script><!-- strophe.jingle bundles -->
|
||||||
<script src="libs/strophe/strophe.jingle.bundle.js?v=8"></script>
|
<script src="libs/strophe/strophe.jingle.bundle.js?v=8"></script>
|
||||||
<script src="libs/strophe/strophe.jingle.js?v=1"></script>
|
<script src="libs/strophe/strophe.jingle.js?v=1"></script>
|
||||||
|
@ -19,18 +19,20 @@
|
||||||
<script src="libs/strophe/strophe.jingle.session.js?v=1"></script>
|
<script src="libs/strophe/strophe.jingle.session.js?v=1"></script>
|
||||||
<script src="libs/colibri/colibri.focus.js?v=8"></script><!-- colibri focus implementation -->
|
<script src="libs/colibri/colibri.focus.js?v=8"></script><!-- colibri focus implementation -->
|
||||||
<script src="libs/colibri/colibri.session.js?v=1"></script>
|
<script src="libs/colibri/colibri.session.js?v=1"></script>
|
||||||
<script src="//code.jquery.com/ui/1.10.4/jquery-ui.js"></script>
|
<script src="libs/jquery-ui.js"></script>
|
||||||
|
<script src="libs/rayo.js?v=1"></script>
|
||||||
<script src="libs/tooltip.js?v=1"></script><!-- bootstrap tooltip lib -->
|
<script src="libs/tooltip.js?v=1"></script><!-- bootstrap tooltip lib -->
|
||||||
<script src="libs/popover.js?v=1"></script><!-- bootstrap tooltip lib -->
|
<script src="libs/popover.js?v=1"></script><!-- bootstrap tooltip lib -->
|
||||||
<script src="config.js?v=2"></script><!-- adapt to your needs, i.e. set hosts and bosh path -->
|
<script src="config.js?v=4"></script><!-- adapt to your needs, i.e. set hosts and bosh path -->
|
||||||
<script src="muc.js?v=12"></script><!-- simple MUC library -->
|
<script src="muc.js?v=13"></script><!-- simple MUC library -->
|
||||||
<script src="estos_log.js?v=2"></script><!-- simple stanza logger -->
|
<script src="estos_log.js?v=2"></script><!-- simple stanza logger -->
|
||||||
<script src="desktopsharing.js?v=2"></script><!-- desktop sharing -->
|
<script src="desktopsharing.js?v=2"></script><!-- desktop sharing -->
|
||||||
<script src="data_channels.js?v=1"></script><!-- data channels -->
|
<script src="data_channels.js?v=3"></script><!-- data channels -->
|
||||||
<script src="app.js?v=28"></script><!-- application logic -->
|
<script src="app.js?v=5"></script><!-- application logic -->
|
||||||
<script src="commands.js?v=1"></script><!-- application logic -->
|
<script src="commands.js?v=1"></script><!-- application logic -->
|
||||||
<script src="chat.js?v=6"></script><!-- chat logic -->
|
<script src="chat.js?v=9"></script><!-- chat logic -->
|
||||||
<script src="util.js?v=5"></script><!-- utility functions -->
|
<script src="contact_list.js?v=1"></script><!-- contact list logic -->
|
||||||
|
<script src="util.js?v=6"></script><!-- utility functions -->
|
||||||
<script src="etherpad.js?v=8"></script><!-- etherpad plugin -->
|
<script src="etherpad.js?v=8"></script><!-- etherpad plugin -->
|
||||||
<script src="prezi.js?v=4"></script><!-- prezi plugin -->
|
<script src="prezi.js?v=4"></script><!-- prezi plugin -->
|
||||||
<script src="smileys.js?v=2"></script><!-- smiley images -->
|
<script src="smileys.js?v=2"></script><!-- smiley images -->
|
||||||
|
@ -38,16 +40,22 @@
|
||||||
<script src="moderatemuc.js?v=3"></script><!-- moderator plugin -->
|
<script src="moderatemuc.js?v=3"></script><!-- moderator plugin -->
|
||||||
<script src="analytics.js?v=1"></script><!-- google analytics plugin -->
|
<script src="analytics.js?v=1"></script><!-- google analytics plugin -->
|
||||||
<script src="rtp_stats.js?v=1"></script><!-- RTP stats processing -->
|
<script src="rtp_stats.js?v=1"></script><!-- RTP stats processing -->
|
||||||
<script src="videolayout.js?v=3"></script><!-- video ui -->
|
<script src="local_stats.js?v=1"></script><!-- Local stats processing -->
|
||||||
<script src="toolbar.js?v=2"></script><!-- toolbar ui -->
|
<script src="videolayout.js?v=8"></script><!-- video ui -->
|
||||||
|
<script src="toolbar.js?v=4"></script><!-- toolbar ui -->
|
||||||
|
<script src="canvas_util.js?v=1"></script><!-- canvas drawing utils -->
|
||||||
|
<script src="audio_levels.js?v=1"></script><!-- audio levels plugin -->
|
||||||
|
<script src="media_stream.js?v=1"></script><!-- media stream -->
|
||||||
|
<script src="bottom_toolbar.js?v=1"></script><!-- media stream -->
|
||||||
<link href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet">
|
<link href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet">
|
||||||
<link rel="stylesheet" href="css/font.css"/>
|
<link rel="stylesheet" href="css/font.css?v=3"/>
|
||||||
<link rel="stylesheet" type="text/css" media="screen" href="css/main.css?v=21"/>
|
<link rel="stylesheet" type="text/css" media="screen" href="css/main.css?v=23"/>
|
||||||
<link rel="stylesheet" type="text/css" media="screen" href="css/videolayout_default.css?v=7" id="videolayout_default"/>
|
<link rel="stylesheet" type="text/css" media="screen" href="css/videolayout_default.css?v=9" id="videolayout_default"/>
|
||||||
<link rel="stylesheet" href="css/jquery-impromptu.css?v=4">
|
<link rel="stylesheet" href="css/jquery-impromptu.css?v=4">
|
||||||
<link rel="stylesheet" href="css/modaldialog.css?v=3">
|
<link rel="stylesheet" href="css/modaldialog.css?v=3">
|
||||||
<link rel="stylesheet" href="css/popup_menu.css?v=2">
|
<link rel="stylesheet" href="css/popup_menu.css?v=2">
|
||||||
<link rel="stylesheet" href="css/popover.css?v=1">
|
<link rel="stylesheet" href="css/popover.css?v=1">
|
||||||
|
<link rel="stylesheet" href="css/contact_list.css?v=1">
|
||||||
<!--
|
<!--
|
||||||
Link used for inline installation of chrome desktop streaming extension,
|
Link used for inline installation of chrome desktop streaming extension,
|
||||||
is updated automatically from the code with the value defined in config.js -->
|
is updated automatically from the code with the value defined in config.js -->
|
||||||
|
@ -57,84 +65,201 @@
|
||||||
<script src="libs/prezi_player.js?v=2"></script>
|
<script src="libs/prezi_player.js?v=2"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div style="position: relative;" id="header_container">
|
<div id="welcome_page">
|
||||||
<div id="header">
|
<div id="welcome_page_header">
|
||||||
<span id="toolbar">
|
<a href="http://jitsi.org" target="_new">
|
||||||
<a class="button" data-toggle="popover" data-placement="bottom" data-content="Mute / Unmute" onclick='toggleAudio();'>
|
<div id="jitsi_logo"></div>
|
||||||
<i id="mute" class="icon-microphone"></i></a>
|
</a>
|
||||||
<div class="header_button_separator"></div>
|
|
||||||
<a class="button" data-toggle="popover" data-placement="bottom" data-content="Start / stop camera" onclick='buttonClick("#video", "icon-camera icon-camera-disabled");toggleVideo();'>
|
<div id="enter_room">
|
||||||
<i id="video" class="icon-camera"></i></a>
|
<input id="enter_room_field" type="text" placeholder="Enter room name" />
|
||||||
<div class="header_button_separator"></div>
|
<input id="enter_room_button" type="button" value="GO" />
|
||||||
<a class="button" data-toggle="popover" data-placement="bottom" data-content="Lock / unlock room" onclick="Toolbar.openLockDialog();">
|
</div>
|
||||||
<i id="lockIcon" class="icon-security"></i></a>
|
<div id="brand_header"></div>
|
||||||
<div class="header_button_separator"></div>
|
<div id="header_text"></div>
|
||||||
<a class="button" data-toggle="popover" data-placement="bottom" data-content="Invite others" onclick="Toolbar.openLinkDialog();"><i class="icon-link"></i></a>
|
</div>
|
||||||
<div class="header_button_separator"></div>
|
<div id="welcome_page_main">
|
||||||
<span class="toolbar_span">
|
<div id="features">
|
||||||
<a class="button" data-toggle="popover" data-placement="bottom" data-content="Open / close chat" onclick='Chat.toggleChat();'><i id="chatButton" class="icon-chat"></i></a>
|
<div class="feature_row">
|
||||||
|
<div class="feature_holder">
|
||||||
|
<div class="feature_icon">Simple to use</div>
|
||||||
|
<div class="feature_description">
|
||||||
|
No downloads required. uTalk works directly within your browser. Simply share your conference URL with others to get started.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="feature_holder">
|
||||||
|
<div class="feature_icon">Low bandwidth</div>
|
||||||
|
<div class="feature_description">
|
||||||
|
Multi-party video conferences work with as little as 128Kbps. Screen-sharing and audio-only conferences are possible with far less.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="feature_holder">
|
||||||
|
<div class="feature_icon">Open source</div>
|
||||||
|
<div class="feature_description">
|
||||||
|
uTalk is licensed under the <GPL/LGPL/WHATEVER>. You can download, use, modify, and share the software without any restrictions.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="feature_holder">
|
||||||
|
<div class="feature_icon">Unlimited users</div>
|
||||||
|
<div class="feature_description">
|
||||||
|
There are no artificial restrictions on the number of users or conference participants. Server power and bandwidth are the only limiting factors.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="feature_row">
|
||||||
|
<div class="feature_holder">
|
||||||
|
<div class="feature_icon">Screen sharing</div>
|
||||||
|
<div class="feature_description">
|
||||||
|
It's easy to share your screen with others. uTalk is ideal for on-line presentations, lectures, and tech support sessions.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="feature_holder">
|
||||||
|
<div class="feature_icon">Secure rooms</div>
|
||||||
|
<div class="feature_description">
|
||||||
|
Need some privacy? uTalk conference rooms can be secured with a password in order to exclude unwanted guests and prevent interruptions.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="feature_holder">
|
||||||
|
<div class="feature_icon">Shared notes</div>
|
||||||
|
<div class="feature_description">
|
||||||
|
uTalk features Etherpad, a real-time collaborative text editor that's great for meeting minutes, writing articles, and more.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="feature_holder">
|
||||||
|
<div class="feature_icon">Usage statistics</div>
|
||||||
|
<div class="feature_description">
|
||||||
|
Learn about your users through easy integration with Piwik, Google Analytics, and other usage monitoring and statistics systems.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="videoconference_page">
|
||||||
|
<div style="position: relative;" id="header_container">
|
||||||
|
<div id="header">
|
||||||
|
<span id="toolbar">
|
||||||
|
<a class="button" data-toggle="popover" data-placement="bottom" data-content="Mute / Unmute" onclick='toggleAudio();'>
|
||||||
|
<i id="mute" class="icon-microphone"></i>
|
||||||
|
</a>
|
||||||
|
<div class="header_button_separator"></div>
|
||||||
|
<a class="button" data-toggle="popover" data-placement="bottom" data-content="Start / stop camera" onclick='buttonClick("#video", "icon-camera icon-camera-disabled");toggleVideo();'>
|
||||||
|
<i id="video" class="icon-camera"></i>
|
||||||
|
</a>
|
||||||
|
<span id="recording" style="display: none">
|
||||||
|
<div class="header_button_separator"></div>
|
||||||
|
<a class="button" data-toggle="popover" data-placement="bottom" data-content="Record" onclick='toggleRecording();'>
|
||||||
|
<i id="recordButton" class="icon-recEnable"></i>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<div class="header_button_separator"></div>
|
||||||
|
<a class="button" data-toggle="popover" data-placement="bottom" data-content="Lock / unlock room" onclick="Toolbar.openLockDialog();">
|
||||||
|
<i id="lockIcon" class="icon-security"></i>
|
||||||
|
</a>
|
||||||
|
<div class="header_button_separator"></div>
|
||||||
|
<a class="button" data-toggle="popover" data-placement="bottom" data-content="Invite others" onclick="Toolbar.openLinkDialog();">
|
||||||
|
<i class="icon-link"></i>
|
||||||
|
</a>
|
||||||
|
<div class="header_button_separator"></div>
|
||||||
|
<span class="toolbar_span">
|
||||||
|
<a class="button" data-toggle="popover" data-placement="bottom" data-content="Open / close chat" onclick='BottomToolbar.toggleChat();'>
|
||||||
|
<i id="chatButton" class="icon-chat"></i>
|
||||||
|
</a>
|
||||||
|
<span id="unreadMessages"></span>
|
||||||
|
</span>
|
||||||
|
<div class="header_button_separator"></div>
|
||||||
|
<a class="button" data-toggle="popover" data-placement="bottom" data-content="Share Prezi" onclick='Prezi.openPreziDialog();'>
|
||||||
|
<i class="icon-prezi"></i>
|
||||||
|
</a>
|
||||||
|
<span id="etherpadButton">
|
||||||
|
<div class="header_button_separator"></div>
|
||||||
|
<a class="button" data-toggle="popover" data-placement="bottom" data-content="Shared document" onclick='Etherpad.toggleEtherpad(0);'>
|
||||||
|
<i class="icon-share-doc"></i>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<div class="header_button_separator"></div>
|
||||||
|
<span id="desktopsharing" style="display: none">
|
||||||
|
<a class="button" data-toggle="popover" data-placement="bottom" data-content="Share screen" onclick="toggleScreenSharing();">
|
||||||
|
<i class="icon-share-desktop"></i>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<div class="header_button_separator"></div>
|
||||||
|
<a class="button" data-toggle="popover" data-placement="bottom" data-content="Enter / Exit Full Screen" onclick='buttonClick("#fullScreen", "icon-full-screen icon-exit-full-screen");Toolbar.toggleFullScreen();'>
|
||||||
|
<i id="fullScreen" class="icon-full-screen"></i>
|
||||||
|
</a>
|
||||||
|
<span id="sipCallButton">
|
||||||
|
<div class="header_button_separator"></div>
|
||||||
|
<a class="button" data-toggle="popover" data-placement="bottom" data-content="Call SIP number" onclick='callSipButtonClicked();'>
|
||||||
|
<i class="icon-telephone"></i></a>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div id="subject"></div>
|
||||||
|
</div>
|
||||||
|
<div id="settings">
|
||||||
|
<h1>Connection Settings</h1>
|
||||||
|
<form id="loginInfo">
|
||||||
|
<label>JID: <input id="jid" type="text" name="jid" placeholder="me@example.com"/></label>
|
||||||
|
<label>Password: <input id="password" type="password" name="password" placeholder="secret"/></label>
|
||||||
|
<label>BOSH URL: <input id="boshURL" type="text" name="boshURL" placeholder="/http-bind"/></label>
|
||||||
|
<input id="connect" type="submit" value="Connect" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div id="reloadPresentation"><a onclick='Prezi.reloadPresentation();'><i title="Reload Prezi" class="fa fa-repeat fa-lg"></i></a></div>
|
||||||
|
<div id="videospace" onmousemove="Toolbar.showToolbar();">
|
||||||
|
<div id="largeVideoContainer" class="videocontainer">
|
||||||
|
<div id="presentation"></div>
|
||||||
|
<div id="etherpad"></div>
|
||||||
|
<a href="http://jitsi.org" target="_new"><div class="watermark" id="leftwatermark"></div></a>
|
||||||
|
<!-- a href="http://jitsi.org" target="_new"><div class="watermark" id="rightwatermark"></div></a -->
|
||||||
|
<video id="largeVideo" autoplay oncontextmenu="return false;"></video>
|
||||||
|
</div>
|
||||||
|
<div id="remoteVideos">
|
||||||
|
<span id="localVideoContainer" class="videocontainer">
|
||||||
|
<span id="localNick" class="nick"></span>
|
||||||
|
<span id="localVideoWrapper">
|
||||||
|
<!--<video id="localVideo" autoplay oncontextmenu="return false;" muted></video> - is now per stream generated -->
|
||||||
|
</span>
|
||||||
|
<audio id="localAudio" autoplay oncontextmenu="return false;" muted></audio>
|
||||||
|
<span class="focusindicator"></span>
|
||||||
|
</span>
|
||||||
|
<audio id="userJoined" src="sounds/joined.wav" preload="auto"></audio>
|
||||||
|
<audio id="userLeft" src="sounds/left.wav" preload="auto"></audio>
|
||||||
|
</div>
|
||||||
|
<span id="bottomToolbar">
|
||||||
|
<span class="bottomToolbar_span">
|
||||||
|
<a class="bottomToolbarButton" data-toggle="popover" data-placement="top" data-content="Open / close chat" onclick='BottomToolbar.toggleChat();'>
|
||||||
|
<i id="chatBottomButton" class="icon-chat-simple"></i>
|
||||||
|
</a>
|
||||||
<span id="unreadMessages"></span>
|
<span id="unreadMessages"></span>
|
||||||
</span>
|
</span>
|
||||||
<div class="header_button_separator"></div>
|
<span class="bottomToolbar_span">
|
||||||
<a class="button" data-toggle="popover" data-placement="bottom" data-content="Share Prezi" onclick='Prezi.openPreziDialog();'><i class="icon-prezi"></i></a>
|
<a class="bottomToolbarButton" data-toggle="popover" data-placement="top" data-content="Open / close contact list" onclick='BottomToolbar.toggleContactList();'>
|
||||||
<span id="etherpadButton">
|
<i id="contactListButton" class="icon-contactList"></i>
|
||||||
<div class="header_button_separator"></div>
|
</a>
|
||||||
<a class="button" data-toggle="popover" data-placement="bottom" data-content="Shared document" onclick='Etherpad.toggleEtherpad(0);'><i class="icon-share-doc"></i></a>
|
<span id="unreadMessages"></span>
|
||||||
</span>
|
</span>
|
||||||
<div class="header_button_separator"></div>
|
|
||||||
<span id="desktopsharing" style="display: none">
|
|
||||||
<a class="button" data-toggle="popover" data-placement="bottom" data-content="Share screen" onclick="toggleScreenSharing();"><i class="icon-share-desktop"></i></a>
|
|
||||||
<div class="header_button_separator"></div>
|
|
||||||
</span>
|
|
||||||
<a class="button" data-toggle="popover" data-placement="bottom" data-content="Enter / Exit Full Screen" onclick='buttonClick("#fullScreen", "icon-full-screen icon-exit-full-screen");Toolbar.toggleFullScreen();'>
|
|
||||||
<i id="fullScreen" class="icon-full-screen"></i></a>
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div id="subject"></div>
|
<div id="chatspace">
|
||||||
</div>
|
<div id="nickname">
|
||||||
<div id="settings">
|
Enter a nickname in the box below
|
||||||
<h1>Connection Settings</h1>
|
<form>
|
||||||
<form id="loginInfo">
|
<input type='text' id="nickinput" placeholder='Choose a nickname' autofocus>
|
||||||
<label>JID: <input id="jid" type="text" name="jid" placeholder="me@example.com"/></label>
|
</form>
|
||||||
<label>Password: <input id="password" type="password" name="password" placeholder="secret"/></label>
|
</div>
|
||||||
<label>BOSH URL: <input id="boshURL" type="text" name="boshURL" placeholder="/http-bind"/></label>
|
|
||||||
<input id="connect" type="submit" value="Connect" />
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div id="reloadPresentation"><a onclick='Prezi.reloadPresentation();'><i title="Reload Prezi" class="fa fa-repeat fa-lg"></i></a></div>
|
|
||||||
<div id="videospace" onmousemove="Toolbar.showToolbar();">
|
|
||||||
<div id="largeVideoContainer" class="videocontainer">
|
|
||||||
<div id="presentation"></div>
|
|
||||||
<div id="etherpad"></div>
|
|
||||||
<a href="http://jitsi.org" target="_new"><div class="watermark" id="leftwatermark"></div></a>
|
|
||||||
<!-- a href="http://jitsi.org" target="_new"><div class="watermark" id="rightwatermark"></div></a -->
|
|
||||||
<video id="largeVideo" autoplay oncontextmenu="return false;"></video>
|
|
||||||
</div>
|
|
||||||
<div id="remoteVideos">
|
|
||||||
<span id="localVideoContainer" class="videocontainer">
|
|
||||||
<span id="localNick" class="nick"></span>
|
|
||||||
<span id="localVideoWrapper">
|
|
||||||
<!--<video id="localVideo" autoplay oncontextmenu="return false;" muted></video> - is now per stream generated -->
|
|
||||||
</span>
|
|
||||||
<audio id="localAudio" autoplay oncontextmenu="return false;" muted></audio>
|
|
||||||
<span class="focusindicator" data-content="The owner of this conference" data-toggle="popover" data-placement="top"></span>
|
|
||||||
</span>
|
|
||||||
<audio id="userJoined" src="sounds/joined.wav" preload="auto"></audio>
|
|
||||||
<audio id="userLeft" src="sounds/left.wav" preload="auto"></audio>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="chatspace">
|
|
||||||
<div id="nickname">
|
|
||||||
Enter a nickname in the box below
|
|
||||||
<form>
|
|
||||||
<input type='text' id="nickinput" placeholder='Choose a nickname' autofocus>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!--div><i class="fa fa-comments"> </i><span class='nick'></span>: <span class='chattext'></span></div-->
|
<!--div><i class="fa fa-comments"> </i><span class='nick'></span>: <span class='chattext'></span></div-->
|
||||||
<div id="chatconversation"></div>
|
<div id="chatconversation"></div>
|
||||||
<audio id="chatNotification" src="sounds/incomingMessage.wav" preload="auto"></audio>
|
<audio id="chatNotification" src="sounds/incomingMessage.wav" preload="auto"></audio>
|
||||||
<textarea id="usermsg" placeholder='Enter text...' autofocus></textarea>
|
<textarea id="usermsg" placeholder='Enter text...' autofocus></textarea>
|
||||||
|
</div>
|
||||||
|
<a id="downloadlog" onclick='dump(event.target);' data-toggle="popover" data-placement="right" data-content="Download logs" ><i class="fa fa-cloud-download"></i></a>
|
||||||
|
</div>
|
||||||
|
<div id="contactlist">
|
||||||
|
<ul>
|
||||||
|
<li class="title"><i class="icon-contact-list"></i> CONTACT LIST</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<a id="downloadlog" onclick='dump(event.target);' data-toggle="popover" data-placement="right" data-content="Download logs" ><i class="fa fa-cloud-download"></i></a>
|
<a id="downloadlog" onclick='dump(event.target);' data-toggle="popover" data-placement="right" data-content="Download logs" ><i class="fa fa-cloud-download"></i></a>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -54,19 +54,25 @@ function ColibriFocus(connection, bridgejid) {
|
||||||
* Default channel expire value in seconds.
|
* Default channel expire value in seconds.
|
||||||
* @type {number}
|
* @type {number}
|
||||||
*/
|
*/
|
||||||
this.channelExpire = 60;
|
this.channelExpire
|
||||||
|
= ('number' === typeof(config.channelExpire))
|
||||||
|
? config.channelExpire
|
||||||
|
: 15;
|
||||||
|
/**
|
||||||
|
* Default channel last-n value.
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
this.channelLastN
|
||||||
|
= ('number' === typeof(config.channelLastN)) ? config.channelLastN : -1;
|
||||||
|
|
||||||
// media types of the conference
|
// media types of the conference
|
||||||
if (config.openSctp)
|
if (config.openSctp)
|
||||||
{
|
|
||||||
this.media = ['audio', 'video', 'data'];
|
this.media = ['audio', 'video', 'data'];
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
this.media = ['audio', 'video'];
|
this.media = ['audio', 'video'];
|
||||||
}
|
|
||||||
|
|
||||||
this.connection.jingle.sessions[this.sid] = this;
|
this.connection.jingle.sessions[this.sid] = this;
|
||||||
|
this.bundledTransports = {};
|
||||||
this.mychannel = [];
|
this.mychannel = [];
|
||||||
this.channels = [];
|
this.channels = [];
|
||||||
this.remotessrc = {};
|
this.remotessrc = {};
|
||||||
|
@ -77,6 +83,12 @@ function ColibriFocus(connection, bridgejid) {
|
||||||
|
|
||||||
// silly wait flag
|
// silly wait flag
|
||||||
this.wait = true;
|
this.wait = true;
|
||||||
|
|
||||||
|
this.recordingEnabled = false;
|
||||||
|
|
||||||
|
// stores information about the endpoints (i.e. display names) to
|
||||||
|
// be sent to the videobridge.
|
||||||
|
this.endpointsInfo = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// creates a conferences with an initial set of peers
|
// creates a conferences with an initial set of peers
|
||||||
|
@ -164,44 +176,162 @@ ColibriFocus.prototype.makeConference = function (peers) {
|
||||||
*/
|
*/
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Sends a COLIBRI message which enables or disables (according to 'state') the
|
||||||
|
// recording on the bridge. Waits for the result IQ and calls 'callback' with
|
||||||
|
// the new recording state, according to the IQ.
|
||||||
|
ColibriFocus.prototype.setRecording = function(state, token, callback) {
|
||||||
|
var self = this;
|
||||||
|
var elem = $iq({to: this.bridgejid, type: 'set'});
|
||||||
|
elem.c('conference', {
|
||||||
|
xmlns: 'http://jitsi.org/protocol/colibri',
|
||||||
|
id: this.confid
|
||||||
|
});
|
||||||
|
elem.c('recording', {state: state, token: token});
|
||||||
|
elem.up();
|
||||||
|
|
||||||
|
this.connection.sendIQ(elem,
|
||||||
|
function (result) {
|
||||||
|
console.log('Set recording "', state, '". Result:', result);
|
||||||
|
var recordingElem = $(result).find('>conference>recording');
|
||||||
|
var newState = ('true' === recordingElem.attr('state'));
|
||||||
|
|
||||||
|
self.recordingEnabled = newState;
|
||||||
|
callback(newState);
|
||||||
|
},
|
||||||
|
function (error) {
|
||||||
|
console.warn(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Updates the display name for an endpoint with a specific jid.
|
||||||
|
* jid: the jid associated with the endpoint.
|
||||||
|
* displayName: the new display name for the endpoint.
|
||||||
|
*/
|
||||||
|
ColibriFocus.prototype.setEndpointDisplayName = function(jid, displayName) {
|
||||||
|
var endpointId = jid.substr(1 + jid.lastIndexOf('/'));
|
||||||
|
var update = false;
|
||||||
|
|
||||||
|
if (this.endpointsInfo === null) {
|
||||||
|
this.endpointsInfo = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
var endpointInfo = this.endpointsInfo[endpointId];
|
||||||
|
if ('undefined' === typeof endpointInfo) {
|
||||||
|
endpointInfo = this.endpointsInfo[endpointId] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endpointInfo['displayname'] !== displayName) {
|
||||||
|
endpointInfo['displayname'] = displayName;
|
||||||
|
update = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (update) {
|
||||||
|
this.updateEndpoints();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Sends a colibri message to the bridge that contains the
|
||||||
|
* current endpoints and their display names.
|
||||||
|
*/
|
||||||
|
ColibriFocus.prototype.updateEndpoints = function() {
|
||||||
|
if (this.confid === null
|
||||||
|
|| this.endpointsInfo === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.confid === 0) {
|
||||||
|
// the colibri conference is currently initiating
|
||||||
|
var self = this;
|
||||||
|
window.setTimeout(function() { self.updateEndpoints()}, 1000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var elem = $iq({to: this.bridgejid, type: 'set'});
|
||||||
|
elem.c('conference', {
|
||||||
|
xmlns: 'http://jitsi.org/protocol/colibri',
|
||||||
|
id: this.confid
|
||||||
|
});
|
||||||
|
|
||||||
|
for (var id in this.endpointsInfo) {
|
||||||
|
elem.c('endpoint');
|
||||||
|
elem.attrs({ id: id,
|
||||||
|
displayname: this.endpointsInfo[id]['displayname']
|
||||||
|
});
|
||||||
|
elem.up();
|
||||||
|
}
|
||||||
|
|
||||||
|
//elem.up(); //conference
|
||||||
|
|
||||||
|
this.connection.sendIQ(
|
||||||
|
elem,
|
||||||
|
function (result) {},
|
||||||
|
function (error) { console.warn(error); }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
ColibriFocus.prototype._makeConference = function () {
|
ColibriFocus.prototype._makeConference = function () {
|
||||||
var self = this;
|
var self = this;
|
||||||
var elem = $iq({to: this.bridgejid, type: 'get'});
|
var elem = $iq({ to: this.bridgejid, type: 'get' });
|
||||||
elem.c('conference', {xmlns: 'http://jitsi.org/protocol/colibri'});
|
elem.c('conference', { xmlns: 'http://jitsi.org/protocol/colibri' });
|
||||||
|
|
||||||
this.media.forEach(function (name) {
|
this.media.forEach(function (name) {
|
||||||
var isData = name === 'data';
|
var elemName;
|
||||||
var channel = isData ? 'sctpconnection' : 'channel';
|
var elemAttrs = { initiator: 'true', expire: self.channelExpire };
|
||||||
|
|
||||||
elem.c('content', {name: name});
|
if ('data' === name)
|
||||||
|
{
|
||||||
|
elemName = 'sctpconnection';
|
||||||
|
elemAttrs['port'] = 5000;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
elemName = 'channel';
|
||||||
|
if (('video' === name) && (self.channelLastN >= 0))
|
||||||
|
elemAttrs['last-n'] = self.channelLastN;
|
||||||
|
}
|
||||||
|
|
||||||
elem.c(channel, {
|
elem.c('content', { name: name });
|
||||||
initiator: 'true',
|
|
||||||
expire: '15',
|
elem.c(elemName, elemAttrs);
|
||||||
endpoint: self.myMucResource
|
elem.attrs({ endpoint: self.myMucResource });
|
||||||
});
|
if (config.useBundle) {
|
||||||
if (isData)
|
elem.attrs({ 'channel-bundle-id': self.myMucResource });
|
||||||
elem.attrs({port: 5000});
|
}
|
||||||
elem.up();// end of channel
|
elem.up();// end of channel/sctpconnection
|
||||||
|
|
||||||
for (var j = 0; j < self.peers.length; j++) {
|
for (var j = 0; j < self.peers.length; j++) {
|
||||||
elem.c(channel, {
|
var peer = self.peers[j];
|
||||||
initiator: 'true',
|
var peerEndpoint = peer.substr(1 + peer.lastIndexOf('/'));
|
||||||
expire: '15',
|
|
||||||
endpoint: self.peers[j].substr(1 + self.peers[j].lastIndexOf('/'))
|
elem.c(elemName, elemAttrs);
|
||||||
});
|
elem.attrs({ endpoint: peerEndpoint });
|
||||||
if (isData)
|
if (config.useBundle) {
|
||||||
elem.attrs({port: 5000});
|
elem.attrs({ 'channel-bundle-id': peerEndpoint });
|
||||||
elem.up(); // end of channel
|
}
|
||||||
|
elem.up(); // end of channel/sctpconnection
|
||||||
}
|
}
|
||||||
elem.up(); // end of content
|
elem.up(); // end of content
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (this.endpointsInfo !== null) {
|
||||||
|
for (var id in this.endpointsInfo) {
|
||||||
|
elem.c('endpoint');
|
||||||
|
elem.attrs({ id: id,
|
||||||
|
displayname: this.endpointsInfo[id]['displayname']
|
||||||
|
});
|
||||||
|
elem.up();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
var localSDP = new SDP(this.peerconnection.localDescription.sdp);
|
var localSDP = new SDP(this.peerconnection.localDescription.sdp);
|
||||||
localSDP.media.forEach(function (media, channel) {
|
localSDP.media.forEach(function (media, channel) {
|
||||||
var name = SDPUtil.parse_mline(media.split('\r\n')[0]).media;
|
var name = SDPUtil.parse_mline(media.split('\r\n')[0]).media;
|
||||||
elem.c('content', {name: name});
|
elem.c('content', {name: name});
|
||||||
elem.c('channel', {initiator: 'false', expire: '15'});
|
elem.c('channel', {initiator: 'false', expire: self.channelExpire});
|
||||||
|
|
||||||
// FIXME: should reuse code from .toJingle
|
// FIXME: should reuse code from .toJingle
|
||||||
var mline = SDPUtil.parse_mline(media.split('\r\n')[0]);
|
var mline = SDPUtil.parse_mline(media.split('\r\n')[0]);
|
||||||
|
@ -215,7 +345,7 @@ ColibriFocus.prototype._makeConference = function () {
|
||||||
|
|
||||||
elem.up(); // end of channel
|
elem.up(); // end of channel
|
||||||
for (j = 0; j < self.peers.length; j++) {
|
for (j = 0; j < self.peers.length; j++) {
|
||||||
elem.c('channel', {initiator: 'true', expire:'15' }).up();
|
elem.c('channel', {initiator: 'true', expire: self.channelExpire }).up();
|
||||||
}
|
}
|
||||||
elem.up(); // end of content
|
elem.up(); // end of content
|
||||||
});
|
});
|
||||||
|
@ -231,7 +361,7 @@ ColibriFocus.prototype._makeConference = function () {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// callback when a conference was created
|
// callback when a colibri conference was created
|
||||||
ColibriFocus.prototype.createdConference = function (result) {
|
ColibriFocus.prototype.createdConference = function (result) {
|
||||||
console.log('created a conference on the bridge');
|
console.log('created a conference on the bridge');
|
||||||
var self = this;
|
var self = this;
|
||||||
|
@ -257,6 +387,14 @@ ColibriFocus.prototype.createdConference = function (result) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// save the 'transport' elements from 'channel-bundle'-s
|
||||||
|
var channelBundles = $(result).find('>conference>channel-bundle');
|
||||||
|
for (var i = 0; i < channelBundles.length; i++)
|
||||||
|
{
|
||||||
|
var endpointId = $(channelBundles[i]).attr('id');
|
||||||
|
this.bundledTransports[endpointId] = $(channelBundles[i]).find('>transport[xmlns="urn:xmpp:jingle:transports:ice-udp:1"]');
|
||||||
|
}
|
||||||
|
|
||||||
console.log('remote channels', this.channels);
|
console.log('remote channels', this.channels);
|
||||||
|
|
||||||
// Notify that the focus has created the conference on the bridge
|
// Notify that the focus has created the conference on the bridge
|
||||||
|
@ -268,6 +406,11 @@ ColibriFocus.prototype.createdConference = function (result) {
|
||||||
's=-\r\n' +
|
's=-\r\n' +
|
||||||
't=0 0\r\n' +
|
't=0 0\r\n' +
|
||||||
/* Audio */
|
/* Audio */
|
||||||
|
(config.useBundle
|
||||||
|
? ('a=group:BUNDLE audio video' +
|
||||||
|
(config.openSctp ? ' data' : '') +
|
||||||
|
'\r\n')
|
||||||
|
: '') +
|
||||||
'm=audio 1 RTP/SAVPF 111 103 104 0 8 106 105 13 126\r\n' +
|
'm=audio 1 RTP/SAVPF 111 103 104 0 8 106 105 13 126\r\n' +
|
||||||
'c=IN IP4 0.0.0.0\r\n' +
|
'c=IN IP4 0.0.0.0\r\n' +
|
||||||
'a=rtcp:1 IN IP4 0.0.0.0\r\n' +
|
'a=rtcp:1 IN IP4 0.0.0.0\r\n' +
|
||||||
|
@ -285,6 +428,7 @@ ColibriFocus.prototype.createdConference = function (result) {
|
||||||
'a=rtpmap:13 CN/8000\r\n' +
|
'a=rtpmap:13 CN/8000\r\n' +
|
||||||
'a=rtpmap:126 telephone-event/8000\r\n' +
|
'a=rtpmap:126 telephone-event/8000\r\n' +
|
||||||
'a=maxptime:60\r\n' +
|
'a=maxptime:60\r\n' +
|
||||||
|
(config.useRtcpMux ? 'a=rtcp-mux\r\n' : '') +
|
||||||
/* Video */
|
/* Video */
|
||||||
'm=video 1 RTP/SAVPF 100 116 117\r\n' +
|
'm=video 1 RTP/SAVPF 100 116 117\r\n' +
|
||||||
'c=IN IP4 0.0.0.0\r\n' +
|
'c=IN IP4 0.0.0.0\r\n' +
|
||||||
|
@ -299,6 +443,7 @@ ColibriFocus.prototype.createdConference = function (result) {
|
||||||
'a=rtcp-fb:100 goog-remb\r\n' +
|
'a=rtcp-fb:100 goog-remb\r\n' +
|
||||||
'a=rtpmap:116 red/90000\r\n' +
|
'a=rtpmap:116 red/90000\r\n' +
|
||||||
'a=rtpmap:117 ulpfec/90000\r\n' +
|
'a=rtpmap:117 ulpfec/90000\r\n' +
|
||||||
|
(config.useRtcpMux ? 'a=rtcp-mux\r\n' : '') +
|
||||||
/* Data SCTP */
|
/* Data SCTP */
|
||||||
(config.openSctp ?
|
(config.openSctp ?
|
||||||
'm=application 1 DTLS/SCTP 5000\r\n' +
|
'm=application 1 DTLS/SCTP 5000\r\n' +
|
||||||
|
@ -347,26 +492,25 @@ ColibriFocus.prototype.createdConference = function (result) {
|
||||||
tmp = $(this.mychannel[channel]).find('>source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]');
|
tmp = $(this.mychannel[channel]).find('>source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]');
|
||||||
// FIXME: check rtp-level-relay-type
|
// FIXME: check rtp-level-relay-type
|
||||||
|
|
||||||
var isData = bridgeSDP.media[channel].indexOf('application') !== -1;
|
var name = bridgeSDP.media[channel].split(" ")[0].substr(2); // 'm=audio ...'
|
||||||
if (!isData && tmp.length)
|
if (name === 'audio' || name === 'video') {
|
||||||
{
|
|
||||||
bridgeSDP.media[channel] += 'a=ssrc:' + tmp.attr('ssrc') + ' ' + 'cname:mixed' + '\r\n';
|
|
||||||
bridgeSDP.media[channel] += 'a=ssrc:' + tmp.attr('ssrc') + ' ' + 'label:mixedlabela0' + '\r\n';
|
|
||||||
bridgeSDP.media[channel] += 'a=ssrc:' + tmp.attr('ssrc') + ' ' + 'msid:mixedmslabel mixedlabela0' + '\r\n';
|
|
||||||
bridgeSDP.media[channel] += 'a=ssrc:' + tmp.attr('ssrc') + ' ' + 'mslabel:mixedmslabel' + '\r\n';
|
|
||||||
}
|
|
||||||
else if (!isData)
|
|
||||||
{
|
|
||||||
// make chrome happy... '3735928559' == 0xDEADBEEF
|
// make chrome happy... '3735928559' == 0xDEADBEEF
|
||||||
// FIXME: this currently appears as two streams, should be one
|
var ssrc = tmp.length ? tmp.attr('ssrc') : '3735928559';
|
||||||
bridgeSDP.media[channel] += 'a=ssrc:' + '3735928559' + ' ' + 'cname:mixed' + '\r\n';
|
|
||||||
bridgeSDP.media[channel] += 'a=ssrc:' + '3735928559' + ' ' + 'label:mixedlabelv0' + '\r\n';
|
bridgeSDP.media[channel] += 'a=ssrc:' + ssrc + ' cname:mixed\r\n';
|
||||||
bridgeSDP.media[channel] += 'a=ssrc:' + '3735928559' + ' ' + 'msid:mixedmslabel mixedlabelv0' + '\r\n';
|
bridgeSDP.media[channel] += 'a=ssrc:' + ssrc + ' label:mixedlabel' + name + '0\r\n';
|
||||||
bridgeSDP.media[channel] += 'a=ssrc:' + '3735928559' + ' ' + 'mslabel:mixedmslabel' + '\r\n';
|
bridgeSDP.media[channel] += 'a=ssrc:' + ssrc + ' msid:mixedmslabel mixedlabel' + name + '0\r\n';
|
||||||
|
bridgeSDP.media[channel] += 'a=ssrc:' + ssrc + ' mslabel:mixedmslabel\r\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: should take code from .fromJingle
|
// FIXME: should take code from .fromJingle
|
||||||
tmp = $(this.mychannel[channel]).find('>transport[xmlns="urn:xmpp:jingle:transports:ice-udp:1"]');
|
var channelBundleId = $(this.mychannel[channel]).attr('channel-bundle-id');
|
||||||
|
if (typeof channelBundleId != 'undefined') {
|
||||||
|
tmp = this.bundledTransports[channelBundleId];
|
||||||
|
} else {
|
||||||
|
tmp = $(this.mychannel[channel]).find('>transport[xmlns="urn:xmpp:jingle:transports:ice-udp:1"]');
|
||||||
|
}
|
||||||
|
|
||||||
if (tmp.length) {
|
if (tmp.length) {
|
||||||
bridgeSDP.media[channel] += 'a=ice-ufrag:' + tmp.attr('ufrag') + '\r\n';
|
bridgeSDP.media[channel] += 'a=ice-ufrag:' + tmp.attr('ufrag') + '\r\n';
|
||||||
bridgeSDP.media[channel] += 'a=ice-pwd:' + tmp.attr('pwd') + '\r\n';
|
bridgeSDP.media[channel] += 'a=ice-pwd:' + tmp.attr('pwd') + '\r\n';
|
||||||
|
@ -390,7 +534,7 @@ ColibriFocus.prototype.createdConference = function (result) {
|
||||||
function (answer) {
|
function (answer) {
|
||||||
self.peerconnection.setLocalDescription(answer,
|
self.peerconnection.setLocalDescription(answer,
|
||||||
function () {
|
function () {
|
||||||
console.log('setLocalDescription succeded.');
|
console.log('setLocalDescription succeeded.');
|
||||||
// make sure our presence is updated
|
// make sure our presence is updated
|
||||||
$(document).trigger('setLocalDescription.jingle', [self.sid]);
|
$(document).trigger('setLocalDescription.jingle', [self.sid]);
|
||||||
var elem = $iq({to: self.bridgejid, type: 'get'});
|
var elem = $iq({to: self.bridgejid, type: 'get'});
|
||||||
|
@ -428,6 +572,7 @@ ColibriFocus.prototype.createdConference = function (result) {
|
||||||
{
|
{
|
||||||
initiator: 'true',
|
initiator: 'true',
|
||||||
expire: self.channelExpire,
|
expire: self.channelExpire,
|
||||||
|
id: self.mychannel[channel].attr('id'),
|
||||||
endpoint: self.myMucResource,
|
endpoint: self.myMucResource,
|
||||||
port: sctpPort
|
port: sctpPort
|
||||||
}
|
}
|
||||||
|
@ -446,7 +591,7 @@ ColibriFocus.prototype.createdConference = function (result) {
|
||||||
},
|
},
|
||||||
function (error) {
|
function (error) {
|
||||||
console.error(
|
console.error(
|
||||||
"ERROR setLocalDescription succeded",
|
"ERROR sending colibri message",
|
||||||
error, elem);
|
error, elem);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -492,10 +637,14 @@ ColibriFocus.prototype.initiate = function (peer, isInitiator) {
|
||||||
var localSDP = new SDP(this.peerconnection.localDescription.sdp);
|
var localSDP = new SDP(this.peerconnection.localDescription.sdp);
|
||||||
// throw away stuff we don't want
|
// throw away stuff we don't want
|
||||||
// not needed with static offer
|
// not needed with static offer
|
||||||
sdp.removeSessionLines('a=group:');
|
if (!config.useBundle) {
|
||||||
|
sdp.removeSessionLines('a=group:');
|
||||||
|
}
|
||||||
sdp.removeSessionLines('a=msid-semantic:'); // FIXME: not mapped over jingle anyway...
|
sdp.removeSessionLines('a=msid-semantic:'); // FIXME: not mapped over jingle anyway...
|
||||||
for (var i = 0; i < sdp.media.length; i++) {
|
for (var i = 0; i < sdp.media.length; i++) {
|
||||||
sdp.removeMediaLines(i, 'a=rtcp-mux');
|
if (!config.useRtcpMux){
|
||||||
|
sdp.removeMediaLines(i, 'a=rtcp-mux');
|
||||||
|
}
|
||||||
sdp.removeMediaLines(i, 'a=ssrc:');
|
sdp.removeMediaLines(i, 'a=ssrc:');
|
||||||
sdp.removeMediaLines(i, 'a=crypto:');
|
sdp.removeMediaLines(i, 'a=crypto:');
|
||||||
sdp.removeMediaLines(i, 'a=candidate:');
|
sdp.removeMediaLines(i, 'a=candidate:');
|
||||||
|
@ -508,7 +657,8 @@ ColibriFocus.prototype.initiate = function (peer, isInitiator) {
|
||||||
if (1) { //i > 0) { // not for audio FIXME: does not work as intended
|
if (1) { //i > 0) { // not for audio FIXME: does not work as intended
|
||||||
// re-add all remote a=ssrcs
|
// re-add all remote a=ssrcs
|
||||||
for (var jid in this.remotessrc) {
|
for (var jid in this.remotessrc) {
|
||||||
if (jid == peer) continue;
|
if (jid == peer || !this.remotessrc[jid][i])
|
||||||
|
continue;
|
||||||
sdp.media[i] += this.remotessrc[jid][i];
|
sdp.media[i] += this.remotessrc[jid][i];
|
||||||
}
|
}
|
||||||
// and local a=ssrc lines
|
// and local a=ssrc lines
|
||||||
|
@ -527,23 +677,26 @@ ColibriFocus.prototype.initiate = function (peer, isInitiator) {
|
||||||
console.log('channel id', chan.attr('id'));
|
console.log('channel id', chan.attr('id'));
|
||||||
|
|
||||||
tmp = chan.find('>source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]');
|
tmp = chan.find('>source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]');
|
||||||
if (tmp.length) {
|
|
||||||
sdp.media[j] += 'a=ssrc:' + tmp.attr('ssrc') + ' ' + 'cname:mixed' + '\r\n';
|
var name = sdp.media[j].split(" ")[0].substr(2); // 'm=audio ...'
|
||||||
sdp.media[j] += 'a=ssrc:' + tmp.attr('ssrc') + ' ' + 'label:mixedlabela0' + '\r\n';
|
if (name === 'audio' || name === 'video') {
|
||||||
sdp.media[j] += 'a=ssrc:' + tmp.attr('ssrc') + ' ' + 'msid:mixedmslabel mixedlabela0' + '\r\n';
|
|
||||||
sdp.media[j] += 'a=ssrc:' + tmp.attr('ssrc') + ' ' + 'mslabel:mixedmslabel' + '\r\n';
|
|
||||||
}
|
|
||||||
// No SSRCs for 'data', comes when j == 2
|
|
||||||
else if (j < 2)
|
|
||||||
{
|
|
||||||
// make chrome happy... '3735928559' == 0xDEADBEEF
|
// make chrome happy... '3735928559' == 0xDEADBEEF
|
||||||
sdp.media[j] += 'a=ssrc:' + '3735928559' + ' ' + 'cname:mixed' + '\r\n';
|
var ssrc = tmp.length ? tmp.attr('ssrc') : '3735928559';
|
||||||
sdp.media[j] += 'a=ssrc:' + '3735928559' + ' ' + 'label:mixedlabelv0' + '\r\n';
|
|
||||||
sdp.media[j] += 'a=ssrc:' + '3735928559' + ' ' + 'msid:mixedmslabel mixedlabelv0' + '\r\n';
|
sdp.media[j] += 'a=ssrc:' + ssrc + ' cname:mixed\r\n';
|
||||||
sdp.media[j] += 'a=ssrc:' + '3735928559' + ' ' + 'mslabel:mixedmslabel' + '\r\n';
|
sdp.media[j] += 'a=ssrc:' + ssrc + ' label:mixedlabel' + name + '0\r\n';
|
||||||
|
sdp.media[j] += 'a=ssrc:' + ssrc + ' msid:mixedmslabel mixedlabel' + name + '0\r\n';
|
||||||
|
sdp.media[j] += 'a=ssrc:' + ssrc + ' mslabel:mixedmslabel\r\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
// In the case of bundle, we add each candidate to all m= lines/jingle contents,
|
||||||
|
// just as chrome does
|
||||||
|
if (config.useBundle){
|
||||||
|
tmp = this.bundledTransports[chan.attr('channel-bundle-id')];
|
||||||
|
} else {
|
||||||
|
tmp = chan.find('>transport[xmlns="urn:xmpp:jingle:transports:ice-udp:1"]');
|
||||||
}
|
}
|
||||||
|
|
||||||
tmp = chan.find('>transport[xmlns="urn:xmpp:jingle:transports:ice-udp:1"]');
|
|
||||||
if (tmp.length) {
|
if (tmp.length) {
|
||||||
if (tmp.attr('ufrag'))
|
if (tmp.attr('ufrag'))
|
||||||
sdp.media[j] += 'a=ice-ufrag:' + tmp.attr('ufrag') + '\r\n';
|
sdp.media[j] += 'a=ice-ufrag:' + tmp.attr('ufrag') + '\r\n';
|
||||||
|
@ -615,9 +768,7 @@ ColibriFocus.prototype.addNewParticipant = function (peer) {
|
||||||
{
|
{
|
||||||
console.error('local description not ready yet, postponing', peer);
|
console.error('local description not ready yet, postponing', peer);
|
||||||
}
|
}
|
||||||
window.setTimeout(function () {
|
window.setTimeout(function () { self.addNewParticipant(peer); }, 250);
|
||||||
self.addNewParticipant(peer);
|
|
||||||
}, 250);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var index = this.channels.length;
|
var index = this.channels.length;
|
||||||
|
@ -625,28 +776,39 @@ ColibriFocus.prototype.addNewParticipant = function (peer) {
|
||||||
this.peers.push(peer);
|
this.peers.push(peer);
|
||||||
|
|
||||||
var elem = $iq({to: this.bridgejid, type: 'get'});
|
var elem = $iq({to: this.bridgejid, type: 'get'});
|
||||||
elem.c('conference', {xmlns: 'http://jitsi.org/protocol/colibri', id: this.confid});
|
elem.c(
|
||||||
|
'conference',
|
||||||
|
{ xmlns: 'http://jitsi.org/protocol/colibri', id: this.confid });
|
||||||
var localSDP = new SDP(this.peerconnection.localDescription.sdp);
|
var localSDP = new SDP(this.peerconnection.localDescription.sdp);
|
||||||
localSDP.media.forEach(function (media, channel) {
|
localSDP.media.forEach(function (media, channel) {
|
||||||
var name = SDPUtil.parse_mid(SDPUtil.find_line(media, 'a=mid:'));
|
var name = SDPUtil.parse_mid(SDPUtil.find_line(media, 'a=mid:'));
|
||||||
elem.c('content', {name: name});
|
var elemName;
|
||||||
if (name !== 'data')
|
var endpointId = peer.substr(1 + peer.lastIndexOf('/'));
|
||||||
{
|
var elemAttrs
|
||||||
elem.c('channel', {
|
= {
|
||||||
initiator: 'true',
|
initiator: 'true',
|
||||||
expire: self.channelExpire,
|
expire: self.channelExpire,
|
||||||
endpoint: peer.substr(1 + peer.lastIndexOf('/'))
|
endpoint: endpointId
|
||||||
});
|
};
|
||||||
|
if (config.useBundle) {
|
||||||
|
elemAttrs['channel-bundle-id'] = endpointId;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if ('data' == name)
|
||||||
|
{
|
||||||
|
elemName = 'sctpconnection';
|
||||||
|
elemAttrs['port'] = 5000;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
elem.c('sctpconnection', {
|
elemName = 'channel';
|
||||||
endpoint: peer.substr(1 + peer.lastIndexOf('/')),
|
if (('video' === name) && (self.channelLastN >= 0))
|
||||||
initiator: 'true',
|
elemAttrs['last-n'] = self.channelLastN;
|
||||||
expire: self.channelExpire,
|
|
||||||
port: 5000
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
elem.c('content', { name: name });
|
||||||
|
elem.c(elemName, elemAttrs);
|
||||||
elem.up(); // end of channel/sctpconnection
|
elem.up(); // end of channel/sctpconnection
|
||||||
elem.up(); // end of content
|
elem.up(); // end of content
|
||||||
});
|
});
|
||||||
|
@ -654,7 +816,8 @@ ColibriFocus.prototype.addNewParticipant = function (peer) {
|
||||||
this.connection.sendIQ(elem,
|
this.connection.sendIQ(elem,
|
||||||
function (result) {
|
function (result) {
|
||||||
var contents = $(result).find('>conference>content').get();
|
var contents = $(result).find('>conference>content').get();
|
||||||
for (var i = 0; i < contents.length; i++) {
|
var i;
|
||||||
|
for (i = 0; i < contents.length; i++) {
|
||||||
var channelXml = $(contents[i]).find('>channel');
|
var channelXml = $(contents[i]).find('>channel');
|
||||||
if (channelXml.length)
|
if (channelXml.length)
|
||||||
{
|
{
|
||||||
|
@ -666,6 +829,12 @@ ColibriFocus.prototype.addNewParticipant = function (peer) {
|
||||||
}
|
}
|
||||||
self.channels[index][i] = tmp[0];
|
self.channels[index][i] = tmp[0];
|
||||||
}
|
}
|
||||||
|
var channelBundles = $(result).find('>conference>channel-bundle');
|
||||||
|
for (i = 0; i < channelBundles.length; i++)
|
||||||
|
{
|
||||||
|
var endpointId = $(channelBundles[i]).attr('id');
|
||||||
|
self.bundledTransports[endpointId] = $(channelBundles[i]).find('>transport[xmlns="urn:xmpp:jingle:transports:ice-udp:1"]');
|
||||||
|
}
|
||||||
self.initiate(peer, true);
|
self.initiate(peer, true);
|
||||||
},
|
},
|
||||||
function (error) {
|
function (error) {
|
||||||
|
@ -682,6 +851,9 @@ ColibriFocus.prototype.updateChannel = function (remoteSDP, participant) {
|
||||||
change.c('conference', {xmlns: 'http://jitsi.org/protocol/colibri', id: this.confid});
|
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:'));
|
var name = SDPUtil.parse_mid(SDPUtil.find_line(remoteSDP.media[channel], 'a=mid:'));
|
||||||
change.c('content', {name: name});
|
change.c('content', {name: name});
|
||||||
if (name !== 'data')
|
if (name !== 'data')
|
||||||
|
@ -714,6 +886,7 @@ ColibriFocus.prototype.updateChannel = function (remoteSDP, participant) {
|
||||||
{
|
{
|
||||||
var sctpmap = SDPUtil.find_line(remoteSDP.media[channel], 'a=sctpmap:');
|
var sctpmap = SDPUtil.find_line(remoteSDP.media[channel], 'a=sctpmap:');
|
||||||
change.c('sctpconnection', {
|
change.c('sctpconnection', {
|
||||||
|
id: $(this.channels[participant][channel]).attr('id'),
|
||||||
endpoint: $(this.channels[participant][channel]).attr('endpoint'),
|
endpoint: $(this.channels[participant][channel]).attr('endpoint'),
|
||||||
expire: self.channelExpire,
|
expire: self.channelExpire,
|
||||||
port: SDPUtil.parse_sctpmap(sctpmap)[0]
|
port: SDPUtil.parse_sctpmap(sctpmap)[0]
|
||||||
|
@ -770,12 +943,7 @@ ColibriFocus.prototype.addSource = function (elem, fromJid) {
|
||||||
if (!this.peerconnection.localDescription)
|
if (!this.peerconnection.localDescription)
|
||||||
{
|
{
|
||||||
console.warn("addSource - localDescription not ready yet")
|
console.warn("addSource - localDescription not ready yet")
|
||||||
setTimeout(function()
|
setTimeout(function() { self.addSource(elem, fromJid); }, 200);
|
||||||
{
|
|
||||||
self.addSource(elem, fromJid);
|
|
||||||
},
|
|
||||||
200
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -816,12 +984,7 @@ ColibriFocus.prototype.removeSource = function (elem, fromJid) {
|
||||||
if (!self.peerconnection.localDescription)
|
if (!self.peerconnection.localDescription)
|
||||||
{
|
{
|
||||||
console.warn("removeSource - localDescription not ready yet");
|
console.warn("removeSource - localDescription not ready yet");
|
||||||
setTimeout(function()
|
setTimeout(function() { self.removeSource(elem, fromJid); }, 200);
|
||||||
{
|
|
||||||
self.removeSource(elem, fromJid);
|
|
||||||
},
|
|
||||||
200
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -862,6 +1025,9 @@ ColibriFocus.prototype.setRemoteDescription = function (session, elem, desctype)
|
||||||
this.remotessrc[session.peerjid] = [];
|
this.remotessrc[session.peerjid] = [];
|
||||||
for (channel = 0; channel < this.channels[participant].length; channel++) {
|
for (channel = 0; channel < this.channels[participant].length; channel++) {
|
||||||
//if (channel == 0) continue; FIXME: does not work as intended
|
//if (channel == 0) continue; FIXME: does not work as intended
|
||||||
|
if (!remoteSDP.media[channel])
|
||||||
|
continue;
|
||||||
|
|
||||||
if (SDPUtil.find_lines(remoteSDP.media[channel], 'a=ssrc:').length)
|
if (SDPUtil.find_lines(remoteSDP.media[channel], 'a=ssrc:').length)
|
||||||
{
|
{
|
||||||
this.remotessrc[session.peerjid][channel] =
|
this.remotessrc[session.peerjid][channel] =
|
||||||
|
@ -873,6 +1039,9 @@ ColibriFocus.prototype.setRemoteDescription = function (session, elem, desctype)
|
||||||
// ACT 4: add new a=ssrc lines to local remotedescription
|
// ACT 4: add new a=ssrc lines to local remotedescription
|
||||||
for (channel = 0; channel < this.channels[participant].length; channel++) {
|
for (channel = 0; channel < this.channels[participant].length; channel++) {
|
||||||
//if (channel == 0) continue; FIXME: does not work as intended
|
//if (channel == 0) continue; FIXME: does not work as intended
|
||||||
|
if (!remoteSDP.media[channel])
|
||||||
|
continue;
|
||||||
|
|
||||||
if (SDPUtil.find_lines(remoteSDP.media[channel], 'a=ssrc:').length) {
|
if (SDPUtil.find_lines(remoteSDP.media[channel], 'a=ssrc:').length) {
|
||||||
this.peerconnection.enqueueAddSsrc(
|
this.peerconnection.enqueueAddSsrc(
|
||||||
channel,
|
channel,
|
||||||
|
@ -893,6 +1062,12 @@ ColibriFocus.prototype.addIceCandidate = function (session, elem) {
|
||||||
$(elem).each(function () {
|
$(elem).each(function () {
|
||||||
var name = $(this).attr('name');
|
var name = $(this).attr('name');
|
||||||
|
|
||||||
|
// If we are using bundle, audio/video/data channel will have the same candidates, so only send them for
|
||||||
|
// the audio channel.
|
||||||
|
if (config.useBundle && name !== 'audio') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var channel = name == 'audio' ? 0 : 1; // FIXME: search mlineindex in localdesc
|
var channel = name == 'audio' ? 0 : 1; // FIXME: search mlineindex in localdesc
|
||||||
if (name != 'audio' && name != 'video')
|
if (name != 'audio' && name != 'video')
|
||||||
channel = 2; // name == 'data'
|
channel = 2; // name == 'data'
|
||||||
|
@ -909,6 +1084,7 @@ ColibriFocus.prototype.addIceCandidate = function (session, elem) {
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
change.c('sctpconnection', {
|
change.c('sctpconnection', {
|
||||||
|
id: $(self.channels[participant][channel]).attr('id'),
|
||||||
endpoint: $(self.channels[participant][channel]).attr('endpoint'),
|
endpoint: $(self.channels[participant][channel]).attr('endpoint'),
|
||||||
expire: self.channelExpire
|
expire: self.channelExpire
|
||||||
});
|
});
|
||||||
|
@ -919,6 +1095,10 @@ ColibriFocus.prototype.addIceCandidate = function (session, elem) {
|
||||||
pwd: $(this).attr('pwd'),
|
pwd: $(this).attr('pwd'),
|
||||||
xmlns: $(this).attr('xmlns')
|
xmlns: $(this).attr('xmlns')
|
||||||
});
|
});
|
||||||
|
if (config.useRtcpMux
|
||||||
|
&& 'channel' === change.node.parentNode.nodeName) {
|
||||||
|
change.c('rtcp-mux').up();
|
||||||
|
}
|
||||||
|
|
||||||
$(this).find('>candidate').each(function () {
|
$(this).find('>candidate').each(function () {
|
||||||
/* not yet
|
/* not yet
|
||||||
|
@ -956,11 +1136,13 @@ ColibriFocus.prototype.sendIceCandidate = function (candidate) {
|
||||||
}
|
}
|
||||||
if (this.drip_container.length === 0) {
|
if (this.drip_container.length === 0) {
|
||||||
// start 20ms callout
|
// start 20ms callout
|
||||||
window.setTimeout(function () {
|
window.setTimeout(
|
||||||
if (self.drip_container.length === 0) return;
|
function () {
|
||||||
self.sendIceCandidates(self.drip_container);
|
if (self.drip_container.length === 0) return;
|
||||||
self.drip_container = [];
|
self.sendIceCandidates(self.drip_container);
|
||||||
}, 20);
|
self.drip_container = [];
|
||||||
|
},
|
||||||
|
20);
|
||||||
}
|
}
|
||||||
this.drip_container.push(candidate);
|
this.drip_container.push(candidate);
|
||||||
};
|
};
|
||||||
|
@ -990,12 +1172,16 @@ ColibriFocus.prototype.sendIceCandidates = function (candidates) {
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
mycands.c('sctpconnection', {
|
mycands.c('sctpconnection', {
|
||||||
|
id: $(this.mychannel[cands[0].sdpMLineIndex]).attr('id'),
|
||||||
endpoint: $(this.mychannel[cands[0].sdpMLineIndex]).attr('endpoint'),
|
endpoint: $(this.mychannel[cands[0].sdpMLineIndex]).attr('endpoint'),
|
||||||
port: $(this.mychannel[cands[0].sdpMLineIndex]).attr('port'),
|
port: $(this.mychannel[cands[0].sdpMLineIndex]).attr('port'),
|
||||||
expire: self.channelExpire
|
expire: self.channelExpire
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
mycands.c('transport', {xmlns: 'urn:xmpp:jingle:transports:ice-udp:1'});
|
mycands.c('transport', {xmlns: 'urn:xmpp:jingle:transports:ice-udp:1'});
|
||||||
|
if (config.useRtcpMux && name !== 'data') {
|
||||||
|
mycands.c('rtcp-mux').up();
|
||||||
|
}
|
||||||
for (var i = 0; i < cands.length; i++) {
|
for (var i = 0; i < cands.length; i++) {
|
||||||
mycands.c('candidate', SDPUtil.candidateToJingle(cands[i].candidate)).up();
|
mycands.c('candidate', SDPUtil.candidateToJingle(cands[i].candidate)).up();
|
||||||
}
|
}
|
||||||
|
@ -1046,6 +1232,7 @@ ColibriFocus.prototype.terminate = function (session, reason) {
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
change.c('sctpconnection', {
|
change.c('sctpconnection', {
|
||||||
|
id: $(this.channels[participant][channel]).attr('id'),
|
||||||
endpoint: $(this.channels[participant][channel]).attr('endpoint'),
|
endpoint: $(this.channels[participant][channel]).attr('endpoint'),
|
||||||
expire: '0'
|
expire: '0'
|
||||||
});
|
});
|
||||||
|
@ -1120,4 +1307,74 @@ ColibriFocus.prototype.sendTerminate = function (session, reason, text) {
|
||||||
window.clearInterval(this.statsinterval);
|
window.clearInterval(this.statsinterval);
|
||||||
this.statsinterval = null;
|
this.statsinterval = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ColibriFocus.prototype.setRTCPTerminationStrategy = function (strategyFQN) {
|
||||||
|
var self = this;
|
||||||
|
var strategyIQ = $iq({to: this.bridgejid, type: 'set'});
|
||||||
|
strategyIQ.c('conference', {
|
||||||
|
xmlns: 'http://jitsi.org/protocol/colibri',
|
||||||
|
id: this.confid,
|
||||||
|
});
|
||||||
|
|
||||||
|
strategyIQ.c('rtcp-termination-strategy', {name: strategyFQN });
|
||||||
|
|
||||||
|
strategyIQ.c('content', {name: "video"});
|
||||||
|
strategyIQ.up(); // end of content
|
||||||
|
|
||||||
|
console.log('setting RTCP termination strategy', strategyFQN);
|
||||||
|
this.connection.sendIQ(strategyIQ,
|
||||||
|
function (res) {
|
||||||
|
console.log('got result');
|
||||||
|
},
|
||||||
|
function (err) {
|
||||||
|
console.error('got error', err);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the default value of the channel last-n attribute in this conference and
|
||||||
|
* updates/patches the existing channels.
|
||||||
|
*/
|
||||||
|
ColibriFocus.prototype.setChannelLastN = function (channelLastN) {
|
||||||
|
if (('number' === typeof(channelLastN))
|
||||||
|
&& (this.channelLastN !== channelLastN))
|
||||||
|
{
|
||||||
|
this.channelLastN = channelLastN;
|
||||||
|
|
||||||
|
// Update/patch the existing channels.
|
||||||
|
var patch = $iq({ to: this.bridgejid, type: 'set' });
|
||||||
|
|
||||||
|
patch.c(
|
||||||
|
'conference',
|
||||||
|
{ xmlns: 'http://jitsi.org/protocol/colibri', id: this.confid });
|
||||||
|
patch.c('content', { name: 'video' });
|
||||||
|
patch.c(
|
||||||
|
'channel',
|
||||||
|
{
|
||||||
|
id: $(this.mychannel[1 /* video */]).attr('id'),
|
||||||
|
'last-n': this.channelLastN
|
||||||
|
});
|
||||||
|
patch.up(); // end of channel
|
||||||
|
for (var p = 0; p < this.channels.length; p++)
|
||||||
|
{
|
||||||
|
patch.c(
|
||||||
|
'channel',
|
||||||
|
{
|
||||||
|
id: $(this.channels[p][1 /* video */]).attr('id'),
|
||||||
|
'last-n': this.channelLastN
|
||||||
|
});
|
||||||
|
patch.up(); // end of channel
|
||||||
|
}
|
||||||
|
this.connection.sendIQ(
|
||||||
|
patch,
|
||||||
|
function (res) {
|
||||||
|
console.info('Set channel last-n succeeded:', res);
|
||||||
|
},
|
||||||
|
function (err) {
|
||||||
|
console.error('Set channel last-n failed:', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
/* jshint -W117 */
|
||||||
|
Strophe.addConnectionPlugin('rayo',
|
||||||
|
{
|
||||||
|
RAYO_XMLNS: 'urn:xmpp:rayo:1',
|
||||||
|
connection: null,
|
||||||
|
init: function (conn)
|
||||||
|
{
|
||||||
|
this.connection = conn;
|
||||||
|
if (this.connection.disco)
|
||||||
|
{
|
||||||
|
this.connection.disco.addFeature('urn:xmpp:rayo:client:1');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.connection.addHandler(
|
||||||
|
this.onRayo.bind(this), this.RAYO_XMLNS, 'iq', 'set', null, null);
|
||||||
|
},
|
||||||
|
onRayo: function (iq)
|
||||||
|
{
|
||||||
|
console.info("Rayo IQ", iq);
|
||||||
|
},
|
||||||
|
dial: function (to, from, roomName)
|
||||||
|
{
|
||||||
|
var self = this;
|
||||||
|
var req = $iq(
|
||||||
|
{
|
||||||
|
type: 'set',
|
||||||
|
to: config.hosts.call_control
|
||||||
|
}
|
||||||
|
);
|
||||||
|
req.c('dial',
|
||||||
|
{
|
||||||
|
xmlns: this.RAYO_XMLNS,
|
||||||
|
to: to,
|
||||||
|
from: from
|
||||||
|
});
|
||||||
|
req.c('header',
|
||||||
|
{
|
||||||
|
name: 'JvbRoomName',
|
||||||
|
value: roomName
|
||||||
|
});
|
||||||
|
|
||||||
|
this.connection.sendIQ(
|
||||||
|
req,
|
||||||
|
function (result)
|
||||||
|
{
|
||||||
|
console.info('Dial result ', result);
|
||||||
|
|
||||||
|
var resource = $(result).find('ref').attr('uri');
|
||||||
|
this.call_resource = resource.substr('xmpp:'.length);
|
||||||
|
console.info(
|
||||||
|
"Received call resource: " + this.call_resource);
|
||||||
|
},
|
||||||
|
function (error)
|
||||||
|
{
|
||||||
|
console.info('Dial error ', error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
hang_up: function ()
|
||||||
|
{
|
||||||
|
if (!this.call_resource)
|
||||||
|
{
|
||||||
|
console.warn("No call in progress");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
var req = $iq(
|
||||||
|
{
|
||||||
|
type: 'set',
|
||||||
|
to: this.call_resource
|
||||||
|
}
|
||||||
|
);
|
||||||
|
req.c('hangup',
|
||||||
|
{
|
||||||
|
xmlns: this.RAYO_XMLNS
|
||||||
|
});
|
||||||
|
|
||||||
|
this.connection.sendIQ(
|
||||||
|
req,
|
||||||
|
function (result)
|
||||||
|
{
|
||||||
|
console.info('Hangup result ', result);
|
||||||
|
self.call_resource = null;
|
||||||
|
},
|
||||||
|
function (error)
|
||||||
|
{
|
||||||
|
console.info('Hangup error ', error);
|
||||||
|
self.call_resource = null;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
|
@ -510,19 +510,21 @@ function getUserMediaWithConstraints(um, success_callback, failure_callback, res
|
||||||
var constraints = {audio: false, video: false};
|
var constraints = {audio: false, video: false};
|
||||||
|
|
||||||
if (um.indexOf('video') >= 0) {
|
if (um.indexOf('video') >= 0) {
|
||||||
constraints.video = {mandatory: {}};// same behaviour as true
|
constraints.video = { mandatory: {}, optional: [] };// same behaviour as true
|
||||||
}
|
}
|
||||||
if (um.indexOf('audio') >= 0) {
|
if (um.indexOf('audio') >= 0) {
|
||||||
constraints.audio = {};// same behaviour as true
|
constraints.audio = { mandatory: {}, optional: []};// same behaviour as true
|
||||||
}
|
}
|
||||||
if (um.indexOf('screen') >= 0) {
|
if (um.indexOf('screen') >= 0) {
|
||||||
constraints.video = {
|
constraints.video = {
|
||||||
mandatory: {
|
mandatory: {
|
||||||
chromeMediaSource: "screen",
|
chromeMediaSource: "screen",
|
||||||
|
googLeakyBucket: true,
|
||||||
maxWidth: window.screen.width,
|
maxWidth: window.screen.width,
|
||||||
maxHeight: window.screen.height,
|
maxHeight: window.screen.height,
|
||||||
maxFrameRate: 3
|
maxFrameRate: 3
|
||||||
}
|
},
|
||||||
|
optional: []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (um.indexOf('desktop') >= 0) {
|
if (um.indexOf('desktop') >= 0) {
|
||||||
|
@ -530,10 +532,35 @@ function getUserMediaWithConstraints(um, success_callback, failure_callback, res
|
||||||
mandatory: {
|
mandatory: {
|
||||||
chromeMediaSource: "desktop",
|
chromeMediaSource: "desktop",
|
||||||
chromeMediaSourceId: desktopStream,
|
chromeMediaSourceId: desktopStream,
|
||||||
|
googLeakyBucket: true,
|
||||||
maxWidth: window.screen.width,
|
maxWidth: window.screen.width,
|
||||||
maxHeight: window.screen.height,
|
maxHeight: window.screen.height,
|
||||||
maxFrameRate: 3
|
maxFrameRate: 3
|
||||||
}
|
},
|
||||||
|
optional: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (constraints.audio) {
|
||||||
|
// if it is good enough for hangouts...
|
||||||
|
constraints.audio.optional.push(
|
||||||
|
{googEchoCancellation: true},
|
||||||
|
{googAutoGainControl: true},
|
||||||
|
{googNoiseSupression: true},
|
||||||
|
{googHighpassFilter: true},
|
||||||
|
{googNoisesuppression2: true},
|
||||||
|
{googEchoCancellation2: true},
|
||||||
|
{googAutoGainControl2: true}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (constraints.video) {
|
||||||
|
constraints.video.optional.push(
|
||||||
|
{googNoiseReduction: true}
|
||||||
|
);
|
||||||
|
if (um.indexOf('video') >= 0) {
|
||||||
|
constraints.video.optional.push(
|
||||||
|
{googLeakyBucket: true}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -541,7 +568,7 @@ function getUserMediaWithConstraints(um, success_callback, failure_callback, res
|
||||||
var isAndroid = navigator.userAgent.indexOf('Android') != -1;
|
var isAndroid = navigator.userAgent.indexOf('Android') != -1;
|
||||||
|
|
||||||
if (resolution && !constraints.video || isAndroid) {
|
if (resolution && !constraints.video || isAndroid) {
|
||||||
constraints.video = {mandatory: {}};// same behaviour as true
|
constraints.video = { mandatory: {}, optional: [] };// same behaviour as true
|
||||||
}
|
}
|
||||||
// see https://code.google.com/p/chromium/issues/detail?id=143631#c9 for list of supported resolutions
|
// see https://code.google.com/p/chromium/issues/detail?id=143631#c9 for list of supported resolutions
|
||||||
switch (resolution) {
|
switch (resolution) {
|
||||||
|
@ -550,23 +577,23 @@ function getUserMediaWithConstraints(um, success_callback, failure_callback, res
|
||||||
case 'fullhd':
|
case 'fullhd':
|
||||||
constraints.video.mandatory.minWidth = 1920;
|
constraints.video.mandatory.minWidth = 1920;
|
||||||
constraints.video.mandatory.minHeight = 1080;
|
constraints.video.mandatory.minHeight = 1080;
|
||||||
constraints.video.mandatory.minAspectRatio = 1.77;
|
constraints.video.optional.push({ minAspectRatio: 1.77 });
|
||||||
break;
|
break;
|
||||||
case '720':
|
case '720':
|
||||||
case 'hd':
|
case 'hd':
|
||||||
constraints.video.mandatory.minWidth = 1280;
|
constraints.video.mandatory.minWidth = 1280;
|
||||||
constraints.video.mandatory.minHeight = 720;
|
constraints.video.mandatory.minHeight = 720;
|
||||||
constraints.video.mandatory.minAspectRatio = 1.77;
|
constraints.video.optional.push({ minAspectRatio: 1.77 });
|
||||||
break;
|
break;
|
||||||
case '360':
|
case '360':
|
||||||
constraints.video.mandatory.minWidth = 640;
|
constraints.video.mandatory.minWidth = 640;
|
||||||
constraints.video.mandatory.minHeight = 360;
|
constraints.video.mandatory.minHeight = 360;
|
||||||
constraints.video.mandatory.minAspectRatio = 1.77;
|
constraints.video.optional.push({ minAspectRatio: 1.77 });
|
||||||
break;
|
break;
|
||||||
case '180':
|
case '180':
|
||||||
constraints.video.mandatory.minWidth = 320;
|
constraints.video.mandatory.minWidth = 320;
|
||||||
constraints.video.mandatory.minHeight = 180;
|
constraints.video.mandatory.minHeight = 180;
|
||||||
constraints.video.mandatory.minAspectRatio = 1.77;
|
constraints.video.optional.push({ minAspectRatio: 1.77 });
|
||||||
break;
|
break;
|
||||||
// 4:3
|
// 4:3
|
||||||
case '960':
|
case '960':
|
||||||
|
@ -592,12 +619,12 @@ function getUserMediaWithConstraints(um, success_callback, failure_callback, res
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bandwidth) { // doesn't work currently, see webrtc issue 1846
|
if (bandwidth) { // doesn't work currently, see webrtc issue 1846
|
||||||
if (!constraints.video) constraints.video = {mandatory: {}};//same behaviour as true
|
if (!constraints.video) constraints.video = {mandatory: {}, optional: []};//same behaviour as true
|
||||||
constraints.video.optional = [{bandwidth: bandwidth}];
|
constraints.video.optional.push({bandwidth: bandwidth});
|
||||||
}
|
}
|
||||||
if (fps) { // for some cameras it might be necessary to request 30fps
|
if (fps) { // for some cameras it might be necessary to request 30fps
|
||||||
// so they choose 30fps mjpg over 10fps yuy2
|
// so they choose 30fps mjpg over 10fps yuy2
|
||||||
if (!constraints.video) constraints.video = {mandatory: {}};// same behaviour as tru;
|
if (!constraints.video) constraints.video = {mandatory: {}, optional: []};// same behaviour as true;
|
||||||
constraints.video.mandatory.minFrameRate = fps;
|
constraints.video.mandatory.minFrameRate = fps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -174,6 +174,9 @@ SDPUtil = {
|
||||||
case 'generation':
|
case 'generation':
|
||||||
candidate.generation = elems[i + 1];
|
candidate.generation = elems[i + 1];
|
||||||
break;
|
break;
|
||||||
|
case 'tcptype':
|
||||||
|
candidate.tcptype = elems[i + 1];
|
||||||
|
break;
|
||||||
default: // TODO
|
default: // TODO
|
||||||
console.log('parse_icecandidate not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
|
console.log('parse_icecandidate not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
|
||||||
}
|
}
|
||||||
|
@ -201,6 +204,12 @@ SDPUtil = {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (cand.hasOwnAttribute('tcptype')) {
|
||||||
|
line += 'tcptype';
|
||||||
|
line += ' ';
|
||||||
|
line += cand.tcptype;
|
||||||
|
line += ' ';
|
||||||
|
}
|
||||||
line += 'generation';
|
line += 'generation';
|
||||||
line += ' ';
|
line += ' ';
|
||||||
line += cand.hasOwnAttribute('generation') ? cand.generation : '0';
|
line += cand.hasOwnAttribute('generation') ? cand.generation : '0';
|
||||||
|
@ -283,7 +292,9 @@ SDPUtil = {
|
||||||
candidateToJingle: function (line) {
|
candidateToJingle: function (line) {
|
||||||
// a=candidate:2979166662 1 udp 2113937151 192.168.2.100 57698 typ host generation 0
|
// a=candidate:2979166662 1 udp 2113937151 192.168.2.100 57698 typ host generation 0
|
||||||
// <candidate component=... foundation=... generation=... id=... ip=... network=... port=... priority=... protocol=... type=.../>
|
// <candidate component=... foundation=... generation=... id=... ip=... network=... port=... priority=... protocol=... type=.../>
|
||||||
if (line.substring(0, 12) != 'a=candidate:') {
|
if (line.indexOf('candidate:') === 0) {
|
||||||
|
line = 'a=' + line;
|
||||||
|
} else if (line.substring(0, 12) != 'a=candidate:') {
|
||||||
console.log('parseCandidate called with a line that is not a candidate line');
|
console.log('parseCandidate called with a line that is not a candidate line');
|
||||||
console.log(line);
|
console.log(line);
|
||||||
return null;
|
return null;
|
||||||
|
@ -319,6 +330,9 @@ SDPUtil = {
|
||||||
case 'generation':
|
case 'generation':
|
||||||
candidate.generation = elems[i + 1];
|
candidate.generation = elems[i + 1];
|
||||||
break;
|
break;
|
||||||
|
case 'tcptype':
|
||||||
|
candidate.tcptype = elems[i + 1];
|
||||||
|
break;
|
||||||
default: // TODO
|
default: // TODO
|
||||||
console.log('not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
|
console.log('not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
|
||||||
}
|
}
|
||||||
|
|
|
@ -287,7 +287,10 @@ JingleSession.prototype.sendIceCandidates = function (candidates) {
|
||||||
if (SDPUtil.find_line(this.localSDP.media[mid], 'a=fingerprint:', this.localSDP.session)) {
|
if (SDPUtil.find_line(this.localSDP.media[mid], 'a=fingerprint:', this.localSDP.session)) {
|
||||||
var tmp = SDPUtil.parse_fingerprint(SDPUtil.find_line(this.localSDP.media[mid], 'a=fingerprint:', this.localSDP.session));
|
var tmp = SDPUtil.parse_fingerprint(SDPUtil.find_line(this.localSDP.media[mid], 'a=fingerprint:', this.localSDP.session));
|
||||||
tmp.required = true;
|
tmp.required = true;
|
||||||
cand.c('fingerprint').t(tmp.fingerprint);
|
cand.c(
|
||||||
|
'fingerprint',
|
||||||
|
{xmlns: 'urn:xmpp:jingle:apps:dtls:0'})
|
||||||
|
.t(tmp.fingerprint);
|
||||||
delete tmp.fingerprint;
|
delete tmp.fingerprint;
|
||||||
cand.attrs(tmp);
|
cand.attrs(tmp);
|
||||||
cand.up();
|
cand.up();
|
||||||
|
|
|
@ -0,0 +1,131 @@
|
||||||
|
/**
|
||||||
|
* Provides statistics for the local stream.
|
||||||
|
*/
|
||||||
|
var LocalStatsCollector = (function() {
|
||||||
|
/**
|
||||||
|
* Size of the webaudio analizer buffer.
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
var WEBAUDIO_ANALIZER_FFT_SIZE = 2048;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Value of the webaudio analizer smoothing time parameter.
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
var WEBAUDIO_ANALIZER_SMOOTING_TIME = 0.8;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <tt>LocalStatsCollector</tt> calculates statistics for the local stream.
|
||||||
|
*
|
||||||
|
* @param stream the local stream
|
||||||
|
* @param interval stats refresh interval given in ms.
|
||||||
|
* @param {function(LocalStatsCollector)} updateCallback the callback called on stats
|
||||||
|
* update.
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function LocalStatsCollectorProto(stream, interval, updateCallback) {
|
||||||
|
window.AudioContext = window.AudioContext || window.webkitAudioContext;
|
||||||
|
this.stream = stream;
|
||||||
|
this.intervalId = null;
|
||||||
|
this.intervalMilis = interval;
|
||||||
|
this.updateCallback = updateCallback;
|
||||||
|
this.audioLevel = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the collecting the statistics.
|
||||||
|
*/
|
||||||
|
LocalStatsCollectorProto.prototype.start = function () {
|
||||||
|
if (!window.AudioContext)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var context = new AudioContext();
|
||||||
|
var analyser = context.createAnalyser();
|
||||||
|
analyser.smoothingTimeConstant = WEBAUDIO_ANALIZER_SMOOTING_TIME;
|
||||||
|
analyser.fftSize = WEBAUDIO_ANALIZER_FFT_SIZE;
|
||||||
|
|
||||||
|
|
||||||
|
var source = context.createMediaStreamSource(this.stream);
|
||||||
|
source.connect(analyser);
|
||||||
|
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
this.intervalId = setInterval(
|
||||||
|
function () {
|
||||||
|
var array = new Uint8Array(analyser.frequencyBinCount);
|
||||||
|
analyser.getByteTimeDomainData(array);
|
||||||
|
var audioLevel = TimeDomainDataToAudioLevel(array);
|
||||||
|
if(audioLevel != self.audioLevel) {
|
||||||
|
self.audioLevel = animateLevel(audioLevel, self.audioLevel);
|
||||||
|
self.updateCallback(LocalStatsCollectorProto.LOCAL_JID, self.audioLevel);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
this.intervalMilis
|
||||||
|
);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops collecting the statistics.
|
||||||
|
*/
|
||||||
|
LocalStatsCollectorProto.prototype.stop = function () {
|
||||||
|
if (this.intervalId) {
|
||||||
|
clearInterval(this.intervalId);
|
||||||
|
this.intervalId = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts time domain data array to audio level.
|
||||||
|
* @param array the time domain data array.
|
||||||
|
* @returns {number} the audio level
|
||||||
|
*/
|
||||||
|
var TimeDomainDataToAudioLevel = function (samples) {
|
||||||
|
|
||||||
|
var maxVolume = 0;
|
||||||
|
|
||||||
|
var length = samples.length;
|
||||||
|
|
||||||
|
for (var i = 0; i < length; i++) {
|
||||||
|
if (maxVolume < samples[i])
|
||||||
|
maxVolume = samples[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseFloat(((maxVolume - 127) / 128).toFixed(3));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Animates audio level change
|
||||||
|
* @param newLevel the new audio level
|
||||||
|
* @param lastLevel the last audio level
|
||||||
|
* @returns {Number} the audio level to be set
|
||||||
|
*/
|
||||||
|
function animateLevel(newLevel, lastLevel)
|
||||||
|
{
|
||||||
|
var value = 0;
|
||||||
|
var diff = lastLevel - newLevel;
|
||||||
|
if(diff > 0.2)
|
||||||
|
{
|
||||||
|
value = lastLevel - 0.2;
|
||||||
|
}
|
||||||
|
else if(diff < -0.4)
|
||||||
|
{
|
||||||
|
value = lastLevel + 0.4;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
value = newLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseFloat(value.toFixed(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that this audio level is for local jid.
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
LocalStatsCollectorProto.LOCAL_JID = 'local';
|
||||||
|
|
||||||
|
return LocalStatsCollectorProto;
|
||||||
|
})();
|
|
@ -0,0 +1,30 @@
|
||||||
|
/**
|
||||||
|
* Provides a wrapper class for the MediaStream.
|
||||||
|
*
|
||||||
|
* TODO : Add here the src from the video element and other related properties
|
||||||
|
* and get rid of some of the mappings that we use throughout the UI.
|
||||||
|
*/
|
||||||
|
var MediaStream = (function() {
|
||||||
|
/**
|
||||||
|
* Creates a MediaStream object for the given data, session id and ssrc.
|
||||||
|
*
|
||||||
|
* @param data the data object from which we obtain the stream,
|
||||||
|
* the peerjid, etc.
|
||||||
|
* @param sid the session id
|
||||||
|
* @param ssrc the ssrc corresponding to this MediaStream
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function MediaStreamProto(data, sid, ssrc) {
|
||||||
|
this.VIDEO_TYPE = "Video";
|
||||||
|
this.AUDIO_TYPE = "Audio";
|
||||||
|
this.stream = data.stream;
|
||||||
|
this.peerjid = data.peerjid;
|
||||||
|
this.ssrc = ssrc;
|
||||||
|
this.session = connection.jingle.sessions[sid];
|
||||||
|
this.type = (this.stream.getVideoTracks().length > 0)
|
||||||
|
? this.VIDEO_TYPE : this.AUDIO_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MediaStreamProto;
|
||||||
|
})();
|
25
muc.js
|
@ -131,6 +131,12 @@ Strophe.addConnectionPlugin('emuc', {
|
||||||
// Always trigger presence to update bindings
|
// Always trigger presence to update bindings
|
||||||
console.log('presence change from', from);
|
console.log('presence change from', from);
|
||||||
$(document).trigger('presence.muc', [from, member, pres]);
|
$(document).trigger('presence.muc', [from, member, pres]);
|
||||||
|
|
||||||
|
// Trigger status message update
|
||||||
|
if (member.status) {
|
||||||
|
$(document).trigger('presence.status.muc', [from, member, pres]);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
onPresenceUnavailable: function (pres) {
|
onPresenceUnavailable: function (pres) {
|
||||||
|
@ -157,6 +163,17 @@ Strophe.addConnectionPlugin('emuc', {
|
||||||
var from = pres.getAttribute('from');
|
var from = pres.getAttribute('from');
|
||||||
if ($(pres).find('>error[type="auth"]>not-authorized[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) {
|
if ($(pres).find('>error[type="auth"]>not-authorized[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) {
|
||||||
$(document).trigger('passwordrequired.muc', [from]);
|
$(document).trigger('passwordrequired.muc', [from]);
|
||||||
|
} else if ($(pres).find(
|
||||||
|
'>error[type="cancel"]>not-allowed[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) {
|
||||||
|
var toDomain = Strophe.getDomainFromJid(pres.getAttribute('to'));
|
||||||
|
if(toDomain === config.hosts.anonymousdomain) {
|
||||||
|
// we are connected with anonymous domain and only non anonymous users can create rooms
|
||||||
|
// we must authorize the user
|
||||||
|
$(document).trigger('passwordrequired.main');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
console.warn('onPresError ', pres);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
console.warn('onPresError ', pres);
|
console.warn('onPresError ', pres);
|
||||||
}
|
}
|
||||||
|
@ -356,5 +373,13 @@ Strophe.addConnectionPlugin('emuc', {
|
||||||
addVideoInfoToPresence: function(isMuted) {
|
addVideoInfoToPresence: function(isMuted) {
|
||||||
this.presMap['videons'] = 'http://jitsi.org/jitmeet/video';
|
this.presMap['videons'] = 'http://jitsi.org/jitmeet/video';
|
||||||
this.presMap['videomuted'] = isMuted.toString();
|
this.presMap['videomuted'] = isMuted.toString();
|
||||||
|
},
|
||||||
|
findJidFromResource: function(resourceJid) {
|
||||||
|
var peerJid = null;
|
||||||
|
Object.keys(this.members).some(function (jid) {
|
||||||
|
peerJid = jid;
|
||||||
|
return Strophe.getResourceFromJid(jid) === resourceJid;
|
||||||
|
});
|
||||||
|
return peerJid;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -213,6 +213,8 @@ StatsCollector.prototype.processReport = function ()
|
||||||
// but it seems to vary between 0 and around 32k.
|
// but it seems to vary between 0 and around 32k.
|
||||||
audioLevel = audioLevel / 32767;
|
audioLevel = audioLevel / 32767;
|
||||||
jidStats.setSsrcAudioLevel(ssrc, audioLevel);
|
jidStats.setSsrcAudioLevel(ssrc, audioLevel);
|
||||||
|
if(jid != connection.emuc.myroomjid)
|
||||||
|
this.updateCallback(jid, audioLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
var key = 'packetsReceived';
|
var key = 'packetsReceived';
|
||||||
|
@ -281,7 +283,5 @@ StatsCollector.prototype.processReport = function ()
|
||||||
// bar indicator
|
// bar indicator
|
||||||
//console.info("Loss SMA3: " + outputAvg + " Q: " + quality);
|
//console.info("Loss SMA3: " + outputAvg + " Q: " + quality);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.updateCallback(self);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
135
toolbar.js
|
@ -5,23 +5,23 @@ var Toolbar = (function (my) {
|
||||||
/**
|
/**
|
||||||
* Opens the lock room dialog.
|
* Opens the lock room dialog.
|
||||||
*/
|
*/
|
||||||
my.openLockDialog = function() {
|
my.openLockDialog = function () {
|
||||||
// Only the focus is able to set a shared key.
|
// Only the focus is able to set a shared key.
|
||||||
if (focus === null) {
|
if (focus === null) {
|
||||||
if (sharedKey)
|
if (sharedKey)
|
||||||
$.prompt("This conversation is currently protected by"
|
$.prompt("This conversation is currently protected by"
|
||||||
+ " a shared secret key.",
|
+ " a shared secret key.",
|
||||||
{
|
{
|
||||||
title: "Secrect key",
|
title: "Secret key",
|
||||||
persistent: false
|
persistent: false
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
else
|
else
|
||||||
$.prompt("This conversation isn't currently protected by"
|
$.prompt("This conversation isn't currently protected by"
|
||||||
+ " a secret key. Only the owner of the conference" +
|
+ " a secret key. Only the owner of the conference"
|
||||||
+ " could set a shared key.",
|
+ " could set a shared key.",
|
||||||
{
|
{
|
||||||
title: "Secrect key",
|
title: "Secret key",
|
||||||
persistent: false
|
persistent: false
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -29,7 +29,7 @@ var Toolbar = (function (my) {
|
||||||
if (sharedKey) {
|
if (sharedKey) {
|
||||||
$.prompt("Are you sure you would like to remove your secret key?",
|
$.prompt("Are you sure you would like to remove your secret key?",
|
||||||
{
|
{
|
||||||
title: "Remove secrect key",
|
title: "Remove secret key",
|
||||||
persistent: false,
|
persistent: false,
|
||||||
buttons: { "Remove": true, "Cancel": false},
|
buttons: { "Remove": true, "Cancel": false},
|
||||||
defaultButton: 1,
|
defaultButton: 1,
|
||||||
|
@ -42,7 +42,7 @@ var Toolbar = (function (my) {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
$.prompt('<h2>Set a secrect key to lock your room</h2>' +
|
$.prompt('<h2>Set a secret key to lock your room</h2>' +
|
||||||
'<input id="lockKey" type="text" placeholder="your shared key" autofocus>',
|
'<input id="lockKey" type="text" placeholder="your shared key" autofocus>',
|
||||||
{
|
{
|
||||||
persistent: false,
|
persistent: false,
|
||||||
|
@ -54,7 +54,7 @@ var Toolbar = (function (my) {
|
||||||
submit: function (e, v, m, f) {
|
submit: function (e, v, m, f) {
|
||||||
if (v) {
|
if (v) {
|
||||||
var lockKey = document.getElementById('lockKey');
|
var lockKey = document.getElementById('lockKey');
|
||||||
|
|
||||||
if (lockKey.value) {
|
if (lockKey.value) {
|
||||||
setSharedKey(Util.escapeHtml(lockKey.value));
|
setSharedKey(Util.escapeHtml(lockKey.value));
|
||||||
lockRoom(true);
|
lockRoom(true);
|
||||||
|
@ -70,28 +70,80 @@ var Toolbar = (function (my) {
|
||||||
/**
|
/**
|
||||||
* Opens the invite link dialog.
|
* Opens the invite link dialog.
|
||||||
*/
|
*/
|
||||||
my.openLinkDialog = function() {
|
my.openLinkDialog = function () {
|
||||||
|
var inviteLink;
|
||||||
|
if (roomUrl == null)
|
||||||
|
inviteLink = "Your conference is currently being created...";
|
||||||
|
else
|
||||||
|
inviteLink = encodeURI(roomUrl);
|
||||||
|
|
||||||
$.prompt('<input id="inviteLinkRef" type="text" value="' +
|
$.prompt('<input id="inviteLinkRef" type="text" value="' +
|
||||||
encodeURI(roomUrl) + '" onclick="this.select();" readonly>',
|
inviteLink + '" onclick="this.select();" readonly>',
|
||||||
{
|
{
|
||||||
title: "Share this link with everyone you want to invite",
|
title: "Share this link with everyone you want to invite",
|
||||||
persistent: false,
|
persistent: false,
|
||||||
buttons: { "Cancel": false},
|
buttons: { "Invite": true, "Cancel": false},
|
||||||
loaded: function (event) {
|
defaultButton: 1,
|
||||||
document.getElementById('inviteLinkRef').select();
|
loaded: function (event) {
|
||||||
|
if (roomUrl)
|
||||||
|
document.getElementById('inviteLinkRef').select();
|
||||||
|
else
|
||||||
|
document.getElementById('jqi_state0_buttonInvite')
|
||||||
|
.disabled = true;
|
||||||
|
},
|
||||||
|
submit: function (e, v, m, f) {
|
||||||
|
if (v) {
|
||||||
|
if (roomUrl) {
|
||||||
|
inviteParticipants();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
);
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invite participants to conference.
|
||||||
|
*/
|
||||||
|
function inviteParticipants() {
|
||||||
|
if (roomUrl == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var sharedKeyText = "";
|
||||||
|
if (sharedKey && sharedKey.length > 0)
|
||||||
|
sharedKeyText
|
||||||
|
= "This conference is password protected. Please use the "
|
||||||
|
+ "following pin when joining:%0D%0A%0D%0A"
|
||||||
|
+ sharedKey + "%0D%0A%0D%0A";
|
||||||
|
|
||||||
|
var conferenceName = roomUrl.substring(roomUrl.lastIndexOf('/') + 1);
|
||||||
|
var subject = "Invitation to a Jitsi Meet (" + conferenceName + ")";
|
||||||
|
var body = "Hey there, I%27d like to invite you to a Jitsi Meet" +
|
||||||
|
" conference I%27ve just set up.%0D%0A%0D%0A" +
|
||||||
|
"Please click on the following link in order" +
|
||||||
|
" to join the conference.%0D%0A%0D%0A" +
|
||||||
|
roomUrl +
|
||||||
|
"%0D%0A%0D%0A" +
|
||||||
|
sharedKeyText +
|
||||||
|
"Note that Jitsi Meet is currently only supported by Chromium," +
|
||||||
|
" Google Chrome and Opera, so you need" +
|
||||||
|
" to be using one of these browsers.%0D%0A%0D%0A" +
|
||||||
|
"Talk to you in a sec!";
|
||||||
|
|
||||||
|
if (window.localStorage.displayname)
|
||||||
|
body += "%0D%0A%0D%0A" + window.localStorage.displayname;
|
||||||
|
|
||||||
|
window.open("mailto:?subject=" + subject + "&body=" + body, '_blank');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens the settings dialog.
|
* Opens the settings dialog.
|
||||||
*/
|
*/
|
||||||
my.openSettingsDialog = function() {
|
my.openSettingsDialog = function () {
|
||||||
$.prompt('<h2>Configure your conference</h2>' +
|
$.prompt('<h2>Configure your conference</h2>' +
|
||||||
'<input type="checkbox" id="initMuted"> Participants join muted<br/>' +
|
'<input type="checkbox" id="initMuted"> Participants join muted<br/>' +
|
||||||
'<input type="checkbox" id="requireNicknames"> Require nicknames<br/><br/>' +
|
'<input type="checkbox" id="requireNicknames"> Require nicknames<br/><br/>' +
|
||||||
'Set a secrect key to lock your room: <input id="lockKey" type="text" placeholder="your shared key" autofocus>',
|
'Set a secret key to lock your room: <input id="lockKey" type="text" placeholder="your shared key" autofocus>',
|
||||||
{
|
{
|
||||||
persistent: false,
|
persistent: false,
|
||||||
buttons: { "Save": true, "Cancel": false},
|
buttons: { "Save": true, "Cancel": false},
|
||||||
|
@ -104,13 +156,13 @@ var Toolbar = (function (my) {
|
||||||
if ($('#initMuted').is(":checked")) {
|
if ($('#initMuted').is(":checked")) {
|
||||||
// it is checked
|
// it is checked
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($('#requireNicknames').is(":checked")) {
|
if ($('#requireNicknames').is(":checked")) {
|
||||||
// it is checked
|
// it is checked
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
var lockKey = document.getElementById('lockKey');
|
var lockKey = document.getElementById('lockKey');
|
||||||
|
|
||||||
if (lockKey.value)
|
if (lockKey.value)
|
||||||
{
|
{
|
||||||
setSharedKey(lockKey.value);
|
setSharedKey(lockKey.value);
|
||||||
|
@ -185,9 +237,12 @@ var Toolbar = (function (my) {
|
||||||
if (!$('#header').is(':visible')) {
|
if (!$('#header').is(':visible')) {
|
||||||
Toolbar.showToolbar();
|
Toolbar.showToolbar();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then clear the time out, to dock the toolbar.
|
// Then clear the time out, to dock the toolbar.
|
||||||
clearTimeout(toolbarTimeout);
|
if (toolbarTimeout) {
|
||||||
toolbarTimeout = null;
|
clearTimeout(toolbarTimeout);
|
||||||
|
toolbarTimeout = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (!$('#header').is(':visible')) {
|
if (!$('#header').is(':visible')) {
|
||||||
|
@ -230,5 +285,37 @@ var Toolbar = (function (my) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Shows or hides the 'recording' button.
|
||||||
|
my.showRecordingButton = function (show) {
|
||||||
|
if (!config.enableRecording) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (show) {
|
||||||
|
$('#recording').css({display: "inline"});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$('#recording').css({display: "none"});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Toggle the state of the recording button
|
||||||
|
my.toggleRecordingButtonState = function() {
|
||||||
|
$('#recordButton').toggleClass('active');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Shows or hides SIP calls button
|
||||||
|
my.showSipCallButton = function (show)
|
||||||
|
{
|
||||||
|
if (config.hosts.call_control && show)
|
||||||
|
{
|
||||||
|
$('#sipCallButton').css({display: "inline"});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$('#sipCallButton').css({display: "none"});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return my;
|
return my;
|
||||||
}(Toolbar || {}));
|
}(Toolbar || {}));
|
||||||
|
|
4
util.js
|
@ -52,7 +52,9 @@ var Util = (function (my) {
|
||||||
*/
|
*/
|
||||||
my.getAvailableVideoWidth = function () {
|
my.getAvailableVideoWidth = function () {
|
||||||
var chatspaceWidth
|
var chatspaceWidth
|
||||||
= $('#chatspace').is(":visible") ? $('#chatspace').width() : 0;
|
= (Chat.isVisible() || ContactList.isVisible())
|
||||||
|
? $('#chatspace').width()
|
||||||
|
: 0;
|
||||||
|
|
||||||
return window.innerWidth - chatspaceWidth;
|
return window.innerWidth - chatspaceWidth;
|
||||||
};
|
};
|
||||||
|
|
606
videolayout.js
|
@ -1,6 +1,8 @@
|
||||||
var VideoLayout = (function (my) {
|
var VideoLayout = (function (my) {
|
||||||
var preMuted = false;
|
var preMuted = false;
|
||||||
var currentActiveSpeaker = null;
|
var currentDominantSpeaker = null;
|
||||||
|
var lastNCount = config.channelLastN;
|
||||||
|
var lastNEndpointsCache = [];
|
||||||
|
|
||||||
my.changeLocalAudio = function(stream) {
|
my.changeLocalAudio = function(stream) {
|
||||||
connection.jingle.localAudio = stream;
|
connection.jingle.localAudio = stream;
|
||||||
|
@ -26,25 +28,33 @@ var VideoLayout = (function (my) {
|
||||||
var localVideoContainer = document.getElementById('localVideoWrapper');
|
var localVideoContainer = document.getElementById('localVideoWrapper');
|
||||||
localVideoContainer.appendChild(localVideo);
|
localVideoContainer.appendChild(localVideo);
|
||||||
|
|
||||||
|
AudioLevels.updateAudioLevelCanvas();
|
||||||
|
|
||||||
var localVideoSelector = $('#' + localVideo.id);
|
var localVideoSelector = $('#' + localVideo.id);
|
||||||
// Add click handler
|
// Add click handler to both video and video wrapper elements in case
|
||||||
|
// there's no video.
|
||||||
localVideoSelector.click(function () {
|
localVideoSelector.click(function () {
|
||||||
VideoLayout.handleVideoThumbClicked(localVideo.src);
|
VideoLayout.handleVideoThumbClicked(localVideo.src);
|
||||||
});
|
});
|
||||||
|
$('#localVideoContainer').click(function () {
|
||||||
|
VideoLayout.handleVideoThumbClicked(localVideo.src);
|
||||||
|
});
|
||||||
|
|
||||||
// Add hover handler
|
// Add hover handler
|
||||||
$('#localVideoContainer').hover(
|
$('#localVideoContainer').hover(
|
||||||
function() {
|
function() {
|
||||||
VideoLayout.showDisplayName('localVideoContainer', true);
|
VideoLayout.showDisplayName('localVideoContainer', true);
|
||||||
},
|
},
|
||||||
function() {
|
function() {
|
||||||
if (focusedVideoSrc !== localVideo.src)
|
if (!VideoLayout.isLargeVideoVisible()
|
||||||
|
|| localVideo.src !== $('#largeVideo').attr('src'))
|
||||||
VideoLayout.showDisplayName('localVideoContainer', false);
|
VideoLayout.showDisplayName('localVideoContainer', false);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
// Add stream ended handler
|
// Add stream ended handler
|
||||||
stream.onended = function () {
|
stream.onended = function () {
|
||||||
localVideoContainer.removeChild(localVideo);
|
localVideoContainer.removeChild(localVideo);
|
||||||
VideoLayout.checkChangeLargeVideo(localVideo.src);
|
VideoLayout.updateRemovedVideo(localVideo.src);
|
||||||
};
|
};
|
||||||
// Flip video x axis if needed
|
// Flip video x axis if needed
|
||||||
flipXLocalVideo = flipX;
|
flipXLocalVideo = flipX;
|
||||||
|
@ -55,6 +65,7 @@ var VideoLayout = (function (my) {
|
||||||
RTC.attachMediaStream(localVideoSelector, stream);
|
RTC.attachMediaStream(localVideoSelector, stream);
|
||||||
|
|
||||||
localVideoSrc = localVideo.src;
|
localVideoSrc = localVideo.src;
|
||||||
|
|
||||||
VideoLayout.updateLargeVideo(localVideoSrc, 0);
|
VideoLayout.updateLargeVideo(localVideoSrc, 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -63,7 +74,7 @@ var VideoLayout = (function (my) {
|
||||||
* another one instead.
|
* another one instead.
|
||||||
* @param removedVideoSrc src stream identifier of the video.
|
* @param removedVideoSrc src stream identifier of the video.
|
||||||
*/
|
*/
|
||||||
my.checkChangeLargeVideo = function(removedVideoSrc) {
|
my.updateRemovedVideo = function(removedVideoSrc) {
|
||||||
if (removedVideoSrc === $('#largeVideo').attr('src')) {
|
if (removedVideoSrc === $('#largeVideo').attr('src')) {
|
||||||
// this is currently displayed as large
|
// this is currently displayed as large
|
||||||
// pick the last visible video in the row
|
// pick the last visible video in the row
|
||||||
|
@ -75,7 +86,8 @@ var VideoLayout = (function (my) {
|
||||||
if (!pick) {
|
if (!pick) {
|
||||||
console.info("Last visible video no longer exists");
|
console.info("Last visible video no longer exists");
|
||||||
pick = $('#remoteVideos>span[id!="mixedstream"]>video').get(0);
|
pick = $('#remoteVideos>span[id!="mixedstream"]>video').get(0);
|
||||||
if (!pick) {
|
|
||||||
|
if (!pick || !pick.src) {
|
||||||
// Try local video
|
// Try local video
|
||||||
console.info("Fallback to local video...");
|
console.info("Fallback to local video...");
|
||||||
pick = $('#remoteVideos>span>span>video').get(0);
|
pick = $('#remoteVideos>span>span>video').get(0);
|
||||||
|
@ -103,6 +115,8 @@ var VideoLayout = (function (my) {
|
||||||
var isVisible = $('#largeVideo').is(':visible');
|
var isVisible = $('#largeVideo').is(':visible');
|
||||||
|
|
||||||
$('#largeVideo').fadeOut(300, function () {
|
$('#largeVideo').fadeOut(300, function () {
|
||||||
|
var oldSrc = $(this).attr('src');
|
||||||
|
|
||||||
$(this).attr('src', newSrc);
|
$(this).attr('src', newSrc);
|
||||||
|
|
||||||
// Screen stream is already rotated
|
// Screen stream is already rotated
|
||||||
|
@ -129,8 +143,25 @@ var VideoLayout = (function (my) {
|
||||||
? getDesktopVideoPosition
|
? getDesktopVideoPosition
|
||||||
: getCameraVideoPosition;
|
: getCameraVideoPosition;
|
||||||
|
|
||||||
if (isVisible)
|
if (isVisible) {
|
||||||
|
// Only if the large video is currently visible.
|
||||||
|
// Disable previous dominant speaker video.
|
||||||
|
var oldJid = getJidFromVideoSrc(oldSrc);
|
||||||
|
if (oldJid) {
|
||||||
|
var oldResourceJid = Strophe.getResourceFromJid(oldJid);
|
||||||
|
VideoLayout.enableDominantSpeaker(oldResourceJid, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable new dominant speaker in the remote videos section.
|
||||||
|
var userJid = getJidFromVideoSrc(newSrc);
|
||||||
|
if (userJid)
|
||||||
|
{
|
||||||
|
var resourceJid = Strophe.getResourceFromJid(userJid);
|
||||||
|
VideoLayout.enableDominantSpeaker(resourceJid, true);
|
||||||
|
}
|
||||||
|
|
||||||
$(this).fadeIn(300);
|
$(this).fadeIn(300);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -138,35 +169,44 @@ var VideoLayout = (function (my) {
|
||||||
my.handleVideoThumbClicked = function(videoSrc) {
|
my.handleVideoThumbClicked = function(videoSrc) {
|
||||||
// Restore style for previously focused video
|
// Restore style for previously focused video
|
||||||
var focusJid = getJidFromVideoSrc(focusedVideoSrc);
|
var focusJid = getJidFromVideoSrc(focusedVideoSrc);
|
||||||
var oldContainer =
|
var oldContainer = getParticipantContainer(focusJid);
|
||||||
getParticipantContainer(focusJid);
|
|
||||||
|
|
||||||
if (oldContainer) {
|
if (oldContainer) {
|
||||||
oldContainer.removeClass("videoContainerFocused");
|
oldContainer.removeClass("videoContainerFocused");
|
||||||
VideoLayout.enableActiveSpeaker(
|
|
||||||
Strophe.getResourceFromJid(focusJid), false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unlock
|
// Unlock current focused.
|
||||||
if (focusedVideoSrc === videoSrc)
|
if (focusedVideoSrc === videoSrc)
|
||||||
{
|
{
|
||||||
focusedVideoSrc = null;
|
focusedVideoSrc = null;
|
||||||
|
var dominantSpeakerVideo = null;
|
||||||
|
// Enable the currently set dominant speaker.
|
||||||
|
if (currentDominantSpeaker) {
|
||||||
|
dominantSpeakerVideo
|
||||||
|
= $('#participant_' + currentDominantSpeaker + '>video')
|
||||||
|
.get(0);
|
||||||
|
|
||||||
|
if (dominantSpeakerVideo) {
|
||||||
|
VideoLayout.updateLargeVideo(dominantSpeakerVideo.src, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lock new video
|
// Lock new video
|
||||||
focusedVideoSrc = videoSrc;
|
focusedVideoSrc = videoSrc;
|
||||||
|
|
||||||
|
// Update focused/pinned interface.
|
||||||
var userJid = getJidFromVideoSrc(videoSrc);
|
var userJid = getJidFromVideoSrc(videoSrc);
|
||||||
if (userJid)
|
if (userJid)
|
||||||
{
|
{
|
||||||
var container = getParticipantContainer(userJid);
|
var container = getParticipantContainer(userJid);
|
||||||
container.addClass("videoContainerFocused");
|
container.addClass("videoContainerFocused");
|
||||||
|
|
||||||
var resourceJid = Strophe.getResourceFromJid(userJid);
|
|
||||||
VideoLayout.enableActiveSpeaker(resourceJid, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Triggers a "video.selected" event. The "false" parameter indicates
|
||||||
|
// this isn't a prezi.
|
||||||
$(document).trigger("video.selected", [false]);
|
$(document).trigger("video.selected", [false]);
|
||||||
|
|
||||||
VideoLayout.updateLargeVideo(videoSrc, 1);
|
VideoLayout.updateLargeVideo(videoSrc, 1);
|
||||||
|
@ -215,44 +255,71 @@ var VideoLayout = (function (my) {
|
||||||
* Shows/hides the large video.
|
* Shows/hides the large video.
|
||||||
*/
|
*/
|
||||||
my.setLargeVideoVisible = function(isVisible) {
|
my.setLargeVideoVisible = function(isVisible) {
|
||||||
|
var largeVideoJid = getJidFromVideoSrc($('#largeVideo').attr('src'));
|
||||||
|
var resourceJid = Strophe.getResourceFromJid(largeVideoJid);
|
||||||
|
|
||||||
if (isVisible) {
|
if (isVisible) {
|
||||||
$('#largeVideo').css({visibility: 'visible'});
|
$('#largeVideo').css({visibility: 'visible'});
|
||||||
$('.watermark').css({visibility: 'visible'});
|
$('.watermark').css({visibility: 'visible'});
|
||||||
|
VideoLayout.enableDominantSpeaker(resourceJid, true);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$('#largeVideo').css({visibility: 'hidden'});
|
$('#largeVideo').css({visibility: 'hidden'});
|
||||||
$('.watermark').css({visibility: 'hidden'});
|
$('.watermark').css({visibility: 'hidden'});
|
||||||
|
VideoLayout.enableDominantSpeaker(resourceJid, false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the large video is currently visible.
|
||||||
|
*
|
||||||
|
* @return <tt>true</tt> if visible, <tt>false</tt> - otherwise
|
||||||
|
*/
|
||||||
|
my.isLargeVideoVisible = function() {
|
||||||
|
return $('#largeVideo').is(':visible');
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if container for participant identified by given peerJid exists
|
* Checks if container for participant identified by given peerJid exists
|
||||||
* in the document and creates it eventually.
|
* in the document and creates it eventually.
|
||||||
*
|
*
|
||||||
* @param peerJid peer Jid to check.
|
* @param peerJid peer Jid to check.
|
||||||
|
*
|
||||||
|
* @return Returns <tt>true</tt> if the peer container exists,
|
||||||
|
* <tt>false</tt> - otherwise
|
||||||
*/
|
*/
|
||||||
my.ensurePeerContainerExists = function(peerJid) {
|
my.ensurePeerContainerExists = function(peerJid) {
|
||||||
var peerResource = Strophe.getResourceFromJid(peerJid);
|
ContactList.ensureAddContact(peerJid);
|
||||||
var videoSpanId = 'participant_' + peerResource;
|
|
||||||
|
var resourceJid = Strophe.getResourceFromJid(peerJid);
|
||||||
|
|
||||||
|
var videoSpanId = 'participant_' + resourceJid;
|
||||||
|
|
||||||
if ($('#' + videoSpanId).length > 0) {
|
if ($('#' + videoSpanId).length > 0) {
|
||||||
// If there's been a focus change, make sure we add focus related
|
// If there's been a focus change, make sure we add focus related
|
||||||
// interface!!
|
// interface!!
|
||||||
if (focus && $('#remote_popupmenu_' + peerResource).length <= 0)
|
if (focus && $('#remote_popupmenu_' + resourceJid).length <= 0)
|
||||||
addRemoteVideoMenu( peerJid,
|
addRemoteVideoMenu( peerJid,
|
||||||
document.getElementById(videoSpanId));
|
document.getElementById(videoSpanId));
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
var container
|
||||||
|
= VideoLayout.addRemoteVideoContainer(peerJid, videoSpanId);
|
||||||
|
|
||||||
var container
|
var nickfield = document.createElement('span');
|
||||||
= VideoLayout.addRemoteVideoContainer(peerJid, videoSpanId);
|
nickfield.className = "nick";
|
||||||
|
nickfield.appendChild(document.createTextNode(resourceJid));
|
||||||
|
container.appendChild(nickfield);
|
||||||
|
|
||||||
var nickfield = document.createElement('span');
|
// In case this is not currently in the last n we don't show it.
|
||||||
nickfield.className = "nick";
|
if (lastNCount
|
||||||
nickfield.appendChild(document.createTextNode(peerResource));
|
&& lastNCount > 0
|
||||||
container.appendChild(nickfield);
|
&& $('#remoteVideos>span').length >= lastNCount + 2) {
|
||||||
VideoLayout.resizeThumbnails();
|
showPeerContainer(resourceJid, false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
VideoLayout.resizeThumbnails();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
my.addRemoteVideoContainer = function(peerJid, spanId) {
|
my.addRemoteVideoContainer = function(peerJid, spanId) {
|
||||||
|
@ -267,14 +334,167 @@ var VideoLayout = (function (my) {
|
||||||
addRemoteVideoMenu(peerJid, container);
|
addRemoteVideoMenu(peerJid, container);
|
||||||
|
|
||||||
remotes.appendChild(container);
|
remotes.appendChild(container);
|
||||||
|
AudioLevels.updateAudioLevelCanvas(peerJid);
|
||||||
|
|
||||||
return container;
|
return container;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows the display name for the given video.
|
* Creates an audio or video stream element.
|
||||||
*/
|
*/
|
||||||
my.setDisplayName = function(videoSpanId, displayName) {
|
my.createStreamElement = function (sid, stream) {
|
||||||
|
var isVideo = stream.getVideoTracks().length > 0;
|
||||||
|
|
||||||
|
var element = isVideo
|
||||||
|
? document.createElement('video')
|
||||||
|
: document.createElement('audio');
|
||||||
|
var id = (isVideo ? 'remoteVideo_' : 'remoteAudio_')
|
||||||
|
+ sid + '_' + stream.id;
|
||||||
|
|
||||||
|
element.id = id;
|
||||||
|
element.autoplay = true;
|
||||||
|
element.oncontextmenu = function () { return false; };
|
||||||
|
|
||||||
|
return element;
|
||||||
|
};
|
||||||
|
|
||||||
|
my.addRemoteStreamElement
|
||||||
|
= function (container, sid, stream, peerJid, thessrc) {
|
||||||
|
var newElementId = null;
|
||||||
|
|
||||||
|
var isVideo = stream.getVideoTracks().length > 0;
|
||||||
|
|
||||||
|
if (container) {
|
||||||
|
var streamElement = VideoLayout.createStreamElement(sid, stream);
|
||||||
|
newElementId = streamElement.id;
|
||||||
|
|
||||||
|
container.appendChild(streamElement);
|
||||||
|
|
||||||
|
var sel = $('#' + newElementId);
|
||||||
|
sel.hide();
|
||||||
|
|
||||||
|
// If the container is currently visible we attach the stream.
|
||||||
|
if (!isVideo
|
||||||
|
|| (container.offsetParent !== null && isVideo)) {
|
||||||
|
RTC.attachMediaStream(sel, stream);
|
||||||
|
|
||||||
|
if (isVideo)
|
||||||
|
waitForRemoteVideo(sel, thessrc, stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.onended = function () {
|
||||||
|
console.log('stream ended', this);
|
||||||
|
|
||||||
|
VideoLayout.removeRemoteStreamElement(stream, container);
|
||||||
|
|
||||||
|
if (peerJid)
|
||||||
|
ContactList.removeContact(peerJid);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add click handler.
|
||||||
|
container.onclick = function (event) {
|
||||||
|
/*
|
||||||
|
* FIXME It turns out that videoThumb may not exist (if there is
|
||||||
|
* no actual video).
|
||||||
|
*/
|
||||||
|
var videoThumb = $('#' + container.id + '>video').get(0);
|
||||||
|
|
||||||
|
if (videoThumb)
|
||||||
|
VideoLayout.handleVideoThumbClicked(videoThumb.src);
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add hover handler
|
||||||
|
$(container).hover(
|
||||||
|
function() {
|
||||||
|
VideoLayout.showDisplayName(container.id, true);
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
var videoSrc = null;
|
||||||
|
if ($('#' + container.id + '>video')
|
||||||
|
&& $('#' + container.id + '>video').length > 0) {
|
||||||
|
videoSrc = $('#' + container.id + '>video').get(0).src;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the video has been "pinned" by the user we want to
|
||||||
|
// keep the display name on place.
|
||||||
|
if (!VideoLayout.isLargeVideoVisible()
|
||||||
|
|| videoSrc !== $('#largeVideo').attr('src'))
|
||||||
|
VideoLayout.showDisplayName(container.id, false);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return newElementId;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the remote stream element corresponding to the given stream and
|
||||||
|
* parent container.
|
||||||
|
*
|
||||||
|
* @param stream the stream
|
||||||
|
* @param container
|
||||||
|
*/
|
||||||
|
my.removeRemoteStreamElement = function (stream, container) {
|
||||||
|
if (!container)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var select = null;
|
||||||
|
var removedVideoSrc = null;
|
||||||
|
if (stream.getVideoTracks().length > 0) {
|
||||||
|
select = $('#' + container.id + '>video');
|
||||||
|
removedVideoSrc = select.get(0).src;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
select = $('#' + container.id + '>audio');
|
||||||
|
|
||||||
|
// Remove video source from the mapping.
|
||||||
|
delete videoSrcToSsrc[removedVideoSrc];
|
||||||
|
|
||||||
|
// Mark video as removed to cancel waiting loop(if video is removed
|
||||||
|
// before has started)
|
||||||
|
select.removed = true;
|
||||||
|
select.remove();
|
||||||
|
|
||||||
|
var audioCount = $('#' + container.id + '>audio').length;
|
||||||
|
var videoCount = $('#' + container.id + '>video').length;
|
||||||
|
|
||||||
|
if (!audioCount && !videoCount) {
|
||||||
|
console.log("Remove whole user", container.id);
|
||||||
|
// Remove whole container
|
||||||
|
container.remove();
|
||||||
|
Util.playSoundNotification('userLeft');
|
||||||
|
VideoLayout.resizeThumbnails();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removedVideoSrc)
|
||||||
|
VideoLayout.updateRemovedVideo(removedVideoSrc);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show/hide peer container for the given resourceJid.
|
||||||
|
*/
|
||||||
|
function showPeerContainer(resourceJid, isShow) {
|
||||||
|
var peerContainer = $('#participant_' + resourceJid);
|
||||||
|
|
||||||
|
if (!peerContainer)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!peerContainer.is(':visible') && isShow)
|
||||||
|
peerContainer.show();
|
||||||
|
else if (peerContainer.is(':visible') && !isShow)
|
||||||
|
peerContainer.hide();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the display name for the given video span id.
|
||||||
|
*/
|
||||||
|
function setDisplayName(videoSpanId, displayName) {
|
||||||
var nameSpan = $('#' + videoSpanId + '>span.displayname');
|
var nameSpan = $('#' + videoSpanId + '>span.displayname');
|
||||||
|
var defaultLocalDisplayName = "Me";
|
||||||
|
var defaultRemoteDisplayName = "Speaker";
|
||||||
|
|
||||||
// If we already have a display name for this video.
|
// If we already have a display name for this video.
|
||||||
if (nameSpan.length > 0) {
|
if (nameSpan.length > 0) {
|
||||||
|
@ -282,21 +502,33 @@ var VideoLayout = (function (my) {
|
||||||
|
|
||||||
if (nameSpanElement.id === 'localDisplayName' &&
|
if (nameSpanElement.id === 'localDisplayName' &&
|
||||||
$('#localDisplayName').text() !== displayName) {
|
$('#localDisplayName').text() !== displayName) {
|
||||||
$('#localDisplayName').text(displayName);
|
if (displayName && displayName.length > 0)
|
||||||
|
$('#localDisplayName').text(displayName + ' (me)');
|
||||||
|
else
|
||||||
|
$('#localDisplayName').text(defaultLocalDisplayName);
|
||||||
} else {
|
} else {
|
||||||
$('#' + videoSpanId + '_name').text(displayName);
|
if (displayName && displayName.length > 0)
|
||||||
|
$('#' + videoSpanId + '_name').text(displayName);
|
||||||
|
else
|
||||||
|
$('#' + videoSpanId + '_name').text(defaultRemoteDisplayName);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var editButton = null;
|
var editButton = null;
|
||||||
|
|
||||||
|
nameSpan = document.createElement('span');
|
||||||
|
nameSpan.className = 'displayname';
|
||||||
|
$('#' + videoSpanId)[0].appendChild(nameSpan);
|
||||||
|
|
||||||
if (videoSpanId === 'localVideoContainer') {
|
if (videoSpanId === 'localVideoContainer') {
|
||||||
editButton = createEditDisplayNameButton();
|
editButton = createEditDisplayNameButton();
|
||||||
|
nameSpan.innerText = defaultLocalDisplayName;
|
||||||
}
|
}
|
||||||
if (displayName.length) {
|
else {
|
||||||
nameSpan = document.createElement('span');
|
nameSpan.innerText = defaultRemoteDisplayName;
|
||||||
nameSpan.className = 'displayname';
|
}
|
||||||
|
|
||||||
|
if (displayName && displayName.length > 0) {
|
||||||
nameSpan.innerText = displayName;
|
nameSpan.innerText = displayName;
|
||||||
$('#' + videoSpanId)[0].appendChild(nameSpan);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!editButton) {
|
if (!editButton) {
|
||||||
|
@ -318,7 +550,9 @@ var VideoLayout = (function (my) {
|
||||||
editableText.setAttribute('placeholder', 'ex. Jane Pink');
|
editableText.setAttribute('placeholder', 'ex. Jane Pink');
|
||||||
$('#' + videoSpanId)[0].appendChild(editableText);
|
$('#' + videoSpanId)[0].appendChild(editableText);
|
||||||
|
|
||||||
$('#localVideoContainer .displayname').bind("click", function (e) {
|
$('#localVideoContainer .displayname')
|
||||||
|
.bind("click", function (e) {
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
$('#localDisplayName').hide();
|
$('#localDisplayName').hide();
|
||||||
$('#editDisplayName').show();
|
$('#editDisplayName').show();
|
||||||
|
@ -336,16 +570,15 @@ var VideoLayout = (function (my) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$('#localDisplayName').is(":visible")) {
|
if (!$('#localDisplayName').is(":visible")) {
|
||||||
if (nickname) {
|
if (nickname)
|
||||||
$('#localDisplayName').text(nickname + " (me)");
|
$('#localDisplayName').text(nickname + " (me)");
|
||||||
$('#localDisplayName').show();
|
else
|
||||||
}
|
$('#localDisplayName')
|
||||||
else {
|
.text(defaultLocalDisplayName);
|
||||||
$('#localDisplayName').text(nickname);
|
$('#localDisplayName').show();
|
||||||
}
|
|
||||||
|
|
||||||
$('#editDisplayName').hide();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$('#editDisplayName').hide();
|
||||||
};
|
};
|
||||||
|
|
||||||
$('#editDisplayName').one("focusout", function (e) {
|
$('#editDisplayName').one("focusout", function (e) {
|
||||||
|
@ -370,7 +603,6 @@ var VideoLayout = (function (my) {
|
||||||
*/
|
*/
|
||||||
my.showDisplayName = function(videoSpanId, isShow) {
|
my.showDisplayName = function(videoSpanId, isShow) {
|
||||||
var nameSpan = $('#' + videoSpanId + '>span.displayname').get(0);
|
var nameSpan = $('#' + videoSpanId + '>span.displayname').get(0);
|
||||||
|
|
||||||
if (isShow) {
|
if (isShow) {
|
||||||
if (nameSpan && nameSpan.innerHTML && nameSpan.innerHTML.length)
|
if (nameSpan && nameSpan.innerHTML && nameSpan.innerHTML.length)
|
||||||
nameSpan.setAttribute("style", "display:inline-block;");
|
nameSpan.setAttribute("style", "display:inline-block;");
|
||||||
|
@ -381,6 +613,38 @@ var VideoLayout = (function (my) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows the presence status message for the given video.
|
||||||
|
*/
|
||||||
|
my.setPresenceStatus = function (videoSpanId, statusMsg) {
|
||||||
|
|
||||||
|
if (!$('#' + videoSpanId).length) {
|
||||||
|
// No container
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var statusSpan = $('#' + videoSpanId + '>span.status');
|
||||||
|
if (!statusSpan.length) {
|
||||||
|
//Add status span
|
||||||
|
statusSpan = document.createElement('span');
|
||||||
|
statusSpan.className = 'status';
|
||||||
|
statusSpan.id = videoSpanId + '_status';
|
||||||
|
$('#' + videoSpanId)[0].appendChild(statusSpan);
|
||||||
|
|
||||||
|
statusSpan = $('#' + videoSpanId + '>span.status');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display status
|
||||||
|
if (statusMsg && statusMsg.length) {
|
||||||
|
$('#' + videoSpanId + '_status').text(statusMsg);
|
||||||
|
statusSpan.get(0).setAttribute("style", "display:inline-block;");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Hide
|
||||||
|
statusSpan.get(0).setAttribute("style", "display:none;");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows a visual indicator for the focus of the conference.
|
* Shows a visual indicator for the focus of the conference.
|
||||||
* Currently if we're not the owner of the conference we obtain the focus
|
* Currently if we're not the owner of the conference we obtain the focus
|
||||||
|
@ -413,9 +677,7 @@ var VideoLayout = (function (my) {
|
||||||
if (!indicatorSpan || indicatorSpan.length === 0) {
|
if (!indicatorSpan || indicatorSpan.length === 0) {
|
||||||
indicatorSpan = document.createElement('span');
|
indicatorSpan = document.createElement('span');
|
||||||
indicatorSpan.className = 'focusindicator';
|
indicatorSpan.className = 'focusindicator';
|
||||||
Util.setTooltip(indicatorSpan,
|
|
||||||
"The owner of<br/>this conference",
|
|
||||||
"top");
|
|
||||||
focusContainer.appendChild(indicatorSpan);
|
focusContainer.appendChild(indicatorSpan);
|
||||||
|
|
||||||
createFocusIndicatorElement(indicatorSpan);
|
createFocusIndicatorElement(indicatorSpan);
|
||||||
|
@ -506,7 +768,9 @@ var VideoLayout = (function (my) {
|
||||||
* Resizes thumbnails.
|
* Resizes thumbnails.
|
||||||
*/
|
*/
|
||||||
my.resizeThumbnails = function() {
|
my.resizeThumbnails = function() {
|
||||||
var thumbnailSize = calculateThumbnailSize();
|
var videoSpaceWidth = $('#remoteVideos').width();
|
||||||
|
|
||||||
|
var thumbnailSize = VideoLayout.calculateThumbnailSize(videoSpaceWidth);
|
||||||
var width = thumbnailSize[0];
|
var width = thumbnailSize[0];
|
||||||
var height = thumbnailSize[1];
|
var height = thumbnailSize[1];
|
||||||
|
|
||||||
|
@ -515,63 +779,66 @@ var VideoLayout = (function (my) {
|
||||||
$('#remoteVideos').height(height);
|
$('#remoteVideos').height(height);
|
||||||
$('#remoteVideos>span').width(width);
|
$('#remoteVideos>span').width(width);
|
||||||
$('#remoteVideos>span').height(height);
|
$('#remoteVideos>span').height(height);
|
||||||
|
|
||||||
|
$(document).trigger("remotevideo.resized", [width, height]);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enables the active speaker UI.
|
* Enables the dominant speaker UI.
|
||||||
*
|
*
|
||||||
* @param resourceJid the jid indicating the video element to
|
* @param resourceJid the jid indicating the video element to
|
||||||
* activate/deactivate
|
* activate/deactivate
|
||||||
* @param isEnable indicates if the active speaker should be enabled or
|
* @param isEnable indicates if the dominant speaker should be enabled or
|
||||||
* disabled
|
* disabled
|
||||||
*/
|
*/
|
||||||
my.enableActiveSpeaker = function(resourceJid, isEnable) {
|
my.enableDominantSpeaker = function(resourceJid, isEnable) {
|
||||||
console.log("Enable active speaker", resourceJid, isEnable);
|
var displayName = resourceJid;
|
||||||
|
var nameSpan = $('#participant_' + resourceJid + '>span.displayname');
|
||||||
|
if (nameSpan.length > 0)
|
||||||
|
displayName = nameSpan.text();
|
||||||
|
|
||||||
|
console.log("UI enable dominant speaker",
|
||||||
|
displayName,
|
||||||
|
resourceJid,
|
||||||
|
isEnable);
|
||||||
|
|
||||||
var videoSpanId = null;
|
var videoSpanId = null;
|
||||||
|
var videoContainerId = null;
|
||||||
if (resourceJid
|
if (resourceJid
|
||||||
=== Strophe.getResourceFromJid(connection.emuc.myroomjid))
|
=== Strophe.getResourceFromJid(connection.emuc.myroomjid)) {
|
||||||
videoSpanId = 'localVideoWrapper';
|
videoSpanId = 'localVideoWrapper';
|
||||||
else
|
videoContainerId = 'localVideoContainer';
|
||||||
|
}
|
||||||
|
else {
|
||||||
videoSpanId = 'participant_' + resourceJid;
|
videoSpanId = 'participant_' + resourceJid;
|
||||||
|
videoContainerId = videoSpanId;
|
||||||
|
}
|
||||||
|
|
||||||
videoSpan = document.getElementById(videoSpanId);
|
videoSpan = document.getElementById(videoContainerId);
|
||||||
|
|
||||||
if (!videoSpan) {
|
if (!videoSpan) {
|
||||||
console.error("No video element for jid", resourceJid);
|
console.error("No video element for jid", resourceJid);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there's an active speaker (automatically) selected we have to
|
|
||||||
// disable this state and update the current active speaker.
|
|
||||||
if (isEnable) {
|
|
||||||
if (currentActiveSpeaker) {
|
|
||||||
var oldSpeaker = currentActiveSpeaker;
|
|
||||||
setTimeout(function () {
|
|
||||||
VideoLayout.enableActiveSpeaker(oldSpeaker, false);
|
|
||||||
}, 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
currentActiveSpeaker = resourceJid;
|
|
||||||
}
|
|
||||||
else if (resourceJid === currentActiveSpeaker)
|
|
||||||
currentActiveSpeaker = null;
|
|
||||||
|
|
||||||
var video = $('#' + videoSpanId + '>video');
|
var video = $('#' + videoSpanId + '>video');
|
||||||
|
|
||||||
if (video && video.length > 0) {
|
if (video && video.length > 0) {
|
||||||
var videoElement = video.get(0);
|
|
||||||
if (isEnable) {
|
if (isEnable) {
|
||||||
if (!videoElement.classList.contains("activespeaker"))
|
VideoLayout.showDisplayName(videoContainerId, true);
|
||||||
videoElement.classList.add("activespeaker");
|
|
||||||
|
|
||||||
VideoLayout.showDisplayName(videoSpanId, true);
|
if (!videoSpan.classList.contains("dominantspeaker"))
|
||||||
|
videoSpan.classList.add("dominantspeaker");
|
||||||
|
|
||||||
|
video.css({visibility: 'hidden'});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
VideoLayout.showDisplayName(videoSpanId, false);
|
VideoLayout.showDisplayName(videoContainerId, false);
|
||||||
|
|
||||||
if (videoElement.classList.contains("activespeaker"))
|
if (videoSpan.classList.contains("dominantspeaker"))
|
||||||
videoElement.classList.remove("activespeaker");
|
videoSpan.classList.remove("dominantspeaker");
|
||||||
|
|
||||||
|
video.css({visibility: 'visible'});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -616,18 +883,26 @@ var VideoLayout = (function (my) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates the thumbnail size.
|
* Calculates the thumbnail size.
|
||||||
|
*
|
||||||
|
* @param videoSpaceWidth the width of the video space
|
||||||
*/
|
*/
|
||||||
var calculateThumbnailSize = function () {
|
my.calculateThumbnailSize = function (videoSpaceWidth) {
|
||||||
// Calculate the available height, which is the inner window height minus
|
// 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
|
// 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
|
// bottom of the large video, minus the 36px space inside the remoteVideos
|
||||||
// container used for highlighting shadow.
|
// container used for highlighting shadow.
|
||||||
var availableHeight = 100;
|
var availableHeight = 100;
|
||||||
|
|
||||||
var numvids = $('#remoteVideos>span:visible').length;
|
var numvids = 0;
|
||||||
|
if (lastNCount && lastNCount > 0)
|
||||||
|
numvids = lastNCount + 1;
|
||||||
|
else
|
||||||
|
numvids = $('#remoteVideos>span:visible').length;
|
||||||
|
|
||||||
|
// Remove the 3px borders arround videos and border around the remote
|
||||||
|
// videos area
|
||||||
|
var availableWinWidth = videoSpaceWidth - 2 * 3 * numvids - 70;
|
||||||
|
|
||||||
// Remove the 1px borders arround videos and the chat width.
|
|
||||||
var availableWinWidth = $('#remoteVideos').width() - 2 * numvids - 50;
|
|
||||||
var availableWidth = availableWinWidth / numvids;
|
var availableWidth = availableWinWidth / numvids;
|
||||||
var aspectRatio = 16.0 / 9.0;
|
var aspectRatio = 16.0 / 9.0;
|
||||||
var maxHeight = Math.min(160, availableHeight);
|
var maxHeight = Math.min(160, availableHeight);
|
||||||
|
@ -703,6 +978,10 @@ var VideoLayout = (function (my) {
|
||||||
var focusIndicator = document.createElement('i');
|
var focusIndicator = document.createElement('i');
|
||||||
focusIndicator.className = 'fa fa-star';
|
focusIndicator.className = 'fa fa-star';
|
||||||
parentElement.appendChild(focusIndicator);
|
parentElement.appendChild(focusIndicator);
|
||||||
|
|
||||||
|
Util.setTooltip(parentElement,
|
||||||
|
"The owner of<br/>this conference",
|
||||||
|
"top");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -733,6 +1012,27 @@ var VideoLayout = (function (my) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current dominant speaker resource jid.
|
||||||
|
*/
|
||||||
|
my.getDominantSpeakerResourceJid = function () {
|
||||||
|
return currentDominantSpeaker;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the corresponding resource jid to the given peer container
|
||||||
|
* DOM element.
|
||||||
|
*
|
||||||
|
* @return the corresponding resource jid to the given peer container
|
||||||
|
* DOM element
|
||||||
|
*/
|
||||||
|
my.getPeerContainerResourceJid = function (containerElement) {
|
||||||
|
var i = containerElement.id.indexOf('participant_');
|
||||||
|
|
||||||
|
if (i >= 0)
|
||||||
|
return containerElement.id.substring(i + 12);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the remote video menu element for the given <tt>jid</tt> in the
|
* Adds the remote video menu element for the given <tt>jid</tt> in the
|
||||||
* given <tt>parentElement</tt>.
|
* given <tt>parentElement</tt>.
|
||||||
|
@ -810,6 +1110,9 @@ var VideoLayout = (function (my) {
|
||||||
popupmenuElement.appendChild(ejectMenuItem);
|
popupmenuElement.appendChild(ejectMenuItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On audio muted event.
|
||||||
|
*/
|
||||||
$(document).bind('audiomuted.muc', function (event, jid, isMuted) {
|
$(document).bind('audiomuted.muc', function (event, jid, isMuted) {
|
||||||
var videoSpanId = null;
|
var videoSpanId = null;
|
||||||
if (jid === connection.emuc.myroomjid) {
|
if (jid === connection.emuc.myroomjid) {
|
||||||
|
@ -828,6 +1131,9 @@ var VideoLayout = (function (my) {
|
||||||
VideoLayout.showAudioIndicator(videoSpanId, isMuted);
|
VideoLayout.showAudioIndicator(videoSpanId, isMuted);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On video muted event.
|
||||||
|
*/
|
||||||
$(document).bind('videomuted.muc', function (event, jid, isMuted) {
|
$(document).bind('videomuted.muc', function (event, jid, isMuted) {
|
||||||
var videoSpanId = null;
|
var videoSpanId = null;
|
||||||
if (jid === connection.emuc.myroomjid) {
|
if (jid === connection.emuc.myroomjid) {
|
||||||
|
@ -841,5 +1147,141 @@ var VideoLayout = (function (my) {
|
||||||
VideoLayout.showVideoIndicator(videoSpanId, isMuted);
|
VideoLayout.showVideoIndicator(videoSpanId, isMuted);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display name changed.
|
||||||
|
*/
|
||||||
|
$(document).bind('displaynamechanged',
|
||||||
|
function (event, jid, displayName, status) {
|
||||||
|
if (jid === 'localVideoContainer'
|
||||||
|
|| jid === connection.emuc.myroomjid) {
|
||||||
|
setDisplayName('localVideoContainer',
|
||||||
|
displayName);
|
||||||
|
} else {
|
||||||
|
VideoLayout.ensurePeerContainerExists(jid);
|
||||||
|
|
||||||
|
setDisplayName(
|
||||||
|
'participant_' + Strophe.getResourceFromJid(jid),
|
||||||
|
displayName,
|
||||||
|
status);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On dominant speaker changed event.
|
||||||
|
*/
|
||||||
|
$(document).bind('dominantspeakerchanged', function (event, resourceJid) {
|
||||||
|
// We ignore local user events.
|
||||||
|
if (resourceJid
|
||||||
|
=== Strophe.getResourceFromJid(connection.emuc.myroomjid))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Update the current dominant speaker.
|
||||||
|
if (resourceJid !== currentDominantSpeaker)
|
||||||
|
currentDominantSpeaker = resourceJid;
|
||||||
|
else
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Obtain container for new dominant speaker.
|
||||||
|
var container = document.getElementById(
|
||||||
|
'participant_' + resourceJid);
|
||||||
|
|
||||||
|
// Local video will not have container found, but that's ok
|
||||||
|
// since we don't want to switch to local video.
|
||||||
|
if (container && !focusedVideoSrc)
|
||||||
|
{
|
||||||
|
var video = container.getElementsByTagName("video");
|
||||||
|
|
||||||
|
// Update the large video if the video source is already available,
|
||||||
|
// otherwise wait for the "videoactive.jingle" event.
|
||||||
|
if (video.length && video[0].currentTime > 0)
|
||||||
|
VideoLayout.updateLargeVideo(video[0].src);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On last N change event.
|
||||||
|
*
|
||||||
|
* @param event the event that notified us
|
||||||
|
* @param lastNEndpoints the list of last N endpoints
|
||||||
|
* @param endpointsEnteringLastN the list currently entering last N
|
||||||
|
* endpoints
|
||||||
|
*/
|
||||||
|
$(document).bind('lastnchanged', function ( event,
|
||||||
|
lastNEndpoints,
|
||||||
|
endpointsEnteringLastN,
|
||||||
|
stream) {
|
||||||
|
if (lastNCount !== lastNEndpoints.length)
|
||||||
|
lastNCount = lastNEndpoints.length;
|
||||||
|
|
||||||
|
lastNEndpointsCache = lastNEndpoints;
|
||||||
|
|
||||||
|
$('#remoteVideos>span').each(function( index, element ) {
|
||||||
|
var resourceJid = VideoLayout.getPeerContainerResourceJid(element);
|
||||||
|
|
||||||
|
if (resourceJid
|
||||||
|
&& lastNEndpoints.length > 0
|
||||||
|
&& lastNEndpoints.indexOf(resourceJid) < 0) {
|
||||||
|
console.log("Remove from last N", resourceJid);
|
||||||
|
showPeerContainer(resourceJid, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!endpointsEnteringLastN || endpointsEnteringLastN.length < 0)
|
||||||
|
endpointsEnteringLastN = lastNEndpoints;
|
||||||
|
|
||||||
|
if (endpointsEnteringLastN && endpointsEnteringLastN.length > 0) {
|
||||||
|
endpointsEnteringLastN.forEach(function (resourceJid) {
|
||||||
|
|
||||||
|
if (!$('#participant_' + resourceJid).is(':visible')) {
|
||||||
|
console.log("Add to last N", resourceJid);
|
||||||
|
showPeerContainer(resourceJid, true);
|
||||||
|
|
||||||
|
mediaStreams.some(function (mediaStream) {
|
||||||
|
if (mediaStream.peerjid
|
||||||
|
&& Strophe.getResourceFromJid(mediaStream.peerjid)
|
||||||
|
=== resourceJid
|
||||||
|
&& mediaStream.type === mediaStream.VIDEO_TYPE) {
|
||||||
|
var sel = $('#participant_' + resourceJid + '>video');
|
||||||
|
|
||||||
|
RTC.attachMediaStream(sel, mediaStream.stream);
|
||||||
|
waitForRemoteVideo(
|
||||||
|
sel,
|
||||||
|
mediaStream.ssrc,
|
||||||
|
mediaStream.stream);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).bind('videoactive.jingle', function (event, videoelem) {
|
||||||
|
if (videoelem.attr('id').indexOf('mixedmslabel') === -1) {
|
||||||
|
// ignore mixedmslabela0 and v0
|
||||||
|
|
||||||
|
videoelem.show();
|
||||||
|
VideoLayout.resizeThumbnails();
|
||||||
|
|
||||||
|
var videoParent = videoelem.parent();
|
||||||
|
var parentResourceJid = null;
|
||||||
|
if (videoParent)
|
||||||
|
parentResourceJid
|
||||||
|
= VideoLayout.getPeerContainerResourceJid(videoParent[0]);
|
||||||
|
|
||||||
|
// Update the large video to the last added video only if there's no
|
||||||
|
// current dominant or focused speaker or update it to the current
|
||||||
|
// dominant speaker.
|
||||||
|
if ((!focusedVideoSrc && !VideoLayout.getDominantSpeakerResourceJid())
|
||||||
|
|| (parentResourceJid
|
||||||
|
&& VideoLayout.getDominantSpeakerResourceJid()
|
||||||
|
=== parentResourceJid)) {
|
||||||
|
VideoLayout.updateLargeVideo(videoelem.attr('src'), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
VideoLayout.showFocusIndicator();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return my;
|
return my;
|
||||||
}(VideoLayout || {}));
|
}(VideoLayout || {}));
|
||||||
|
|