Commit 8bc04cbc authored by Dele Olajide's avatar Dele Olajide Committed by dele

OF-716 Modified build.xml to support plugins with embedded web apps

OF-716 Added Jitsi video cpnference web application to plugin


git-svn-id: http://svn.igniterealtime.org/svn/repos/openfire/trunk@13822 b35dd754-fafc-0310-a699-88a17e54d16e
parent c5d271b9
......@@ -1604,6 +1604,7 @@
<include name="database/**/*.sql"/>
<include name="i18n/*.properties"/>
<include name="web/**/*.*"/>
<include name="**/*.*"/>
<exclude name="web/WEB-INF/web.xml"/>
<exclude name="web/**/*.jsp"/>
<exclude name="web/**/*.jspf"/>
......
......@@ -44,10 +44,10 @@
Jitsi Video Bridge Plugin Changelog
</h1>
<p><b>1.0</b> -- Nov 30th, 2013</p>
<p><b>1.1</b> -- Dec 4th, 2013</p>
<ul>
<li>OF-716 Added to Openfire plugins </li>
<li>OF-716 Added to Openfire plugins with webrtc demo video application</li>
</ul>
<p><b>1.0</b> -- Apr 12, 2013</p>
......
......@@ -4,9 +4,9 @@
<class>org.jitsi.videobridge.openfire.PluginImpl</class>
<description>Integrates Jitsi Video Bridge into Openfire.</description>
<licenseType>other</licenseType>
<minServerVersion>3.0.0</minServerVersion>
<minServerVersion>3.9.0</minServerVersion>
<name>Jitsi Video Bridge</name>
<version>1.0</version>
<version>1.1</version>
<adminconsole>
<tab id="tab-server">
......
......@@ -63,6 +63,7 @@ Jitsi Videobridge does not mix the video channels into a composite video stream,
but only relays the received video channels to all call participants.
Therefore, while it does need to run on a server with good network bandwidth,
CPU horsepower is not that critical for performance.
A demo video conference application using WebRTC is included.
</p>
......@@ -76,5 +77,9 @@ plugin will then be automatically deployed. To upgrade to a new version, copy th
Under Server settings -> Jitsi Videobridge tab you can configure it.
<h2>How to use</h2>
To run the demo video conference application, point your browser at https://your_server:7443/videobridge
</body>
</html>
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
</web-app>
\ No newline at end of file
This diff is collapsed.
<html>
<head>
<title>Jitsi Videobridge</title>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="strophejingle.bundle.js"></script>
<script src="colibri.js"></script>
<script src="videobridge.js"></script>
<script src="muc.js"></script>
<link href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet">
<style type='text/css'>
html,body{margin:0px;}
#largeVideo {width:1280px;height:720px;margin-left:auto;margin-right:auto;display:block}
#localVideo {-webkit-transform: scale(-1,1);}
#remoteVideos {text-align:center;height:180px;}
#remoteVideos video {width:320;height:180;}
#spacer {height:40px;}
#settings {display:none;}
#header {text-align:center;visibility:hidden;}
#nowebrtc {display:none;}
</style>
</head>
<body>
<div id="header" onclick='window.prompt("Share this link with anyone you want to invite to join you:", window.location.href);return;'>
<i class='fa fa-external-link'>&nbsp;</i>Others can join you by just going to <span id='roomurl'></span>
</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>
<input id="connect" type="submit" value="Connect" />
</form>
</div>
<video id="largeVideo" autoplay oncontextmenu="return false;"></video>
<div id="spacer"></div>
<div id="remoteVideos">
<video id="localVideo" autoplay oncontextmenu="return false;" muted/>
</div>
<script>
var connection = null;
var master = null;
var RTC = setupRTC();
if (RTC == null) {
alert('Sorry, your browser is not WebRTC enabled!'); // BAO
window.location.href = 'about:blank';
} else if (RTC.browser != 'chrome') {
alert('Sorry, only Chrome supported for now!'); // BAO
window.location.href = 'about:blank';
}
var RTCPeerconnection = RTC.peerconnection;
document.getElementById('jid').value = window.location.hostname;
var connType = "BOSH"; // change this line to WEBSOCKETS for Openfire websockets
if (connType == "WEBSOCKETS")
connection = new Openfire.Connection(window.location.protocol + '//' + window.location.host + '/http-bind/'); // BAO
else
connection = new Strophe.Connection(window.location.protocol + '//' + window.location.host + '/http-bind/'); // BAO
window.connection.resource = Math.random().toString(36).substr(2, 20);
connection.rawInput = function (data) { console.log('RECV: ' + data); };
connection.rawOutput = function (data) { console.log('SEND: ' + data); };
connection.jingle.pc_constraints = RTC.pc_constraints;
var jid = document.getElementById('jid').value || window.location.hostname;
connection.connect(jid, document.getElementById('password').value, function(status) {
if (status == Strophe.Status.CONNECTED) {
console.log('connected');
connection.send($pres()); // BAO
getUserMediaWithConstraints(['audio','video'], '360');
document.getElementById('connect').disabled = true;
} else {
console.log('status', status);
}
});
$(document).bind('mediaready.jingle', function(event, stream) {
connection.emuc.doJoin();
connection.jingle.localStream = stream;
RTC.attachMediaStream($('#localVideo'), stream);
document.getElementById('localVideo').muted = true;
document.getElementById('localVideo').autoplay = true;
document.getElementById('localVideo').volume = 0;
document.getElementById('largeVideo').volume = 0;
document.getElementById('largeVideo').src = document.getElementById('localVideo').src;
});
//$(document).bind('mediafailure.jingle', onMediaFailure);
$(document).bind('remotestreamadded.jingle', function(event, data, sid) {
// TODO: when adding a video, make sure they fit in a single row
function waitForRemoteVideo(selector, sid) {
var sess = connection.jingle.sessions[sid];
videoTracks = data.stream.getVideoTracks();
if (videoTracks.length === 0 || selector[0].currentTime > 0) {
RTC.attachMediaStream(selector, data.stream); // FIXME: why do i have to do this for FF?
$(document).trigger('callactive.jingle', [selector, sid]);
console.log('waitForremotevideo', sess.peerconnection.iceConnectionState, sess.peerconnection.signalingState);
} else {
setTimeout(function() { waitForRemoteVideo(selector, sid); }, 100);
}
}
var sess = connection.jingle.sessions[sid];
var vid = document.createElement('video');
var id = 'remoteVideo_' + sid + '_' + data.stream.id;
vid.id = id;
vid.autoplay = true;
vid.oncontextmenu = function() { return false; };
var remotes = document.getElementById('remoteVideos');
remotes.appendChild(vid);
var sel = $('#' + id);
sel.hide();
RTC.attachMediaStream(sel, data.stream);
waitForRemoteVideo(sel, sid);
data.stream.onended = function() {
console.log('stream ended', this.id);
var src = $('#' + id).attr('src');
$('#' + id).remove();
if (src === $('#largeVideo').attr('src')) {
// this is currently displayed as large
// pick the last visible video in the row
// ... well, if nobody else is left, this picks the local video
var pick = $('#remoteVideos :visible:last').get(0);
// mute if localvideo
document.getElementById('largeVideo').volume = pick.volume;
document.getElementById('largeVideo').src = pick.src;
}
}
// FIXME: hover is bad, this causes flicker. How about moving this?
// remember that moving this in the DOM requires to play() again
sel.hover(
function() {
console.log('hover in', $(this).attr('src'));
if ($('#largeVideo').attr('src') != $(this).attr('src')) {
document.getElementById('largeVideo').volume = 1;
$('#largeVideo').attr('src', $(this).attr('src'));
}
},
function() {
//console.log('hover out', $(this).attr('src'));
//$('#largeVideo').attr('src', null);
}
);
});
$(document).bind('callincoming.jingle', function(event, sid) {
var sess = connection.jingle.sessions[sid];
sess.sendAnswer();
sess.accept();
});
$(document).bind('callactive.jingle', function(event, videoelem, sid) {
console.log('call active');
if (videoelem.attr('id').indexOf('mixedmslabel') == -1) {
// ignore mixedmslabela0 and v0
videoelem.show();
document.getElementById('largeVideo').volume = 1;
$('#largeVideo').attr('src', videoelem.attr('src'));
}
});
$(document).bind('callterminated.jingle', function(event, sid, reason) {
// FIXME
});
function resizeLarge() {
var availableHeight = window.innerHeight;
availableHeight -= $('#remoteVideos').height();
availableHeight -= 100; // padding + link ontop
var availableWidth = window.innerWidth;
var aspectRatio = 16.0 / 9.0;
if (availableHeight < availableWidth / aspectRatio) {
availableWidth = availableHeight * aspectRatio;
}
if (availableWidth < 0 || availableHeight < 0) return;
$('#largeVideo').width(availableWidth);
$('#largeVideo').height(availableWidth/aspectRatio);
}
$(document).ready(function() {
resizeLarge();
$(window).resize(function() {
resizeLarge();
});
});
</script>
</body>
</html>
......@@ -17,10 +17,17 @@ import org.jitsi.util.*;
import org.jitsi.videobridge.*;
import org.jivesoftware.openfire.container.*;
import org.jivesoftware.util.*;
import org.jivesoftware.openfire.http.HttpBindManager;
import org.slf4j.*;
import org.slf4j.Logger;
import org.xmpp.component.*;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.webapp.WebAppContext;
/**
* Implements <tt>org.jivesoftware.openfire.container.Plugin</tt> to integrate
* Jitsi Video Bridge into Openfire.
......@@ -37,6 +44,12 @@ public class PluginImpl
*/
private static final Logger Log = LoggerFactory.getLogger(PluginImpl.class);
/**
* The name of the property that contains the name of video conference application
*/
public static final String VIDEO_CONFERENCE_PROPERTY_NAME
= "org.jitsi.videobridge.video.conference.name";
/**
* The name of the property that contains the maximum port number that we'd
* like our RTP managers to bind upon.
......@@ -122,6 +135,22 @@ public class PluginImpl
System.setProperty("net.java.sip.communicator.SC_HOME_DIR_LOCATION", pluginDirectory.getPath());
System.setProperty("net.java.sip.communicator.SC_HOME_DIR_NAME", ".");
// start video conference web application
try {
String appName = JiveGlobals.getProperty(VIDEO_CONFERENCE_PROPERTY_NAME, "videobridge");
Log.info("Initialize Web App " + appName);
ContextHandlerCollection contexts = HttpBindManager.getInstance().getContexts();
WebAppContext context = new WebAppContext(contexts, pluginDirectory.getPath(), "/" + appName);
context.setWelcomeFiles(new String[]{"index.html"});
}
catch(Exception e) {
Log.error( "Jitsi Videobridge web app initialize error", e);
}
// Let's check for custom configuration
String maxVal = JiveGlobals.getProperty(MAX_PORT_NUMBER_PROPERTY_NAME);
String minVal = JiveGlobals.getProperty(MIN_PORT_NUMBER_PROPERTY_NAME);
......
var CONFERENCEDOMAIN = 'conference.' + window.location.hostname; // BAO
Strophe.addConnectionPlugin('emuc', {
connection: null,
roomjid: null,
myroomjid: null,
list_members: [],
isOwner: false,
init: function (conn) {
this.connection = conn;
},
doJoin: function () {
var roomnode = urlParam("r"); // BAO
console.log("roomnode = " + roomnode);
if (!roomnode) {
roomnode = Math.random().toString(36).substr(2, 20);
window.history.pushState('VideoChat', 'Room: ' + roomnode, window.location.pathname + "?r=" + roomnode);
}
if (this.roomjid == null) {
this.roomjid = roomnode + '@' + CONFERENCEDOMAIN;
}
this.myroomjid = this.roomjid + '/' + Strophe.getNodeFromJid(this.connection.jid);
console.log('joining', this.roomjid);
// muc stuff
this.connection.addHandler(this.onPresence.bind(this), null, 'presence', null, null, this.roomjid, {matchBare: true});
this.connection.addHandler(this.onPresenceUnavailable.bind(this), null, 'presence', 'unavailable', null, this.roomjid, {matchBare: true});
this.connection.addHandler(this.onPresenceError.bind(this), null, 'presence', 'error', null, this.roomjid, {matchBare: true});
this.connection.send($pres({to: this.myroomjid }).c('x', {xmlns: 'http://jabber.org/protocol/muc'}));
},
onPresence: function (pres) {
var from = pres.getAttribute('from'),
type = pres.getAttribute('type');
if (type != null) {
return true;
}
if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="201"]').length) {
// http://xmpp.org/extensions/xep-0045.html#createroom-instant
this.isOwner = true;
var create = $iq({type: 'set', to: this.roomjid})
.c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'})
.c('x', {xmlns: 'jabber:x:data', type: 'submit'});
this.connection.send(create); // fire away
}
if (from == this.myroomjid) {
this.onJoinComplete();
} else if (this.list_members.indexOf(from) == -1) {
// new participant
this.list_members.push(from);
// FIXME: belongs into an event so we can separate emuc and colibri
if (master !== null) {
// FIXME: this should prepare the video
if (master.peers.length == 0) {
console.log('make new conference with', from);
master.makeConference(this.list_members);
} else {
console.log('invite', from, 'into conference');
master.addNewParticipant(from);
}
}
} else {
console.log('presence change from', from);
}
return true;
},
onPresenceUnavailable: function (pres) {
// FIXME: first part doesn't belong into EMUC
// FIXME: this should actually hide the video already for a nicer UX
this.connection.jingle.terminateByJid($(pres).attr('from'));
/*
if (Object.keys(this.connection.jingle.sessions).length == 0) {
console.log('everyone left');
}
*/
for (var i = 0; i < this.list_members.length; i++) {
if (this.list_members[i] == $(pres).attr('from')) {
this.list_members.splice(i, 1);
break;
}
}
if (this.list_members.length == 0) {
console.log('everyone left');
}
return true;
},
onPresenceError: function(pres) {
var ob = this;
if ($(pres).find('>error[type="auth"]>not-authorized[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) {
window.setTimeout(function() {
var given = window.prompt('Password required');
if (given != null) {
ob.connection.send($pres({to: ob.myroomjid }).c('x', {xmlns: 'http://jabber.org/protocol/muc'}).c('password').t(given));
} else {
// user aborted
}
}, 50);
} else {
console.warn('onPresError ', pres);
}
return true;
},
onJoinComplete: function() {
console.log('onJoinComplete');
$('#roomurl').text(window.location.href);
$('#header').css('visibility', 'visible');
if (this.list_members.length < 1) {
// FIXME: belongs into an event so we can separate emuc and colibri
master = new Colibri(connection, bridgejid);
return;
}
},
lockRoom: function(key) {
//http://xmpp.org/extensions/xep-0045.html#roomconfig
var ob = this;
this.connection.sendIQ($iq({to:this.roomjid, type:'get'}).c('query', {xmlns:'http://jabber.org/protocol/muc#owner'}),
function(res) {
if ($(res).find('>query>x[xmlns="jabber:x:data"]>field[var="muc#roomconfig_roomsecret"]').length) {
var formsubmit = $iq({to:ob.roomjid, type:'set'}).c('query', {xmlns:'http://jabber.org/protocol/muc#owner'});
formsubmit.c('x', {xmlns: 'jabber:x:data', type: 'submit'});
formsubmit.c('field', {'var': 'FORM_TYPE'}).c('value').t('http://jabber.org/protocol/muc#roomconfig').up().up();
formsubmit.c('field', {'var': 'muc#roomconfig_roomsecret'}).c('value').t(key).up().up();
// FIXME: is muc#roomconfig_passwordprotectedroom required?
this.connection.sendIQ(formsubmit,
function(res) {
console.log('set room password');
},
function(err) {
console.warn('setting password failed', err);
}
);
} else {
console.warn('room passwords not supported');
}
},
function(err) {
console.warn('setting password failed', err);
}
);
}
});
$(window).bind('beforeunload', function() {
if (connection && connection.connected) {
// ensure signout
$.ajax({
type: 'POST',
url: '/http-bind',
async: false,
cache: false,
contentType: 'application/xml',
data: "<body rid='" + connection.rid + "' xmlns='http://jabber.org/protocol/httpbind' sid='" + connection.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 + ')');
}
});
}
})
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment