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 @@ ...@@ -1604,6 +1604,7 @@
<include name="database/**/*.sql"/> <include name="database/**/*.sql"/>
<include name="i18n/*.properties"/> <include name="i18n/*.properties"/>
<include name="web/**/*.*"/> <include name="web/**/*.*"/>
<include name="**/*.*"/>
<exclude name="web/WEB-INF/web.xml"/> <exclude name="web/WEB-INF/web.xml"/>
<exclude name="web/**/*.jsp"/> <exclude name="web/**/*.jsp"/>
<exclude name="web/**/*.jspf"/> <exclude name="web/**/*.jspf"/>
......
...@@ -44,10 +44,10 @@ ...@@ -44,10 +44,10 @@
Jitsi Video Bridge Plugin Changelog Jitsi Video Bridge Plugin Changelog
</h1> </h1>
<p><b>1.0</b> -- Nov 30th, 2013</p> <p><b>1.1</b> -- Dec 4th, 2013</p>
<ul> <ul>
<li>OF-716 Added to Openfire plugins </li> <li>OF-716 Added to Openfire plugins with webrtc demo video application</li>
</ul> </ul>
<p><b>1.0</b> -- Apr 12, 2013</p> <p><b>1.0</b> -- Apr 12, 2013</p>
......
...@@ -4,9 +4,9 @@ ...@@ -4,9 +4,9 @@
<class>org.jitsi.videobridge.openfire.PluginImpl</class> <class>org.jitsi.videobridge.openfire.PluginImpl</class>
<description>Integrates Jitsi Video Bridge into Openfire.</description> <description>Integrates Jitsi Video Bridge into Openfire.</description>
<licenseType>other</licenseType> <licenseType>other</licenseType>
<minServerVersion>3.0.0</minServerVersion> <minServerVersion>3.9.0</minServerVersion>
<name>Jitsi Video Bridge</name> <name>Jitsi Video Bridge</name>
<version>1.0</version> <version>1.1</version>
<adminconsole> <adminconsole>
<tab id="tab-server"> <tab id="tab-server">
......
...@@ -63,6 +63,7 @@ Jitsi Videobridge does not mix the video channels into a composite video stream, ...@@ -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. 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, Therefore, while it does need to run on a server with good network bandwidth,
CPU horsepower is not that critical for performance. CPU horsepower is not that critical for performance.
A demo video conference application using WebRTC is included.
</p> </p>
...@@ -76,5 +77,9 @@ plugin will then be automatically deployed. To upgrade to a new version, copy th ...@@ -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. 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> </body>
</html> </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.*; ...@@ -17,10 +17,17 @@ import org.jitsi.util.*;
import org.jitsi.videobridge.*; import org.jitsi.videobridge.*;
import org.jivesoftware.openfire.container.*; import org.jivesoftware.openfire.container.*;
import org.jivesoftware.util.*; import org.jivesoftware.util.*;
import org.jivesoftware.openfire.http.HttpBindManager;
import org.slf4j.*; import org.slf4j.*;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.xmpp.component.*; 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 * Implements <tt>org.jivesoftware.openfire.container.Plugin</tt> to integrate
* Jitsi Video Bridge into Openfire. * Jitsi Video Bridge into Openfire.
...@@ -37,6 +44,12 @@ public class PluginImpl ...@@ -37,6 +44,12 @@ public class PluginImpl
*/ */
private static final Logger Log = LoggerFactory.getLogger(PluginImpl.class); 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 * The name of the property that contains the maximum port number that we'd
* like our RTP managers to bind upon. * like our RTP managers to bind upon.
...@@ -122,6 +135,22 @@ public class PluginImpl ...@@ -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_LOCATION", pluginDirectory.getPath());
System.setProperty("net.java.sip.communicator.SC_HOME_DIR_NAME", "."); 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 // Let's check for custom configuration
String maxVal = JiveGlobals.getProperty(MAX_PORT_NUMBER_PROPERTY_NAME); String maxVal = JiveGlobals.getProperty(MAX_PORT_NUMBER_PROPERTY_NAME);
String minVal = JiveGlobals.getProperty(MIN_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