Commit b992ee1f authored by Dele Olajide's avatar Dele Olajide Committed by dele

Jitsi Videobridge - Further work on ver 1.3.0

git-svn-id: http://svn.igniterealtime.org/svn/repos/openfire/trunk@13991 b35dd754-fafc-0310-a699-88a17e54d16e
parent a1c233d8
......@@ -9,6 +9,10 @@ var config = {
// useIPv6: true, // ipv6 support. use at your own risk
useNicks: false,
bosh: 'https://btg199251:7443/http-bind/', // FIXME: use xep-0156 for that
desktopSharing: 'webrtc', // Desktop sharing method. Can be set to 'ext', 'webrtc' or false to disable.
chromeExtensionId: 'diibjkoicjeejcmhdnailmkgecihlobk', // Id of desktop streamer Chrome extension
minChromeExtVersion: '0.0.8', // Required version of Chrome extension
getroomnode: function (path)
{
......
......@@ -49,7 +49,7 @@ html, body{
font-size: 10pt;
}
#localVideo {
.flipVideoX {
-moz-transform: scaleX(-1);
-webkit-transform: scaleX(-1);
-o-transform: scaleX(-1);
......
/**
* Indicates that desktop stream is currently in use(for toggle purpose).
* @type {boolean}
*/
var isUsingScreenStream = false;
/**
* Indicates that switch stream operation is in progress and prevent from triggering new events.
* @type {boolean}
*/
var switchInProgress = false;
/**
* Method used to get screen sharing stream.
*
* @type {function(stream_callback, failure_callback}
*/
var obtainDesktopStream = null;
/**
* @returns {boolean} <tt>true</tt> if desktop sharing feature is available and enabled.
*/
function isDesktopSharingEnabled() {
if(obtainDesktopStream === obtainScreenFromExtension) {
// Parse chrome version
var userAgent = navigator.userAgent.toLowerCase();
// We can assume that user agent is chrome, because it's enforced when 'ext' streaming method is set
var ver = parseInt(userAgent.match(/chrome\/(\d+)\./)[1], 10);
console.log("Chrome version" + userAgent, ver);
return ver >= 35;
} else {
return obtainDesktopStream === obtainWebRTCScreen;
}
}
/**
* Call this method to toggle desktop sharing feature.
* @param method pass "ext" to use chrome extension for desktop capture(chrome extension required),
* pass "webrtc" to use WebRTC "screen" desktop source('chrome://flags/#enable-usermedia-screen-capture'
* must be enabled), pass any other string or nothing in order to disable this feature completely.
*/
function setDesktopSharing(method) {
if(method == "ext") {
if(RTC.browser === 'chrome') {
obtainDesktopStream = obtainScreenFromExtension;
console.info("Using Chrome extension for desktop sharing");
} else {
console.error("Chrome is required to use extension method");
obtainDesktopStream = null;
}
} else if(method == "webrtc") {
obtainDesktopStream = obtainWebRTCScreen;
console.info("Using WebRTC for desktop sharing");
} else {
obtainDesktopStream = null;
console.info("Desktop sharing disabled");
}
showDesktopSharingButton();
}
function showDesktopSharingButton() {
if(isDesktopSharingEnabled()) {
$('#desktopsharing').css( {display:"inline"} );
} else {
$('#desktopsharing').css( {display:"none"} );
}
}
/*
* Toggles screen sharing.
*/
function toggleScreenSharing() {
if (switchInProgress || !obtainDesktopStream) {
console.warn("Switch in progress or no method defined");
return;
}
switchInProgress = true;
// Only the focus is able to set a shared key.
if(!isUsingScreenStream)
{
obtainDesktopStream(
function(stream) {
// We now use screen stream
isUsingScreenStream = true;
// Hook 'ended' event to restore camera when screen stream stops
stream.addEventListener('ended',
function(e) {
if(!switchInProgress && isUsingScreenStream) {
toggleScreenSharing();
}
}
);
newStreamCreated(stream);
},
getSwitchStreamFailed );
} else {
// Disable screen stream
getUserMediaWithConstraints(
['video'],
function(stream) {
// We are now using camera stream
isUsingScreenStream = false;
newStreamCreated(stream);
},
getSwitchStreamFailed, config.resolution || '360'
);
}
}
function getSwitchStreamFailed(error) {
console.error("Failed to obtain the stream to switch to", error);
switchInProgress = false;
}
function newStreamCreated(stream) {
var oldStream = connection.jingle.localVideo;
change_local_video(stream, !isUsingScreenStream);
var conferenceHandler = getConferenceHandler();
if(conferenceHandler) {
// FIXME: will block switchInProgress on true value in case of exception
conferenceHandler.switchStreams(stream, oldStream, streamSwitchDone);
} else {
// We are done immediately
console.error("No conference handler");
streamSwitchDone();
}
}
function streamSwitchDone() {
//window.setTimeout(
// function() {
switchInProgress = false;
// }, 100
//);
}
/**
* Method obtains desktop stream from WebRTC 'screen' source.
* Flag 'chrome://flags/#enable-usermedia-screen-capture' must be enabled.
*/
function obtainWebRTCScreen(streamCallback, failCallback) {
getUserMediaWithConstraints(
['screen'],
streamCallback,
failCallback
);
}
/**
* Asks Chrome extension to call chooseDesktopMedia and gets chrome 'desktop' stream for returned stream token.
*/
function obtainScreenFromExtension(streamCallback, failCallback) {
checkExtInstalled(
function(isInstalled) {
if(isInstalled) {
doGetStreamFromExtension(streamCallback, failCallback);
} else {
chrome.webstore.install(
"https://chrome.google.com/webstore/detail/" + config.chromeExtensionId,
function(arg) {
console.log("Extension installed successfully", arg);
// We need to reload the page in order to get the access to chrome.runtime
window.location.reload(false);
},
function(arg) {
console.log("Failed to install the extension", arg);
failCallback(arg);
}
);
}
}
);
}
function checkExtInstalled(isInstalledCallback) {
if(!chrome.runtime) {
// No API, so no extension for sure
isInstalledCallback(false);
return false;
}
chrome.runtime.sendMessage(
config.chromeExtensionId,
{ getVersion: true },
function(response){
if(!response || !response.version) {
// Communication failure - assume that no endpoint exists
console.warn("Extension not installed?: "+chrome.runtime.lastError);
isInstalledCallback(false);
} else {
// Check installed extension version
var extVersion = response.version;
console.log('Extension version is: '+extVersion);
var updateRequired = extVersion < config.minChromeExtVersion;
if(updateRequired) {
alert(
'Jitsi Desktop Streamer requires update. ' +
'Changes will take effect after next Chrome restart.' );
}
isInstalledCallback(!updateRequired);
}
}
);
}
function doGetStreamFromExtension(streamCallback, failCallback) {
// Sends 'getStream' msg to the extension. Extension id must be defined in the config.
chrome.runtime.sendMessage(
config.chromeExtensionId,
{ getStream: true},
function(response) {
if(!response) {
failCallback(chrome.runtime.lastError);
return;
}
console.log("Response from extension: "+response);
if(response.streamId) {
getUserMediaWithConstraints(
['desktop'],
function(stream) {
streamCallback(stream);
},
failCallback,
null, null, null,
response.streamId);
} else {
failCallback("Extension failed to get the stream");
}
}
);
}
......@@ -6,12 +6,16 @@
<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.sdp.js?v=1"></script>
<script src="libs/strophe/strophe.jingle.sdp.util.js?v=1"></script>
<script src="libs/strophe/strophe.jingle.sessionbase.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.session.js?v=1"></script>
<script src="//code.jquery.com/ui/1.10.4/jquery-ui.js"></script>
<script src="config.js"></script><!-- adapt to your needs, i.e. set hosts and bosh path -->
<script src="muc.js?v=9"></script><!-- simple MUC library -->
<script src="estos_log.js?v=2"></script><!-- simple stanza logger -->
<script src="desktopsharing.js?v=1"></script><!-- desktop sharing -->
<script src="app.js?v=23"></script><!-- application logic -->
<script src="chat.js?v=3"></script><!-- chat logic -->
<script src="util.js?v=2"></script><!-- utility functions -->
......@@ -22,9 +26,12 @@
<link rel="stylesheet" type="text/css" media="screen" href="css/main.css?v=19"/>
<link rel="stylesheet" href="css/jquery-impromptu.css?v=4">
<link rel="stylesheet" href="css/modaldialog.css?v=3">
<!--
Link used for inline installation of chrome desktop streaming extension,
must contain the same extension id as defined in config.js -->
<link rel="chrome-webstore-item" href="https://chrome.google.com/webstore/detail/diibjkoicjeejcmhdnailmkgecihlobk">
<script src="libs/jquery-impromptu.js"></script>
<script src="libs/jquery.autosize.js"></script>
<script src="config.js"></script><!-- adapt to your needs, i.e. set hosts and bosh path -->
<script src="libs/prezi_player.js?v=2"></script>
</head>
<body>
......@@ -35,7 +42,7 @@
<a class="button" onclick='buttonClick("#mute", "fa fa-microphone fa-lg fa fa-microphone-slash fa-lg");toggleAudio();'>
<i id="mute" title="Mute / unmute" class="fa fa-microphone fa-lg"></i></a>
<div class="header_button_separator"></div>
<a class="button" onclick='buttonClick("#video", "fa fa-video-camera fa-lg fa fa-video-camera no-fa-video-camera fa-lg");toggleVideo();'>
<a class="button" onclick='buttonClick("#video");toggleVideo();'>
<i id="video" title="Start / stop camera" class="fa fa-video-camera fa-lg"></i></a>
<div class="header_button_separator"></div>
<a class="button" onclick="openLockDialog();"><i id="lockIcon" title="Lock/unlock room" class="fa fa-unlock fa-lg"></i></a>
......@@ -53,6 +60,10 @@
<a class="button" onclick='Etherpad.toggleEtherpad(0);'><i title="Open shared document" class="fa fa-file-text fa-lg"></i></a>
</span>
<div class="header_button_separator"></div>
<span id="desktopsharing" style="display: none">
<a class="button" onclick="toggleScreenSharing();"><i title="Share screen" class="fa fa-desktop fa-lg"></i></a>
<div class="header_button_separator"></div>
</span>
<a class="button" onclick='toggleFullScreen();'><i title="Enter / Exit Full Screen" class="fa fa-arrows-alt fa-lg"></i></a>
</span>
</div>
......@@ -78,7 +89,8 @@
<div id="remoteVideos">
<span id="localVideoContainer" class="videocontainer">
<span id="localNick"></span>
<video id="localVideo" autoplay oncontextmenu="return false;" muted></video>
<!--<video id="localVideo" autoplay oncontextmenu="return false;" muted></video> - is now per stream generated -->
<audio id="localAudio" autoplay oncontextmenu="return false;" muted></audio>
<span class="focusindicator"></span>
</span>
<audio id="userJoined" src="sounds/joined.wav" preload="auto"></audio>
......@@ -99,6 +111,7 @@
<textarea id="usermsg" placeholder='Enter text...' autofocus></textarea>
</div>
<a id="downloadlog" onclick='dump(event.target);'><i title="Download support information" class="fa fa-cloud-download"></i></a>
<!-- Google Analytics -->
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
......@@ -106,5 +119,6 @@
ga('create', 'UA-319188-14', 'jit.si');
ga('send', 'pageview');
</script>
<!-- End Google Analytics -->
</body>
</html>
......@@ -61,6 +61,14 @@ ColibriSession.prototype.accept = function () {
console.log('ColibriSession.accept');
};
ColibriSession.prototype.addSource = function (elem, fromJid) {
this.colibri.addSource(elem, fromJid);
};
ColibriSession.prototype.removeSource = function (elem, fromJid) {
this.colibri.removeSource(elem, fromJid);
};
ColibriSession.prototype.terminate = function (reason) {
this.colibri.terminate(this, reason);
};
......
......@@ -12,7 +12,8 @@ Strophe.addConnectionPlugin('jingle', {
}
// MozDontOfferDataChannel: true when this is firefox
},
localStream: null,
localAudio: null,
localVideo: null,
init: function (conn) {
this.connection = conn;
......@@ -38,12 +39,13 @@ Strophe.addConnectionPlugin('jingle', {
onJingle: function (iq) {
var sid = $(iq).find('jingle').attr('sid');
var action = $(iq).find('jingle').attr('action');
var fromJid = iq.getAttribute('from');
// send ack first
var ack = $iq({type: 'result',
to: iq.getAttribute('from'),
to: fromJid,
id: iq.getAttribute('id')
});
console.log('on jingle ' + action);
console.log('on jingle ' + action + ' from ' + fromJid, iq);
var sess = this.sessions[sid];
if ('session-initiate' != action) {
if (sess === null) {
......@@ -56,8 +58,8 @@ Strophe.addConnectionPlugin('jingle', {
}
// compare from to sess.peerjid (bare jid comparison for later compat with message-mode)
// local jid is not checked
if (Strophe.getBareJidFromJid(iq.getAttribute('from')) != Strophe.getBareJidFromJid(sess.peerjid)) {
console.warn('jid mismatch for session id', sid, iq.getAttribute('from'), sess.peerjid);
if (Strophe.getBareJidFromJid(fromJid) != Strophe.getBareJidFromJid(sess.peerjid)) {
console.warn('jid mismatch for session id', sid, fromJid, sess.peerjid);
ack.type = 'error';
ack.c('error', {type: 'cancel'})
.c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up()
......@@ -82,14 +84,17 @@ Strophe.addConnectionPlugin('jingle', {
case 'session-initiate':
sess = new JingleSession($(iq).attr('to'), $(iq).find('jingle').attr('sid'), this.connection);
// configure session
if (this.localStream) {
sess.localStreams.push(this.localStream);
if (this.localAudio) {
sess.localStreams.push(this.localAudio);
}
if (this.localVideo) {
sess.localStreams.push(this.localVideo);
}
sess.media_constraints = this.media_constraints;
sess.pc_constraints = this.pc_constraints;
sess.ice_config = this.ice_config;
sess.initiate($(iq).attr('from'), false);
sess.initiate(fromJid, false);
// FIXME: setRemoteDescription should only be done when this call is to be accepted
sess.setRemoteDescription($(iq).find('>jingle'), 'offer');
......@@ -136,10 +141,10 @@ Strophe.addConnectionPlugin('jingle', {
}
break;
case 'addsource': // FIXME: proprietary
sess.addSource($(iq).find('>jingle>content'));
sess.addSource($(iq).find('>jingle>content'), fromJid);
break;
case 'removesource': // FIXME: proprietary
sess.removeSource($(iq).find('>jingle>content'));
sess.removeSource($(iq).find('>jingle>content'), fromJid);
break;
default:
console.warn('jingle action not implemented', action);
......@@ -152,8 +157,11 @@ Strophe.addConnectionPlugin('jingle', {
Math.random().toString(36).substr(2, 12), // random string
this.connection);
// configure session
if (this.localStream) {
sess.localStreams.push(this.localStream);
if (this.localAudio) {
sess.localStreams.push(this.localAudio);
}
if (this.localVideo) {
sess.localStreams.push(this.localVideo);
}
sess.media_constraints = this.media_constraints;
sess.pc_constraints = this.pc_constraints;
......
/* jshint -W117 */
// Jingle stuff
JingleSession.prototype = Object.create(SessionBase.prototype);
function JingleSession(me, sid, connection) {
SessionBase.call(this, connection, sid);
this.me = me;
this.sid = sid;
this.connection = connection;
this.initiator = null;
this.responder = null;
this.isInitiator = null;
this.peerjid = null;
this.state = null;
this.peerconnection = null;
this.remoteStream = null;
this.localSDP = null;
this.remoteSDP = null;
this.localStreams = [];
......@@ -35,10 +35,6 @@ function JingleSession(me, sid, connection) {
this.reason = null;
this.addssrc = [];
this.removessrc = [];
this.pendingop = null;
this.wait = true;
}
......@@ -54,16 +50,6 @@ JingleSession.prototype.initiate = function (peerjid, isInitiator) {
this.initiator = isInitiator ? this.me : peerjid;
this.responder = !isInitiator ? this.me : peerjid;
this.peerjid = peerjid;
//console.log('create PeerConnection ' + JSON.stringify(this.ice_config));
try {
this.peerconnection = new RTCPeerconnection(this.ice_config,
this.pc_constraints);
} catch (e) {
console.error('Failed to create PeerConnection, exception: ',
e.message);
console.error(e);
return;
}
this.hadstuncandidate = false;
this.hadturncandidate = false;
this.lasticecandidate = false;
......@@ -71,13 +57,16 @@ JingleSession.prototype.initiate = function (peerjid, isInitiator) {
self.sendIceCandidate(event.candidate);
};
this.peerconnection.onaddstream = function (event) {
self.remoteStream = event.stream;
self.remoteStreams.push(event.stream);
$(document).trigger('remotestreamadded.jingle', [event, self.sid]);
};
this.peerconnection.onremovestream = function (event) {
self.remoteStream = null;
// FIXME: remove from this.remoteStreams
// Remove the stream from remoteStreams
var streamIdx = self.remoteStreams.indexOf(event.stream);
if(streamIdx !== -1){
self.remoteStreams.splice(streamIdx, 1);
}
// FIXME: remotestreamremoved.jingle not defined anywhere(unused)
$(document).trigger('remotestreamremoved.jingle', [event, self.sid]);
};
this.peerconnection.onsignalingstatechange = function (event) {
......@@ -165,6 +154,22 @@ JingleSession.prototype.accept = function () {
);
};
/**
* Implements SessionBase.sendSSRCUpdate.
*/
JingleSession.prototype.sendSSRCUpdate = function(sdpMediaSsrcs, fromJid, isadd) {
var self = this;
console.log('tell', self.peerjid, 'about ' + (isadd ? 'new' : 'removed') + ' ssrcs from' + self.me);
if (!(this.peerconnection.signalingState == 'stable' && this.peerconnection.iceConnectionState == 'connected')){
console.log("Too early to send updates");
return;
}
this.sendSSRCUpdateIq(sdpMediaSsrcs, self.sid, self.initiator, self.peerjid, isadd);
};
JingleSession.prototype.terminate = function (reason) {
this.state = 'ended';
this.reason = reason;
......@@ -633,158 +638,6 @@ JingleSession.prototype.sendTerminate = function (reason, text) {
}
};
JingleSession.prototype.addSource = function (elem) {
console.log('addssrc', new Date().getTime());
console.log('ice', this.peerconnection.iceConnectionState);
var sdp = new SDP(this.peerconnection.remoteDescription.sdp);
var self = this;
$(elem).each(function (idx, content) {
var name = $(content).attr('name');
var lines = '';
tmp = $(content).find('>source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]');
tmp.each(function () {
var ssrc = $(this).attr('ssrc');
$(this).find('>parameter').each(function () {
lines += 'a=ssrc:' + ssrc + ' ' + $(this).attr('name');
if ($(this).attr('value') && $(this).attr('value').length)
lines += ':' + $(this).attr('value');
lines += '\r\n';
});
});
sdp.media.forEach(function(media, idx) {
if (!SDPUtil.find_line(media, 'a=mid:' + name))
return;
sdp.media[idx] += lines;
if (!self.addssrc[idx]) self.addssrc[idx] = '';
self.addssrc[idx] += lines;
});
sdp.raw = sdp.session + sdp.media.join('');
});
this.modifySources();
};
JingleSession.prototype.removeSource = function (elem) {
console.log('removessrc', new Date().getTime());
console.log('ice', this.peerconnection.iceConnectionState);
var sdp = new SDP(this.peerconnection.remoteDescription.sdp);
var self = this;
$(elem).each(function (idx, content) {
var name = $(content).attr('name');
var lines = '';
tmp = $(content).find('>source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]');
tmp.each(function () {
var ssrc = $(this).attr('ssrc');
$(this).find('>parameter').each(function () {
lines += 'a=ssrc:' + ssrc + ' ' + $(this).attr('name');
if ($(this).attr('value') && $(this).attr('value').length)
lines += ':' + $(this).attr('value');
lines += '\r\n';
});
});
sdp.media.forEach(function(media, idx) {
if (!SDPUtil.find_line(media, 'a=mid:' + name))
return;
sdp.media[idx] += lines;
if (!self.removessrc[idx]) self.removessrc[idx] = '';
self.removessrc[idx] += lines;
});
sdp.raw = sdp.session + sdp.media.join('');
});
this.modifySources();
};
JingleSession.prototype.modifySources = function() {
var self = this;
if (this.peerconnection.signalingState == 'closed') return;
if (!(this.addssrc.length || this.removessrc.length || this.pendingop !== null)) return;
if (!(this.peerconnection.signalingState == 'stable' && this.peerconnection.iceConnectionState == 'connected')) {
console.warn('modifySources not yet', this.peerconnection.signalingState, this.peerconnection.iceConnectionState);
this.wait = true;
window.setTimeout(function() { self.modifySources(); }, 250);
return;
}
if (this.wait) {
window.setTimeout(function() { self.modifySources(); }, 2500);
this.wait = false;
return;
}
var sdp = new SDP(this.peerconnection.remoteDescription.sdp);
// add sources
this.addssrc.forEach(function(lines, idx) {
sdp.media[idx] += lines;
});
this.addssrc = [];
// remove sources
this.removessrc.forEach(function(lines, idx) {
lines = lines.split('\r\n');
lines.pop(); // remove empty last element;
lines.forEach(function(line) {
sdp.media[idx] = sdp.media[idx].replace(line + '\r\n', '');
});
});
this.removessrc = [];
sdp.raw = sdp.session + sdp.media.join('');
this.peerconnection.setRemoteDescription(new RTCSessionDescription({type: 'offer', sdp: sdp.raw}),
function() {
self.peerconnection.createAnswer(
function(modifiedAnswer) {
// change video direction, see https://github.com/jitsi/jitmeet/issues/41
if (self.pendingop !== null) {
var sdp = new SDP(modifiedAnswer.sdp);
if (sdp.media.length > 1) {
switch(self.pendingop) {
case 'mute':
sdp.media[1] = sdp.media[1].replace('a=sendrecv', 'a=recvonly');
break;
case 'unmute':
sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv');
break;
}
sdp.raw = sdp.session + sdp.media.join('');
modifiedAnswer.sdp = sdp.raw;
}
self.pendingop = null;
}
self.peerconnection.setLocalDescription(modifiedAnswer,
function() {
//console.log('modified setLocalDescription ok');
$(document).trigger('setLocalDescription.jingle', [self.sid]);
},
function(error) {
console.log('modified setLocalDescription failed');
}
);
},
function(error) {
console.log('modified answer failed');
}
);
},
function(error) {
console.log('modify failed');
}
);
};
// SDP-based mute by going recvonly/sendrecv
// FIXME: should probably black out the screen as well
JingleSession.prototype.hardMuteVideo = function (muted) {
this.pendingop = muted ? 'mute' : 'unmute';
this.modifySources();
this.connection.jingle.localStream.getVideoTracks().forEach(function (track) {
track.enabled = !muted;
});
};
JingleSession.prototype.sendMute = function (muted, content) {
var info = $iq({to: this.peerjid,
type: 'set'})
......
/**
* Base class for ColibriFocus and JingleSession.
* @param connection Strophe connection object
* @param sid my session identifier(resource)
* @constructor
*/
function SessionBase(connection, sid){
this.connection = connection;
this.sid = sid;
this.peerconnection
= new TraceablePeerConnection(
connection.jingle.ice_config,
connection.jingle.pc_constraints);
}
SessionBase.prototype.modifySources = function (successCallback) {
var self = this;
this.peerconnection.modifySources(function(){
$(document).trigger('setLocalDescription.jingle', [self.sid]);
if(successCallback) {
successCallback();
}
});
};
SessionBase.prototype.addSource = function (elem, fromJid) {
this.peerconnection.addSource(elem);
this.modifySources();
};
SessionBase.prototype.removeSource = function (elem, fromJid) {
this.peerconnection.removeSource(elem);
this.modifySources();
};
/**
* Switches video streams.
* @param new_stream new stream that will be used as video of this session.
* @param oldStream old video stream of this session.
* @param success_callback callback executed after successful stream switch.
*/
SessionBase.prototype.switchStreams = function (new_stream, oldStream, success_callback) {
var self = this;
// Remember SDP to figure out added/removed SSRCs
var oldSdp = null;
if(self.peerconnection.localDescription) {
oldSdp = new SDP(self.peerconnection.localDescription.sdp);
}
// Stop the stream to trigger onended event for old stream
oldStream.stop();
self.peerconnection.removeStream(oldStream);
self.connection.jingle.localVideo = new_stream;
self.peerconnection.addStream(self.connection.jingle.localVideo);
self.connection.jingle.localStreams = [];
self.connection.jingle.localStreams.push(self.connection.jingle.localAudio);
self.connection.jingle.localStreams.push(self.connection.jingle.localVideo);
// Conference is not active
if(!oldSdp) {
success_callback();
return;
}
self.peerconnection.switchstreams = true;
self.modifySources(function() {
console.log('modify sources done');
var newSdp = new SDP(self.peerconnection.localDescription.sdp);
console.log("SDPs", oldSdp, newSdp);
self.notifyMySSRCUpdate(oldSdp, newSdp);
success_callback();
});
};
/**
* Figures out added/removed ssrcs and send update IQs.
* @param old_sdp SDP object for old description.
* @param new_sdp SDP object for new description.
*/
SessionBase.prototype.notifyMySSRCUpdate = function (old_sdp, new_sdp) {
var old_media = old_sdp.getMediaSsrcMap();
var new_media = new_sdp.getMediaSsrcMap();
//console.log("old/new medias: ", old_media, new_media);
var toAdd = old_sdp.getNewMedia(new_sdp);
var toRemove = new_sdp.getNewMedia(old_sdp);
//console.log("to add", toAdd);
//console.log("to remove", toRemove);
if(Object.keys(toRemove).length > 0){
this.sendSSRCUpdate(toRemove, null, false);
}
if(Object.keys(toAdd).length > 0){
this.sendSSRCUpdate(toAdd, null, true);
}
};
/**
* Empty method that does nothing by default. It should send SSRC update IQs to session participants.
* @param sdpMediaSsrcs array of
* @param fromJid
* @param isAdd
*/
SessionBase.prototype.sendSSRCUpdate = function(sdpMediaSsrcs, fromJid, isAdd) {
//FIXME: put default implementation here(maybe from JingleSession?)
}
/**
* Sends SSRC update IQ.
* @param sdpMediaSsrcs SSRCs map obtained from SDP.getNewMedia. Cntains SSRCs to add/remove.
* @param sid session identifier that will be put into the IQ.
* @param initiator initiator identifier.
* @param toJid destination Jid
* @param isAdd indicates if this is remove or add operation.
*/
SessionBase.prototype.sendSSRCUpdateIq = function(sdpMediaSsrcs, sid, initiator, toJid, isAdd) {
var self = this;
var modify = $iq({to: toJid, type: 'set'})
.c('jingle', {
xmlns: 'urn:xmpp:jingle:1',
action: isAdd ? 'addsource' : 'removesource',
initiator: initiator,
sid: sid
}
);
// FIXME: only announce video ssrcs since we mix audio and dont need
// the audio ssrcs therefore
var modified = false;
Object.keys(sdpMediaSsrcs).forEach(function(channelNum){
modified = true;
var channel = sdpMediaSsrcs[channelNum];
modify.c('content', {name: channel.mediaType});
// FIXME: not completly sure this operates on blocks and / or handles different ssrcs correctly
// generate sources from lines
Object.keys(channel.ssrcs).forEach(function(ssrcNum) {
var mediaSsrc = channel.ssrcs[ssrcNum];
modify.c('source', { xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
modify.attrs({ssrc: mediaSsrc.ssrc});
// iterate over ssrc lines
mediaSsrc.lines.forEach(function (line) {
var idx = line.indexOf(' ');
var kv = line.substr(idx + 1);
modify.c('parameter');
if (kv.indexOf(':') == -1) {
modify.attrs({ name: kv });
} else {
modify.attrs({ name: kv.split(':', 2)[0] });
modify.attrs({ value: kv.split(':', 2)[1] });
}
modify.up(); // end of parameter
});
modify.up(); // end of source
});
modify.up(); // end of content
});
if (modified) {
self.connection.sendIQ(modify,
function (res) {
console.info('got modify result', res);
},
function (err) {
console.error('got modify error', err);
}
);
} else {
console.log('modification not necessary');
}
};
// SDP-based mute by going recvonly/sendrecv
// FIXME: should probably black out the screen as well
SessionBase.prototype.toggleVideoMute = function (callback) {
var ismuted = false;
var localVideo = connection.jingle.localVideo;
for (var idx = 0; idx < localVideo.getVideoTracks().length; idx++) {
ismuted = !localVideo.getVideoTracks()[idx].enabled;
}
for (var idx = 0; idx < localVideo.getVideoTracks().length; idx++) {
localVideo.getVideoTracks()[idx].enabled = !localVideo.getVideoTracks()[idx].enabled;
}
this.peerconnection.hardMuteVideo(!ismuted);
this.modifySources(callback(!ismuted));
};
\ No newline at end of file
......@@ -232,6 +232,14 @@ Strophe.addConnectionPlugin('emuc', {
this.presMap['source' + sourceNumber + '_ssrc'] = ssrcs;
this.presMap['source' + sourceNumber + '_direction'] = direction;
},
clearPresenceMedia: function () {
var self = this;
Object.keys(this.presMap).forEach( function(key) {
if(key.indexOf('source') != -1) {
delete self.presMap[key];
}
});
},
addPreziToPresence: function (url, currentSlide) {
this.presMap['prezins'] = 'http://jitsi.org/jitmeet/prezi';
this.presMap['preziurl'] = url;
......
<html>
<head>
<title></title>
<link rel="stylesheet" type="text/css" media="screen" href="css/chromeonly.css" />
</head>
<body>
<!-- wrap starts here -->
<div id="wrap">
<a href="http://google.com/chrome"><div id="left"></div></a>
<div id="middle"></div>
<div id="text">
<p>This service only works with Chrome.</p>
<p><a href="http://google.com/chrome">Download Chrome</a></p>
</div>
<!-- wrap ends here -->
</div>
</body>
</html>
\ No newline at end of file
<head>
<title>JitMeet: Unsupported Browser</title>
<link rel="stylesheet" type="text/css" media="screen" href="css/chromeonly.css" />
</head>
<body>
<!-- wrap starts here -->
<div id="wrap">
<a href="http://google.com/chrome"><div id="left"></div></a>
<div id="middle"></div>
<div id="text">
<p>This application is currently only supported by <a href="http://google.com/chrome">Chrome</a>, <a href="http://www.chromium.org/">Chromium</a> and <a href="http://www.opera.com">Opera</a></p>
<p><a href="http://google.com/chrome">Download Chrome</a></p>
<p class="firefox">We are hoping that <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=977864">multistream support</a> for Firefox would not be long so that we could all use this application with our favorite browser.</p>
</div>
<!-- wrap ends here -->
</div>
</body>
</html>
......@@ -89,6 +89,33 @@ html, body{
border-left:1px solid #424242;
}
#chat {
font-size:1.65em;
-webkit-transition: all .5s ease-in-out;;
-moz-transition: all .5s ease-in-out;;
transition: all .5s ease-in-out;;
}
#chat.active {
-webkit-text-shadow: 0 0 10px #ffffff;
-moz-text-shadow: 0 0 10px #ffffff;
text-shadow: 0 0 10px #ffffff;
/* -webkit-transform: scale(1.1); */
}
.toolbar_span {
display: inline-block;
position: relative;
}
.toolbar_span>span {
display: inline-block;
position: absolute;
font-size: 7pt;
color: #ffffff;
text-align:center;
cursor: pointer;
}
.localuser {
color: #087dba;
......
......@@ -12,6 +12,7 @@
<script src="js/webrtc.sdp.js"></script>
<script src="js/strophe-openfire.js"></script>
<script src="js/main.js"></script>
<script src="js/util.js"></script>
<link rel="shortcut icon" href="favicon.ico"/>
<link rel="stylesheet" href="font-awesome-4.0.3/css/font-awesome.css">
......@@ -36,7 +37,10 @@
<div class="header_button_separator"></div>
<a class="button" onclick="openLinkDialog();"><i title="Invite others" class="fa fa-link fa-lg"></i></a>
<div class="header_button_separator"></div>
<a class="button" onclick='openChat();'><i id="chat" title="Open chat" class="fa fa-comments fa-lg"></i></a>
<span class="toolbar_span">
<a class="button" onclick='openChat();'><i id="chat" title="Open chat" class="fa fa-comments-o fa-lg"></i></a>
<span id="unreadMessages"></span>
</span>
<div class="header_button_separator"></div>
<a class="button" onclick='openPDFDialog();'><i id="pdf" title="Share PDF" class="fa fa-file fa-lg"></i></a>
<div class="header_button_separator"></div>
......
......@@ -9,6 +9,8 @@ var pdfFrame = null;
var pdfPage = "1";
var altView = false;
var sipUri = null;
var notificationInterval = false;
var unreadMessages = 0;
$(document).ready(function ()
{
......@@ -55,12 +57,16 @@ $(document).ready(function ()
$('#usermsg').keydown(function(event)
{
if (event.keyCode == 13) {
event.preventDefault();
var message = this.value;
$('#usermsg').val('').trigger('autosize.resize');
this.focus();
connection.emuc.sendMessage(message, nickname);
if (event.keyCode == 13)
{
event.preventDefault();
var message = this.value;
$('#usermsg').val('').trigger('autosize.resize');
this.focus();
connection.emuc.sendMessage(message, nickname);
unreadMessages = 0;
setVisualNotification(false);
}
});
......@@ -376,7 +382,10 @@ function getConstraints(um, resolution, bandwidth, fps)
if (um.indexOf('screen') >= 0) {
window.RTC.rayo.constraints.video = {
"mandatory": {
"chromeMediaSource": "screen"
"chromeMediaSource": "screen",
"maxWidth": window.screen.width,
"maxHeight": window.screen.height,
"maxFrameRate": "3"
}
};
}
......@@ -998,24 +1007,28 @@ function toggleScreenShare()
function updateChatConversation(nick, message)
{
var timestamp = new Date();
//console.log("updateChatConversation", nick, message, timestamp);
if (!nick) nick = "System";
divClassName = "Out";
if (nickname == nick)
divClassName = "In";
var timestamp = new Date();
//console.log("updateChatConversation", nick, message, timestamp);
if (!nick) nick = "System";
divClassName = "Out";
if (nickname == nick)
divClassName = "In";
else {
unreadMessages++;
setVisualNotification(true);
}
var content = '<div class="message message' + divClassName + '">'
+'<span class="msgText">' + setEmoticons(message) + '</span>'
+'<span class="msgPerson">' + nick + '<span class="msgTime">&nbsp;-&nbsp;' + new Date().format("m-d-Y H:i:s") + '</span></span>'
+'</div>';
$('#ofmeet-log').append(content);
$('#ofmeet-log').animate({ scrollTop: $('#ofmeet-log')[0].scrollHeight}, 1000);
$('#ofmeet-log').append(content);
$('#ofmeet-log').animate({ scrollTop: $('#ofmeet-log')[0].scrollHeight}, 1000);
}
function buttonClick(id, classname) {
......@@ -1361,24 +1374,55 @@ function setEmoticons(body)
function linkify(inputText)
{
var replacedText, replacePattern1, replacePattern2, replacePattern3;
//URLs starting with http://, https://, or ftp://
replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim;
replacedText = inputText.replace(replacePattern1, '<a href="$1" target="_blank">$1</a>');
//URLs starting with "www." (without // before it, or it'd re-link the ones done above).
replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim;
replacedText = replacedText.replace(replacePattern2, '$1<a href="http://$2" target="_blank">$2</a>');
//Change email addresses to mailto:: links.
replacePattern3 = /(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim;
replacedText = replacedText.replace(replacePattern3, '<a href="mailto:$1">$1</a>');
return replacedText;
var replacedText, replacePattern1, replacePattern2, replacePattern3;
//URLs starting with http://, https://, or ftp://
replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim;
replacedText = inputText.replace(replacePattern1, '<a href="$1" target="_blank">$1</a>');
//URLs starting with "www." (without // before it, or it'd re-link the ones done above).
replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim;
replacedText = replacedText.replace(replacePattern2, '$1<a href="http://$2" target="_blank">$2</a>');
//Change email addresses to mailto:: links.
replacePattern3 = /(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim;
replacedText = replacedText.replace(replacePattern3, '<a href="mailto:$1">$1</a>');
return replacedText;
}
Date.prototype.format = function(format) {
function setVisualNotification(show)
{
var unreadMsgElement = document.getElementById('unreadMessages');
if (unreadMessages) {
unreadMsgElement.innerHTML = unreadMessages.toString();
var chatButtonElement = document.getElementById('chat').parentNode;
var leftIndent = (Util.getTextWidth(chatButtonElement) - Util.getTextWidth(unreadMsgElement) - 5)/2;
var topIndent = (Util.getTextHeight(chatButtonElement) - Util.getTextHeight(unreadMsgElement))/2 - 2;
unreadMsgElement.setAttribute('style', 'top:' + topIndent + '; left:' + leftIndent +';');
}
else
unreadMsgElement.innerHTML = '';
var glower = $('#chat');
if (show && !notificationInterval) {
notificationInterval = window.setInterval(function() {
glower.toggleClass('active');
}, 800);
}
else if (!show && notificationInterval) {
window.clearInterval(notificationInterval);
notificationInterval = false;
glower.removeClass('active');
}
}
Date.prototype.format = function(format)
{
var returnStr = '';
var replace = Date.replaceChars;
for (var i = 0; i < format.length; i++) {
......
......@@ -182,9 +182,9 @@ function getConstraints(um, resolution, bandwidth, fps)
window.RTC.rayo.constraints.video = {
"mandatory": {
"chromeMediaSource": "screen",
"maxWidth": "1280",
"maxHeight": "1280",
"maxFrameRate": "30"
"maxWidth": window.screen.width,
"maxHeight": window.screen.height,
"maxFrameRate": "3"
}
};
}
......
/**
* Utility functions.
*/
var Util = (function (my) {
/**
* Returns the text width for the given element.
*
* @param el the element
*/
my.getTextWidth = function(el) {
return (el.clientWidth + 1);
};
/**
* Returns the text height for the given element.
*
* @param el the element
*/
my.getTextHeight = function(el) {
return (el.clientHeight + 1);
};
/**
* Casts the given number to integer.
*
* @param number the number to cast
*/
my.toInteger = function(number) {
return Math.round(Number(number));
};
/**
* Plays the sound given by id.
*
* @param id the identifier of the audio element.
*/
my.playSoundNotification = function(id) {
document.getElementById(id).play();
};
/**
* Escapes the given text.
*/
my.escapeHtml = function(unsafeText) {
return $('<div/>').text(unsafeText).html();
};
/**
* Indicates if the given string is an alphanumeric string.
* Note that some special characters are also allowed (-, _ , /) for the
* purpose of checking URIs. (FIXME: This should maybe moved to another not
* so generic method in the future.)
*/
my.isAlphanumeric = function(unsafeText) {
var regex = /^[a-z0-9-_\/]+$/i;
return regex.test(unsafeText);
};
return my;
}(Util || {}));
\ No newline at end of file
/**
* JEBML - Java library to read/write EBML/Matroska elements.
* Copyright (C) 2004 Jory Stone <jebml@jory.info>
* Based on Javatroska (C) 2002 John Cannon <spyder@matroska.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Element.java
*
*
*/
package org.ebml;
/**
* Defines the basic EBML element. Subclasses may provide child element
* access.
* Created on November 19, 2002, 9:11 PM
* @author John Cannon
*/
public class BinaryElement extends Element {
/*private byte[] type = {
0x00};*/
private static int MIN_SIZE_LENGTH = 4;
//private long size = 0;
//protected byte[] data;
/*
* Creates a new instance of Element
@param type The type ID of this element
*/
public BinaryElement(byte[] type) {
super(type);
}
/** Getter for property data.
* @return Value of property data.
*
*/
public byte[] getData() {
return this.data;
}
/** Setter for property data.
* @param data New value of property data.
*
*/
public void setData(byte[] data) {
this.data = data;
this.size = data.length;
}
/** Getter for property size.
* @return Value of property size.
*
*/
public long getSize() {
return size;
}
/** Setter for property size.
* @param size New value of property size.
*
*/
public void setSize(long size) {
this.size = size;
}
/** Getter for property type.
* @return Value of property type.
*
*/
public byte[] getType() {
return type;
}
/** Setter for property type.
* @param type New value of property type.
*
*/
public void setType(byte[] type) {
this.type = type;
}
public byte[] toByteArray() {
byte[] head = makeEbmlCode(type, size);
byte[] ret = new byte[head.length + data.length];
org.ebml.util.ArrayCopy.arraycopy(head, 0, ret, 0, head.length);
org.ebml.util.ArrayCopy.arraycopy(data, 0, ret, head.length, data.length);
return ret;
}
public static void setMinSizeLength(int minSize) {
MIN_SIZE_LENGTH = minSize;
}
public static int getMinSizeLength() {
return MIN_SIZE_LENGTH;
}
}
/**
* JEBML - Java library to read/write EBML/Matroska elements.
* Copyright (C) 2004 Jory Stone <jebml@jory.info>
* Based on Javatroska (C) 2002 John Cannon <spyder@matroska.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.ebml;
import java.util.*;
public class DateElement extends SignedIntegerElement {
//const uint64 EbmlDate::UnixEpochDelay = 978307200; // 2001/01/01 00:00:00 UTC
public static long UnixEpochDelay = 978307200; // 2001/01/01 00:00:00 UTC
private static int MIN_SIZE_LENGTH = 8;
public DateElement(byte[] type) {
super(type);
}
/**
* Set the Date of this element
* @param value Date to set
*/
public void setDate(Date value) {
long val = (value.getTime() - UnixEpochDelay) * 1000000000;
setData(packInt(val, MIN_SIZE_LENGTH));
}
/**
* Get the Date value of this element
* @return Date of this element
*/
public Date getDate() {
/*
Date begin = new Date(0);
Date start = new Date(1970, 1, 1, 0, 0, 0);
Date end = new Date(2001, 1, 1, 0, 0, 0);
long diff0 = begin.getTime();
long diff1 = start.getTime();
long diff2 = end.getTime();
long diff3 = Date.UTC(2001, 1, 1, 0, 0, 0) - Date.UTC(1970, 1, 1, 0, 0, 0);
*/
long val = getValue();;
val = val / 1000000000 + UnixEpochDelay;
return new Date(val);
}
/**
* It's not recommended to use this method.
* Use the setDate(Date) method instead.
*/
public void setValue(long value)
{
setData(packInt(value, MIN_SIZE_LENGTH));
}
}
\ No newline at end of file
/**
* JEBML - Java library to read/write EBML/Matroska elements.
* Copyright (C) 2004 Jory Stone <jebml@jory.info>
* Based on Javatroska (C) 2002 John Cannon <spyder@matroska.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.ebml;
public interface DocType {
public ElementType getElements();
public Element createElement(ElementType type);
public Element createElement(byte [] type);
}
\ No newline at end of file
This diff is collapsed.
/**
* JEBML - Java library to read/write EBML/Matroska elements.
* Copyright (C) 2004 Jory Stone <jebml@jory.info>
* Based on Javatroska (C) 2002 John Cannon <spyder@matroska.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.ebml;
import org.ebml.io.*;
/**
* Summary description for EBMLWriter.
*/
public class EBMLWriter
{
protected DataWriter writer;
/** Creates a new <code>EBMLReader</code> reading from the <code>DataSource
* source</code>. The <code>DocType doc</code> is used to validate the
* document.
*
* @param source DataSource to read from
* @param doc DocType to use to validate the docment
*/
public EBMLWriter(DataWriter writer)
{
this.writer = writer;
}
public long writeElement(Element elem)
{
return elem.writeHeaderData(writer) + elem.writeData(writer);
}
}
/**
* JEBML - Java library to read/write EBML/Matroska elements.
* Copyright (C) 2004 Jory Stone <jebml@jory.info>
* Based on Javatroska (C) 2002 John Cannon <spyder@matroska.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.ebml;
/*
* Element.java
*
* Created on November 19, 2002, 9:11 PM
*/
import org.ebml.io.*;
import org.ebml.util.*;
/**
* Defines the basic EBML element. Subclasses may provide child element access.
* @author John Cannon
*/
public class Element {
protected Element parent;
protected ElementType typeInfo;
protected byte[] type = {
0x00};
private static int MIN_SIZE_LENGTH = 0;
protected long size = 0;
protected byte[] data;
protected boolean dataRead = false;
private int headerSize;
/** Creates a new instance of Element */
public Element(byte[] type) {
this.type = type;
}
/** Read the element data
*/
public void readData(DataSource source) {
//Setup a buffer for it's data
this.data = new byte[(int)size];
//Read the data
source.read(this.data, 0, this.data.length);
dataRead = true;
}
/** Skip the element data
*/
public void skipData(DataSource source) {
if (!dataRead) {
// Skip the data
source.skip(size);
dataRead = true;
}
}
public long writeElement(DataWriter writer)
{
return writeHeaderData(writer) + writeData(writer);
}
/** Write the element header data.
* Override this in sub-classes for more specialized writing.
*/
public long writeHeaderData(DataWriter writer)
{
long len = 0;
byte [] type = getType();
len += type.length;
writer.write(type);
byte [] size = Element.makeEbmlCodedSize(getSize());
len += size.length;
writer.write(size);
return len;
}
/** Write the element data.
* Override this in sub-classes for more specialized writing.
*/
public long writeData(DataWriter writer)
{
return writer.write(this.data, 0, this.data.length);
}
/** Getter for property data.
* @return Value of property data.
*
*/
public byte[] getData() {
return this.data;
}
/** Setter for property data.
* @param data New value of property data.
*
*/
public void setData(byte[] data) {
this.data = data;
this.size = data.length;
}
/** Clears the data of this element, useful if you just want
* this element to be a placeholder
*/
public void clearData()
{
this.data = null;
}
/** Getter for property size.
* @return Value of property size.
*
*/
public long getSize() {
return size;
}
/** Setter for property size.
* @param size New value of property size.
*
*/
public void setSize(long size) {
this.size = size;
}
/** Get the total size of this element
*/
public long getTotalSize()
{
long totalSize = 0;
totalSize += getType().length;
//totalSize += Element.codedSizeLength(getSize());
totalSize += this.headerSize;
totalSize += getSize();
return totalSize;
}
/** Getter for property type.
* @return Value of property type.
*
*/
public byte[] getType() {
return type;
}
/** Setter for property type.
* @param type New value of property type.
*
*/
public void setType(byte[] type) {
this.type = type;
}
public void setElementType(ElementType typeInfo) {
this.typeInfo = typeInfo;
}
public ElementType getElementType() {
return typeInfo;
}
/** Getter for property parent.
* @return Value of property parent.
*
*/
public Element getParent() {
return this.parent;
}
/** Setter for property parent.
* @param parent New value of property parent.
*
*/
public void setParent(Element parent) {
this.parent = parent;
}
public byte[] toByteArray() {
byte[] head = makeEbmlCode(type, size);
byte[] ret = new byte[head.length + data.length];
org.ebml.util.ArrayCopy.arraycopy(head, 0, ret, 0, head.length);
org.ebml.util.ArrayCopy.arraycopy(data, 0, ret, head.length, data.length);
return ret;
}
public boolean equals(byte [] typeId) {
return ElementType.compareIDs(this.type, typeId);
}
public boolean equals(ElementType elemType) {
return this.equals(elemType.id);
}
public static void setMinSizeLength(int minSize) {
MIN_SIZE_LENGTH = minSize;
}
public static int getMinSizeLength() {
return MIN_SIZE_LENGTH;
}
public static byte[] makeEbmlCode(byte[] typeID, long size) {
int codedLen = codedSizeLength(size);
byte[] ret = new byte[typeID.length + codedLen];
ArrayCopy.arraycopy(typeID, 0, ret, 0, typeID.length);
byte[] codedSize = makeEbmlCodedSize(size);
ArrayCopy.arraycopy(codedSize, 0, ret, typeID.length, codedSize.length);
return ret;
}
public static byte[] makeEbmlCodedSize(long size) {
int len = codedSizeLength(size);
byte[] ret = new byte[len];
//byte[] packedSize = packIntUnsigned(size);
long mask = 0x00000000000000FFL;
for (int i = 0; i < len; i++) {
ret[len - 1 - i] = (byte)((size & mask) >>> (i * 8));
mask <<= 8;
}
//The first size bits should be clear, otherwise we have an error in the size determination.
ret[0] |= 0x80 >> (len - 1);
return ret;
}
public static int getMinByteSize(long value) {
if (value <= 0x7F && value >= 0x80) {
return 1;
}
else if (value <= 0x7FFF && value >= 0x8000) {
return 2;
}
else if (value <= 0x7FFFFF && value >= 0x800000) {
return 3;
}
else if (value <= 0x7FFFFFFF && value >= 0x80000000) {
return 4;
}
else if (value <= 0x7FFFFFFFFFL && value >= 0x8000000000L) {
return 5;
}
else if (value <= 0x7FFFFFFFFFFFL && value >= 0x800000000000L) {
return 6;
}
else if (value <= 0x7FFFFFFFFFFFFFL && value >= 0x80000000000000L) {
return 7;
}
else {
return 8;
}
}
public static int getMinByteSizeUnsigned(long value) {
int size = 8;
long mask = 0xFF00000000000000L;
for (int i = 0; i < 8; i++) {
if ((value & mask) == 0) {
mask = mask >>> 8;
size--;
}
else {
return size;
}
}
return 8;
}
public static int codedSizeLength(long value) {
int codedSize = 0;
if (value < 127) {
codedSize = 1;
}
else if (value < 16383) {
codedSize = 2;
}
else if (value < 2097151) {
codedSize = 3;
}
else if (value < 268435455) {
codedSize = 4;
}
if ((MIN_SIZE_LENGTH > 0) && (codedSize <= MIN_SIZE_LENGTH)) {
codedSize = MIN_SIZE_LENGTH;
}
else {
//codedSize = 8;
}
return codedSize;
}
public static byte[] packIntUnsigned(long value) {
int size = getMinByteSizeUnsigned(value);
return packInt(value, size);
}
public static byte[] packInt(long value) {
int size = getMinByteSize(value);
return packInt(value, size);
}
public static byte[] packInt(long value, int size)
{
byte[] ret = new byte[size];
long mask = 0x00000000000000FFL;
int b = size - 1;
for (int i = 0; i < size; i++)
{
ret[b] = (byte)(((value >>> (8 * i)) & mask));
b--;
}
return ret;
}
public void setHeaderSize(int headerSize) {
this.headerSize = headerSize;
}
}
/**
* JEBML - Java library to read/write EBML/Matroska elements.
* Copyright (C) 2004 Jory Stone <jebml@jory.info>
* Based on Javatroska (C) 2002 John Cannon <spyder@matroska.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.ebml;
import java.util.*;
public class ElementType {
public static short UNKNOWN_ELEMENT = 0;
public static short MASTER_ELEMENT = 1;
public static short BINARY_ELEMENT = 2;
public static short SINTEGER_ELEMENT = 3;
public static short UINTEGER_ELEMENT = 4;
public static short FLOAT_ELEMENT = 5;
public static short STRING_ELEMENT = 6;
public static short ASCII_STRING_ELEMENT = 7;
public static short DATE_ELEMENT = 8;
public static short LAST_ELEMENT_TYPE = 100;
public String name;
public short level;
public byte [] id;
public short type;
//public HashMap child;
public ArrayList<ElementType> children;
public ElementType() {
}
public ElementType(String name, short level, byte [] id, short type, ArrayList<ElementType> children) {
this.name = name;
this.level = level;
this.id = id;
this.type = type;
this.children = children;
}
public ElementType findElement(byte [] id) {
if (this.isElement(id))
return this;
if (children != null) {
for (int i = 0; i < children.size(); i++) {
ElementType entry = (ElementType)children.get(i);
if (entry.isElement(id))
return entry;
entry = entry.findElement(id);
if (entry != null)
return entry;
}
}
return null;
}
public boolean isElement(byte [] id) {
return ElementType.compareIDs(this.id, id);
}
public static boolean compareIDs(byte[] id1, byte[] id2) {
if ((id1 == null)
|| (id2 == null)
|| (id1.length != id2.length))
return false;
for (int i = 0; i < id1.length; i++) {
if (id1[i] != id2[i])
return false;
}
return true;
}
public Element createElement() {
Element elem;
if (this.type == ElementType.MASTER_ELEMENT) {
elem = new MasterElement(this.id);
} else if (this.type == ElementType.BINARY_ELEMENT) {
elem = new BinaryElement(this.id);
} else if (this.type == ElementType.STRING_ELEMENT) {
elem = new StringElement(this.id);
} else if (this.type == ElementType.ASCII_STRING_ELEMENT) {
elem = new StringElement(this.id, "US-ASCII");
} else if (this.type == ElementType.SINTEGER_ELEMENT) {
elem = new SignedIntegerElement(this.id);
} else if (this.type == ElementType.UINTEGER_ELEMENT) {
elem = new UnsignedIntegerElement(this.id);
} else if (this.type == ElementType.FLOAT_ELEMENT) {
elem = new FloatElement(this.id);
} else if (this.type == ElementType.DATE_ELEMENT) {
elem = new DateElement(this.id);
} else if (this.type == ElementType.UNKNOWN_ELEMENT) {
elem = new BinaryElement(this.id);
} else {
return null;
}
elem.setElementType(this);
return elem;
}
}
\ No newline at end of file
/**
* JEBML - Java library to read/write EBML/Matroska elements.
* Copyright (C) 2004 Jory Stone <jebml@jory.info>
* Based on Javatroska (C) 2002 John Cannon <spyder@matroska.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.ebml;
import java.io.*;
public class FloatElement extends BinaryElement {
public FloatElement(byte[] type) {
super(type);
}
/**
* Set the float value of this element
* @param value Float value to set
* @throws ArithmeticException if the float value is larger than Double.MAX_VALUE
*/
public void setValue(double value) {
try {
if (value < Float.MAX_VALUE) {
ByteArrayOutputStream bIO = new ByteArrayOutputStream(4);
DataOutputStream dIO = new DataOutputStream(bIO);
dIO.writeFloat((float)value);
setData(bIO.toByteArray());
} else if (value < Double.MAX_VALUE) {
ByteArrayOutputStream bIO = new ByteArrayOutputStream(8);
DataOutputStream dIO = new DataOutputStream(bIO);
dIO.writeDouble(value);
setData(bIO.toByteArray());
} else {
throw new ArithmeticException(
"80-bit floats are not supported, BTW How did you create such a large float in Java?");
}
} catch (IOException ex) {
return;
}
}
/**
* Get the float value of this element
* @return Float value of this element
* @throws ArithmeticException for 80-bit or 10-byte floats. AFAIK Java doesn't support them
*/
public double getValue() {
try {
if (size == 4) {
float value = 0;
ByteArrayInputStream bIS = new ByteArrayInputStream(data);
DataInputStream dIS = new DataInputStream(bIS);
value = dIS.readFloat();
return value;
} else if (size == 8) {
double value = 0;
ByteArrayInputStream bIS = new ByteArrayInputStream(data);
DataInputStream dIS = new DataInputStream(bIS);
value = dIS.readDouble();
return value;
} else {
throw new ArithmeticException(
"80-bit floats are not supported");
}
} catch (IOException ex) {
return 0;
}
}
}
\ No newline at end of file
/**
* JEBML - Java library to read/write EBML/Matroska elements.
* Copyright (C) 2004 Jory Stone <jebml@jory.info>
* Based on Javatroska (C) 2002 John Cannon <spyder@matroska.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.ebml;
import org.ebml.io.*;
import java.util.*;
public class MasterElement extends Element {
protected long usedSize;
protected ArrayList<Element> children = new ArrayList<Element>();
public MasterElement(byte[] type) {
super(type);
usedSize = 0;
}
public Element readNextChild(EBMLReader reader) {
if (usedSize >= this.getSize())
return null;
Element elem = reader.readNextElement();
if (elem == null)
return null;
elem.setParent(this);
usedSize += elem.getTotalSize();
return elem;
}
/* Skip the element data */
public void skipData(DataSource source) {
// Skip the child elements
source.skip(size-usedSize);
}
public long writeData(DataWriter writer)
{
long len = 0;
for (int i = 0; i < children.size(); i++)
{
Element elem = (Element)children.get(i);
len += elem.writeElement(writer);
}
return len;
}
public void addChildElement(Element elem)
{
children.add(elem);
size += elem.getTotalSize();
}
}
\ No newline at end of file
/**
* JEBML - Java library to read/write EBML/Matroska elements.
* Copyright (C) 2004 Jory Stone <jebml@jory.info>
* Based on Javatroska (C) 2002 John Cannon <spyder@matroska.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.ebml;
/*
* SignedInteger.java
*
* Created on February 17, 2003, 1:54 PM
*/
/**
* Basic class for the Signed Integer EBML data type.
* @author John Cannon
*/
public class SignedIntegerElement
extends BinaryElement {
public SignedIntegerElement(byte[] typeID) {
super(typeID);
}
public void setValue(long value) {
//System.out.println(Long.toHexString(value));
setData(packInt(value));
/*for (int i = 0; i < data.length; i++) {
System.out.print(Integer.toHexString(data[i]) + ", ");
}
System.out.print("\n");*/
}
public long getValue() {
long l = 0;
long tmp = 0;
l |= ((long)data[0] << (56 - ((8 - data.length) * 8)));
for (int i = 1; i < data.length; i++) {
tmp = ((long)data[data.length - i]) << 56;
tmp >>>= 56 - (8 * (i - 1));
l |= tmp;
}
//System.out.println(Long.toHexString(l));
return l;
}
}
/**
* JEBML - Java library to read/write EBML/Matroska elements.
* Copyright (C) 2004 Jory Stone <jebml@jory.info>
* Based on Javatroska (C) 2002 John Cannon <spyder@matroska.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.ebml;
/**
* Baisc class for handling an EBML string data type. This class encapsulates
* both UTF and ASCII string types and can use any string type supported by
* the Java platform.
*
* @author John Cannon
*/
public class StringElement
extends org.ebml.BinaryElement {
private String charset = "UTF-8";
/** Creates a new instance of StringElement */
public StringElement(byte[] typeID) {
super(typeID);
}
public StringElement(byte[] typeID, String encoding) {
super(typeID);
charset = encoding;
}
private boolean checkForCharsetHack()
{
// Check if we are trying to read UTF-8, if so lets try UTF8.
// Microsofts Java supports "UTF8" but not "UTF-8"
if (charset.compareTo("UTF-8") == 0)
{
charset = "UTF8";
// Let's try again
return true;
}
else if (charset.compareTo("US-ASCII") == 0)
{
// This is the same story as UTF-8,
// If Microsoft is going to hijack Java they should at least support the orignal :>
charset = "ASCII";
// Let's try again
return true;
}
return false;
}
public String getValue() {
try {
if (data == null)
throw new java.lang.IllegalStateException("Call readData() before trying to extract the string value.");
return new String(data, charset);
}
catch (java.io.UnsupportedEncodingException ex) {
if (checkForCharsetHack())
{
return getValue();
}
ex.printStackTrace();
return "";
}
}
public void setValue(String value) {
try {
setData(value.getBytes(charset));
}
catch (java.io.UnsupportedEncodingException ex) {
if (checkForCharsetHack())
{
setValue(value);
return;
}
ex.printStackTrace();
}
}
public String getEncoding() {
return charset;
}
}
/**
* JEBML - Java library to read/write EBML/Matroska elements.
* Copyright (C) 2004 Jory Stone <jebml@jory.info>
* Based on Javatroska (C) 2002 John Cannon <spyder@matroska.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.ebml;
import java.util.*;
public class UnknownElementType extends ElementType {
public UnknownElementType(byte [] id) {
super("Unknown", (short)0, id, ElementType.UNKNOWN_ELEMENT, (ArrayList<ElementType>)null);
}
}
/**
* JEBML - Java library to read/write EBML/Matroska elements.
* Copyright (C) 2004 Jory Stone <jebml@jory.info>
* Based on Javatroska (C) 2002 John Cannon <spyder@matroska.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.ebml;
/*
* UnsignedIntegerElement.java
*
* Created on February 15, 2003, 6:27 PM
*/
/**
* Basic class for the Unsigned Integer data type in EBML.
* @author John Cannon
*/
public class UnsignedIntegerElement
extends org.ebml.BinaryElement {
public UnsignedIntegerElement(byte[] typeID) {
super(typeID);
}
public void setValue(long value) {
setData(packIntUnsigned(value));
}
public long getValue() {
long l = 0;
long tmp = 0;
for (int i = 0; i < data.length; i++) {
tmp = ((long)data[data.length - 1 - i]) << 56;
tmp >>>= (56 - (i * 8));
l |= tmp;
}
return l;
}
}
/**
* JEBML - Java library to read/write EBML/Matroska elements.
* Copyright (C) 2004 Jory Stone <jebml@jory.info>
* Based on Javatroska (C) 2002 John Cannon <spyder@matroska.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.ebml.io;
/**
* Interface for seeking operations.
* Not designed to be used alone but as a super interface for a more
* specialied reading or writing interfaces.
*/
public interface DataSeekable
{
/**
* Returns the length
*
* @return <code>-1</code> if the length is unknown
* @return <code> >0</code> length of the <code>DataSeekable</code>
*/
public long length();
public long getFilePointer();
/**
* Check if the <code>DataSeekable</code> object is seekable
*
* @return <code>true</code> if seeking is supported.
* @return <code>false</code> if seeking is not supported.
*/
public boolean isSeekable();
/**
* Seeks in the <code>DataSeekable</code>
*
* @param pos Absolute position to seek to
* @return The new file position
*/
public long seek(long pos);
}
/**
* JEBML - Java library to read/write EBML/Matroska elements.
* Copyright (C) 2004 Jory Stone <jebml@jory.info>
* Based on Javatroska (C) 2002 John Cannon <spyder@matroska.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.ebml.io;
/*
* DataSource.java
*
* Created on November 18, 2002, 4:08 PM
*/
/**
* Defines the interface used for custom <code>DataSource</code>'s. A
* <code>DataSource</code> provides methods of reading bytes individually or
* in arrays. These basic functions must be defined in any <code>DataSource</code>
* objects to be used with the <code>EBMLReader</code>.
*
* @author John Cannon
* @author Jory Stone
*/
public interface DataSource extends DataSeekable {
public byte readByte();
public int read(byte[] buff);
public int read(byte[] buff, int offset, int length);
/**
* Skip a number of bytes in the <code>DataSeekable</code>
*
* @param offset The number of bytes to skip
* @return The number of bytes skipped
*/
public long skip(long offset);
}
/**
* JEBML - Java library to read/write EBML/Matroska elements.
* Copyright (C) 2004 Jory Stone <jebml@jory.info>
* Based on Javatroska (C) 2002 John Cannon <spyder@matroska.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.ebml.io;
/**
* Defines the interface used for custom <code>DataWriter</code>'s. A
* <code>DataWriter</code> provides methods of writing bytes individually or
* in arrays. These basic functions must be defined in any <code>DataWriter</code>
* objects to be used with the <code>EBMLWriter</code>.
*
* @author Jory Stone
*/
public interface DataWriter extends DataSeekable
{
public int write(byte b);
public int write(byte[] buff);
public int write(byte[] buff, int offset, int length);
}
package org.ebml.matroska;
import java.io.*;
/**
* <p>Title: JEBML</p>
* <p>Description: Java Classes to Read EBML Elements</p>
* <p>Copyright: Copyright (c) 2002-2004 John Cannon <spyder@matroska.org>, Jory Stone <jcsston@toughguy.net></p>
* <p>Company: </p>
* @author jcsston
* @version 1.0
*/
public class MatroskaFileFilter extends javax.swing.filechooser.FileFilter {
public MatroskaFileFilter() {
}
public boolean accept(File parm1) {
if (parm1.isDirectory())
return true;
String path = parm1.getAbsolutePath();
path = path.toLowerCase();
if (path.endsWith("mkv")
|| path.endsWith("mka")
|| path.endsWith("mks"))
return true;
return false;
}
public String getDescription() {
return "Matroska Video/Audio Files";
}
}
\ No newline at end of file
This diff is collapsed.
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