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
......@@ -4,12 +4,12 @@ var connection = null;
var focus = null;
var activecall = null;
var RTC = null;
var RTCPeerConnection = null;
var nickname = null;
var sharedKey = '';
var roomUrl = null;
var ssrc2jid = {};
var localVideoSrc = null;
var flipXLocalVideo = true;
var preziPlayer = null;
/* window.onbeforeunload = closePageWarning; */
......@@ -23,7 +23,6 @@ function init() {
window.location.href = 'chromeonly.html';
return;
}
RTCPeerconnection = TraceablePeerConnection;
connection = new Strophe.Connection(document.getElementById('boshURL').value || config.bosh || '/http-bind');
......@@ -49,11 +48,10 @@ function init() {
if (config.useStunTurn) {
connection.jingle.getStunAndTurnCredentials();
}
if (RTC.browser === 'firefox') {
getUserMediaWithConstraints(['audio']);
} else {
getUserMediaWithConstraints(['audio', 'video'], config.resolution || '360');
}
getUserMediaWithConstraints( ['audio'], audioStreamReady,
function(error){
console.error('failed to obtain audio stream - stop', error);
});
document.getElementById('connect').disabled = true;
} else {
console.log('status', status);
......@@ -61,6 +59,31 @@ function init() {
});
}
function audioStreamReady(stream) {
change_local_audio(stream);
if(RTC.browser !== 'firefox') {
getUserMediaWithConstraints( ['video'], videoStreamReady, videoStreamFailed, config.resolution || '360' );
} else {
doJoin();
}
}
function videoStreamReady(stream) {
change_local_video(stream, true);
doJoin();
}
function videoStreamFailed(error) {
console.warn("Failed to obtain video stream - continue anyway", error);
doJoin();
}
function doJoin() {
var roomnode = null;
var path = window.location.pathname;
......@@ -104,39 +127,57 @@ function doJoin() {
connection.emuc.doJoin(roomjid);
}
$(document).bind('mediaready.jingle', function (event, stream) {
connection.jingle.localStream = stream;
RTC.attachMediaStream($('#localVideo'), stream);
document.getElementById('localVideo').autoplay = true;
document.getElementById('localVideo').volume = 0;
function change_local_audio(stream) {
localVideoSrc = document.getElementById('localVideo').src;
updateLargeVideo(localVideoSrc, true, 0);
connection.jingle.localAudio = stream;
RTC.attachMediaStream($('#localAudio'), stream);
document.getElementById('localAudio').autoplay = true;
document.getElementById('localAudio').volume = 0;
}
$('#localVideo').click(function () {
$(document).trigger("video.selected", [false]);
updateLargeVideo($(this).attr('src'), true, 0);
function change_local_video(stream, flipX) {
$('video').each(function (idx, el) {
if (el.id.indexOf('mixedmslabel') !== -1) {
el.volume = 0;
el.volume = 1;
}
});
});
connection.jingle.localVideo = stream;
doJoin();
});
var localVideo = document.createElement('video');
localVideo.id = 'localVideo_'+stream.id;
localVideo.autoplay = true;
localVideo.volume = 0; // is it required if audio is separated ?
localVideo.oncontextmenu = function () { return false; };
$(document).bind('mediafailure.jingle', function () {
// FIXME
});
var localVideoContainer = document.getElementById('localVideoContainer');
localVideoContainer.appendChild(localVideo);
var localVideoSelector = $('#' + localVideo.id);
// Add click handler
localVideoSelector.click(function () { handleVideoThumbClicked(localVideo.src); } );
// Add stream ended handler
stream.onended = function () {
localVideoContainer.removeChild(localVideo);
checkChangeLargeVideo(localVideo.src);
};
// Flip video x axis if needed
flipXLocalVideo = flipX;
if(flipX) {
localVideoSelector.addClass("flipVideoX");
}
// Attach WebRTC stream
RTC.attachMediaStream(localVideoSelector, stream);
localVideoSrc = localVideo.src;
updateLargeVideo(localVideoSrc, 0);
}
$(document).bind('remotestreamadded.jingle', function (event, data, sid) {
function waitForRemoteVideo(selector, sid) {
if(selector.removed) {
console.warn("media removed before had started", selector);
return;
}
var sess = connection.jingle.sessions[sid];
if (data.stream.id === 'mixedmslabel') return;
videoTracks = data.stream.getVideoTracks();
console.log("waiting..", videoTracks, selector[0]);
if (videoTracks.length === 0 || selector[0].currentTime > 0) {
RTC.attachMediaStream(selector, data.stream); // FIXME: why do i have to do this for FF?
$(document).trigger('callactive.jingle', [selector, sid]);
......@@ -180,7 +221,9 @@ $(document).bind('remotestreamadded.jingle', function (event, data, sid) {
}
} else {
if (data.stream.id !== 'mixedmslabel') {
console.warn('can not associate stream', data.stream.id, 'with a participant');
console.error('can not associate stream', data.stream.id, 'with a participant');
// We don't want to add it here since it will cause troubles
return;
}
// FIXME: for the mixed ms we dont need a video -- currently
container = document.createElement('span');
......@@ -188,8 +231,11 @@ $(document).bind('remotestreamadded.jingle', function (event, data, sid) {
remotes.appendChild(container);
Util.playSoundNotification('userJoined');
}
var vid = document.createElement('video');
var id = 'remoteVideo_' + sid + '_' + data.stream.id;
var isVideo = data.stream.getVideoTracks().length > 0;
var vid = isVideo ? document.createElement('video') : document.createElement('audio');
var id = (isVideo ? 'remoteVideo_' : 'remoteAudio_') + sid + '_' + data.stream.id;
vid.id = id;
vid.autoplay = true;
vid.oncontextmenu = function () { return false; };
......@@ -203,44 +249,85 @@ $(document).bind('remotestreamadded.jingle', function (event, data, sid) {
var sel = $('#' + id);
sel.hide();
RTC.attachMediaStream(sel, data.stream);
if(isVideo) {
waitForRemoteVideo(sel, sid);
}
data.stream.onended = function () {
console.log('stream ended', this.id);
var src = $('#' + id).attr('src');
if (src === $('#largeVideo').attr('src')) {
// this is currently displayed as large
// pick the last visible video in the row
// if nobody else is left, this picks the local video
var pick = $('#remoteVideos>span[id!="mixedstream"]:visible:last>video').get(0);
// mute if localvideo
var isLocalVideo = false;
if (pick) {
if (pick.src === localVideoSrc)
isLocalVideo = true;
updateLargeVideo(pick.src, isLocalVideo, pick.volume);
}
}
$('#' + id).parent().remove();
// Mark video as removed to cancel waiting loop(if video is removed before has started)
sel.removed = true;
sel.remove();
var audioCount = $('#'+container.id+'>audio').length;
var videoCount = $('#'+container.id+'>video').length;
if(!audioCount && !videoCount) {
console.log("Remove whole user");
// Remove whole container
container.remove();
Util.playSoundNotification('userLeft');
resizeThumbnails();
};
sel.click(
function () {
$(document).trigger("video.selected", [false]);
updateLargeVideo($(this).attr('src'), false, 1);
}
);
checkChangeLargeVideo(vid.src);
};
// Add click handler
sel.click(function () { handleVideoThumbClicked(vid.src); });
// an attempt to work around https://github.com/jitsi/jitmeet/issues/32
if (data.peerjid && sess.peerjid === data.peerjid &&
if (isVideo
&& data.peerjid && sess.peerjid === data.peerjid &&
data.stream.getVideoTracks().length === 0 &&
connection.jingle.localStream.getVideoTracks().length > 0) {
connection.jingle.localVideo.getVideoTracks().length > 0) {
window.setTimeout(function() {
sendKeyframe(sess.peerconnection);
}, 3000);
}
});
function handleVideoThumbClicked(videoSrc) {
$(document).trigger("video.selected", [false]);
updateLargeVideo(videoSrc, 1);
$('audio').each(function (idx, el) {
// We no longer mix so we check for local audio now
if(el.id != 'localAudio') {
el.volume = 0;
el.volume = 1;
}
});
}
/**
* Checks if removed video is currently displayed and tries to display another one instead.
* @param removedVideoSrc src stream identifier of the video.
*/
function checkChangeLargeVideo(removedVideoSrc){
if (removedVideoSrc === $('#largeVideo').attr('src')) {
// this is currently displayed as large
// pick the last visible video in the row
// if nobody else is left, this picks the local video
var pick = $('#remoteVideos>span[id!="mixedstream"]:visible:last>video').get(0);
if(!pick) {
console.info("Last visible video no longer exists");
pick = $('#remoteVideos>span[id!="mixedstream"]>video').get(0);
}
// mute if localvideo
if (pick) {
updateLargeVideo(pick.src, pick.volume);
} else {
console.warn("Failed to elect large video");
}
}
}
// an attempt to work around https://github.com/jitsi/jitmeet/issues/32
function sendKeyframe(pc) {
console.log('sendkeyframe', pc.iceConnectionState);
......@@ -355,7 +442,7 @@ $(document).bind('callactive.jingle', function (event, videoelem, sid) {
videoelem.show();
resizeThumbnails();
updateLargeVideo(videoelem.attr('src'), false, 1);
updateLargeVideo(videoelem.attr('src'), 1);
showFocusIndicator();
}
......@@ -363,6 +450,8 @@ $(document).bind('callactive.jingle', function (event, videoelem, sid) {
$(document).bind('callterminated.jingle', function (event, sid, reason) {
// FIXME
focus = null;
activecall = null;
});
$(document).bind('setLocalDescription.jingle', function (event, sid) {
......@@ -379,15 +468,27 @@ $(document).bind('setLocalDescription.jingle', function (event, sid) {
var ssrc = SDPUtil.find_line(media, 'a=ssrc:').substring(7).split(' ')[0];
newssrcs[type] = ssrc;
directions[type] = (SDPUtil.find_line(media, 'a=sendrecv') || SDPUtil.find_line(media, 'a=recvonly') || SDPUtil.find_line('a=sendonly') || SDPUtil.find_line('a=inactive') || 'a=sendrecv').substr(2);
directions[type] = (
SDPUtil.find_line(media, 'a=sendrecv')
|| SDPUtil.find_line(media, 'a=recvonly')
|| SDPUtil.find_line('a=sendonly')
|| SDPUtil.find_line('a=inactive')
|| 'a=sendrecv' ).substr(2);
}
});
console.log('new ssrcs', newssrcs);
// Have to clear presence map to get rid of removed streams
connection.emuc.clearPresenceMedia();
var i = 0;
Object.keys(newssrcs).forEach(function (mtype) {
i++;
connection.emuc.addMediaToPresence(i, mtype, newssrcs[mtype], directions[mtype]);
var type = mtype;
// Change video type to screen
if(mtype === 'video' && isUsingScreenStream) {
type = 'screen';
}
connection.emuc.addMediaToPresence(i, type, newssrcs[mtype], directions[mtype]);
});
if (i > 0) {
connection.emuc.sendPresence();
......@@ -422,7 +523,7 @@ $(document).bind('joined.muc', function (event, jid, info) {
$(document).bind('entered.muc', function (event, jid, info, pres) {
console.log('entered', jid, info);
console.log(focus);
console.log('is focus?' + focus ? 'true' : 'false');
var videoSpanId = 'participant_' + Strophe.getResourceFromJid(jid);
var container = addRemoteVideoContainer(videoSpanId);
......@@ -489,12 +590,21 @@ $(document).bind('left.muc', function (event, jid) {
});
$(document).bind('presence.muc', function (event, jid, info, pres) {
// Remove old ssrcs coming from the jid
Object.keys(ssrc2jid).forEach(function(ssrc){
if(ssrc2jid[ssrc] == jid){
delete ssrc2jid[ssrc];
}
});
$(pres).find('>media[xmlns="http://estos.de/ns/mjs"]>source').each(function (idx, ssrc) {
//console.log(jid, 'assoc ssrc', ssrc.getAttribute('type'), ssrc.getAttribute('ssrc'));
ssrc2jid[ssrc.getAttribute('ssrc')] = jid;
var type = ssrc.getAttribute('type');
// might need to update the direction if participant just went from sendrecv to recvonly
if (ssrc.getAttribute('type') === 'video') {
if (type === 'video' || type === 'screen') {
var el = $('#participant_' + Strophe.getResourceFromJid(jid) + '>video');
switch(ssrc.getAttribute('direction')) {
case 'sendrecv':
......@@ -502,8 +612,18 @@ $(document).bind('presence.muc', function (event, jid, info, pres) {
break;
case 'recvonly':
el.hide();
// FIXME: Check if we have to change large video
//checkChangeLargeVideo(el);
break;
}
// Camera video or shared screen ?
if (type === 'screen') {
// Shared screen
//console.info("Have screen ssrc from "+jid, ssrc);
} else {
// Camera video
//console.info("Have camera ssrc from "+jid, ssrc);
}
}
});
......@@ -687,23 +807,27 @@ function isPresentationVisible() {
/**
* Updates the large video with the given new video source.
*/
function updateLargeVideo(newSrc, localVideo, vol) {
function updateLargeVideo(newSrc, vol) {
console.log('hover in', newSrc);
setPresentationVisible(false);
if ($('#largeVideo').attr('src') !== newSrc) {
document.getElementById('largeVideo').volume = vol;
// FIXME: is it still required ? audio is separated
//document.getElementById('largeVideo').volume = vol;
$('#largeVideo').fadeOut(300, function () {
$(this).attr('src', newSrc);
// Screen stream is already rotated
var flipX = (newSrc === localVideoSrc) && flipXLocalVideo;
var videoTransform = document.getElementById('largeVideo').style.webkitTransform;
if (localVideo && videoTransform !== 'scaleX(-1)') {
if (flipX && videoTransform !== 'scaleX(-1)') {
document.getElementById('largeVideo').style.webkitTransform = "scaleX(-1)";
}
else if (!localVideo && videoTransform === 'scaleX(-1)') {
else if (!flipX && videoTransform === 'scaleX(-1)') {
document.getElementById('largeVideo').style.webkitTransform = "none";
}
......@@ -712,27 +836,34 @@ function updateLargeVideo(newSrc, localVideo, vol) {
}
}
function getConferenceHandler() {
return focus ? focus : activecall;
}
function toggleVideo() {
if (!(connection && connection.jingle.localStream)) return;
var ismuted = false;
for (var idx = 0; idx < connection.jingle.localStream.getVideoTracks().length; idx++) {
ismuted = !connection.jingle.localStream.getVideoTracks()[idx].enabled;
if (!(connection && connection.jingle.localVideo)) return;
var sess = getConferenceHandler();
if (sess) {
sess.toggleVideoMute(
function(isMuted){
if(isMuted) {
$('#video').removeClass("fa fa-video-camera fa-lg");
$('#video').addClass("fa fa-video-camera no-fa-video-camera fa-lg");
} else {
$('#video').removeClass("fa fa-video-camera no-fa-video-camera fa-lg");
$('#video').addClass("fa fa-video-camera fa-lg");
}
for (var idx = 0; idx < connection.jingle.localStream.getVideoTracks().length; idx++) {
connection.jingle.localStream.getVideoTracks()[idx].enabled = !connection.jingle.localStream.getVideoTracks()[idx].enabled;
}
var sess = focus || activecall;
if (!sess) {
return;
);
}
sess.pendingop = ismuted ? 'unmute' : 'mute';
sess.modifySources();
}
function toggleAudio() {
if (!(connection && connection.jingle.localStream)) return;
for (var idx = 0; idx < connection.jingle.localStream.getAudioTracks().length; idx++) {
connection.jingle.localStream.getAudioTracks()[idx].enabled = !connection.jingle.localStream.getAudioTracks()[idx].enabled;
if (!(connection && connection.jingle.localAudio)) return;
var localAudio = connection.jingle.localAudio;
for (var idx = 0; idx < localAudio.getAudioTracks().length; idx++) {
localAudio.getAudioTracks()[idx].enabled = !localAudio.getAudioTracks()[idx].enabled;
}
}
......@@ -1132,6 +1263,8 @@ function showToolbar() {
// TODO: Enable settings functionality. Need to uncomment the settings button in index.html.
// $('#settingsButton').css({visibility:"visible"});
}
// Set desktop sharing method
setDesktopSharing(config.desktopSharing);
}
/*
......@@ -1170,6 +1303,10 @@ function showFocusIndicator() {
var session = connection.jingle.sessions[Object.keys(connection.jingle.sessions)[0]];
var focusId = 'participant_' + Strophe.getResourceFromJid(session.peerjid);
var focusContainer = document.getElementById(focusId);
if(!focusContainer) {
console.error("No focus container!");
return;
}
var indicatorSpan = $('#' + focusId + ' .focusindicator');
if (!indicatorSpan || indicatorSpan.length === 0) {
......
......@@ -10,6 +10,10 @@ var config = {
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)
{
console.log('getroomnode', 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>
......@@ -34,30 +34,24 @@
THE SOFTWARE.
*/
/* jshint -W117 */
ColibriFocus.prototype = Object.create(SessionBase.prototype);
function ColibriFocus(connection, bridgejid) {
this.connection = connection;
SessionBase.call(this, connection, Math.random().toString(36).substr(2, 12));
this.bridgejid = bridgejid;
this.peers = [];
this.confid = null;
this.peerconnection = null;
// media types of the conference
this.media = ['audio', 'video'];
this.sid = Math.random().toString(36).substr(2, 12);
this.connection.jingle.sessions[this.sid] = this;
this.mychannel = [];
this.channels = [];
this.remotessrc = {};
// ssrc lines to be added on next update
this.addssrc = [];
// ssrc lines to be removed on next update
this.removessrc = [];
// pending mute/unmute video op that modify local description
this.pendingop = null;
// container for candidates from the focus
// gathered before confid is known
this.drip_container = [];
......@@ -81,8 +75,12 @@ ColibriFocus.prototype.makeConference = function (peers) {
self.channels.push([]);
});
this.peerconnection = new TraceablePeerConnection(this.connection.jingle.ice_config, this.connection.jingle.pc_constraints);
this.peerconnection.addStream(this.connection.jingle.localStream);
if(connection.jingle.localAudio) {
this.peerconnection.addStream(connection.jingle.localAudio);
}
if(connection.jingle.localVideo) {
this.peerconnection.addStream(connection.jingle.localVideo);
}
this.peerconnection.oniceconnectionstatechange = function (event) {
console.warn('ice connection state changed to', self.peerconnection.iceConnectionState);
/*
......@@ -102,14 +100,10 @@ ColibriFocus.prototype.makeConference = function (peers) {
*/
};
this.peerconnection.onaddstream = function (event) {
self.remoteStream = event.stream;
// search the jid associated with this stream
Object.keys(self.remotessrc).forEach(function (jid) {
if (self.remotessrc[jid].join('\r\n').indexOf('mslabel:' + event.stream.id) != -1) {
event.peerjid = jid;
if (self.connection.jingle.jid2session[jid]) {
self.connection.jingle.jid2session[jid].remotestream = event.stream;
}
}
});
$(document).trigger('remotestreamadded.jingle', [event, self.sid]);
......@@ -151,12 +145,18 @@ ColibriFocus.prototype._makeConference = function () {
var elem = $iq({to: this.bridgejid, type: 'get'});
elem.c('conference', {xmlns: 'http://jitsi.org/protocol/colibri'});
var stream = this.connection.jingle.localStream;
this.media.forEach(function (name) {
elem.c('content', {name: name});
elem.c('channel', {initiator: 'true', expire: '15'}).up();
elem.c('channel', {
initiator: 'true',
expire: '15',
endpoint: 'fix_me_focus_endpoint'}).up();
for (var j = 0; j < self.peers.length; j++) {
elem.c('channel', {initiator: 'true', expire:'15' }).up();
elem.c('channel', {
initiator: 'true',
expire: '15',
endpoint: self.peers[j].substr(1 + self.peers[j].lastIndexOf('/'))
}).up();
}
elem.up(); // end of content
});
......@@ -308,7 +308,8 @@ ColibriFocus.prototype.createdConference = function (result) {
elem.c('channel', {
initiator: 'true',
expire: '15',
id: self.mychannel[channel].attr('id')
id: self.mychannel[channel].attr('id'),
endpoint: 'fix_me_focus_endpoint'
});
// FIXME: should reuse code from .toJingle
......@@ -448,7 +449,8 @@ ColibriFocus.prototype.initiate = function (peer, isInitiator) {
this.connection);
sess.initiate(peer);
sess.colibri = this;
sess.localStream = this.connection.jingle.localStream;
// We do not announce our audio per conference peer, so only video is set here
sess.localVideo = this.connection.jingle.localVideo;
sess.media_constraints = this.connection.jingle.media_constraints;
sess.pc_constraints = this.connection.jingle.pc_constraints;
sess.ice_config = this.connection.jingle.ice_config;
......@@ -497,7 +499,11 @@ ColibriFocus.prototype.addNewParticipant = function (peer) {
localSDP.media.forEach(function (media, channel) {
var name = SDPUtil.parse_mline(media.split('\r\n')[0]).media;
elem.c('content', {name: name});
elem.c('channel', {initiator: 'true', expire:'15'});
elem.c('channel', {
initiator: 'true',
expire:'15',
endpoint: peer.substr(1 + peer.lastIndexOf('/'))
});
elem.up(); // end of channel
elem.up(); // end of content
});
......@@ -524,7 +530,11 @@ ColibriFocus.prototype.updateChannel = function (remoteSDP, participant) {
change.c('conference', {xmlns: 'http://jitsi.org/protocol/colibri', id: this.confid});
for (channel = 0; channel < this.channels[participant].length; channel++) {
change.c('content', {name: channel === 0 ? 'audio' : 'video'});
change.c('channel', {id: $(this.channels[participant][channel]).attr('id')});
change.c('channel', {
id: $(this.channels[participant][channel]).attr('id'),
endpoint: $(this.channels[participant][channel]).attr('endpoint'),
expire: '15'
});
var rtpmap = SDPUtil.find_lines(remoteSDP.media[channel], 'a=rtpmap:');
rtpmap.forEach(function (val) {
......@@ -561,75 +571,92 @@ ColibriFocus.prototype.updateChannel = function (remoteSDP, participant) {
// tell everyone about a new participants a=ssrc lines (isadd is true)
// or a leaving participants a=ssrc lines
// FIXME: should not take an SDP, but rather the a=ssrc lines and probably a=mid
ColibriFocus.prototype.sendSSRCUpdate = function (sdp, jid, isadd) {
ColibriFocus.prototype.sendSSRCUpdate = function (sdpMediaSsrcs, fromJid, isadd) {
var self = this;
this.peers.forEach(function (peerjid) {
if (peerjid == jid) return;
console.log('tell', peerjid, 'about ' + (isadd ? 'new' : 'removed') + ' ssrcs from', jid);
if (peerjid == fromJid) return;
console.log('tell', peerjid, 'about ' + (isadd ? 'new' : 'removed') + ' ssrcs from', fromJid);
if (!self.remotessrc[peerjid]) {
// FIXME: this should only send to participants that are stable, i.e. who have sent a session-accept
// possibly, this.remoteSSRC[session.peerjid] does not exist yet
console.warn('do we really want to bother', peerjid, 'with updates yet?');
}
var channel;
var peersess = self.connection.jingle.jid2session[peerjid];
var modify = $iq({to: peerjid, type: 'set'})
.c('jingle', {
xmlns: 'urn:xmpp:jingle:1',
action: isadd ? 'addsource' : 'removesource',
initiator: peersess.initiator,
sid: peersess.sid
}
);
// FIXME: only announce video ssrcs since we mix audio and dont need
// the audio ssrcs therefore
var modified = false;
for (channel = 0; channel < sdp.media.length; channel++) {
modified = true;
tmp = SDPUtil.find_lines(sdp.media[channel], 'a=ssrc:');
modify.c('content', {name: SDPUtil.parse_mid(SDPUtil.find_line(sdp.media[channel], 'a=mid:'))});
modify.c('source', { xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
// FIXME: not completly sure this operates on blocks and / or handles different ssrcs correctly
tmp.forEach(function (line) {
var idx = line.indexOf(' ');
var linessrc = line.substr(0, idx).substr(7);
modify.attrs({ssrc: linessrc});
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] });
if(!peersess){
console.warn('no session with peer: '+peerjid+' yet...');
return;
}
modify.up();
self.sendSSRCUpdateIq(sdpMediaSsrcs, peersess.sid, peersess.initiator, peerjid, isadd);
});
modify.up(); // end of source
modify.up(); // end of content
};
/**
* Overrides SessionBase.addSource.
*
* @param elem proprietary 'add source' Jingle request(XML node).
* @param fromJid JID of the participant to whom new ssrcs belong.
*/
ColibriFocus.prototype.addSource = function (elem, fromJid) {
var self = this;
this.peerconnection.addSource(elem);
var peerSsrc = this.remotessrc[fromJid];
//console.log("On ADD", self.addssrc, peerSsrc);
this.peerconnection.addssrc.forEach(function(val, idx){
if(!peerSsrc[idx]){
// add ssrc
peerSsrc[idx] = val;
} else {
if(peerSsrc[idx].indexOf(val) == -1){
peerSsrc[idx] = peerSsrc[idx]+val;
}
if (modified) {
self.connection.sendIQ(modify,
function (res) {
console.warn('got modify result');
},
function (err) {
console.warn('got modify error');
}
);
} else {
console.log('modification not necessary');
});
var oldRemoteSdp = new SDP(this.peerconnection.remoteDescription.sdp);
this.modifySources(function(){
// Notify other participants about added ssrc
var remoteSDP = new SDP(self.peerconnection.remoteDescription.sdp);
var newSSRCs = oldRemoteSdp.getNewMedia(remoteSDP);
self.sendSSRCUpdate(newSSRCs, fromJid, true);
});
};
/**
* Overrides SessionBase.removeSource.
*
* @param elem proprietary 'remove source' Jingle request(XML node).
* @param fromJid JID of the participant to whom removed ssrcs belong.
*/
ColibriFocus.prototype.removeSource = function (elem, fromJid) {
var self = this;
this.peerconnection.removeSource(elem);
var peerSsrc = this.remotessrc[fromJid];
//console.log("On REMOVE", self.removessrc, peerSsrc);
this.peerconnection.removessrc.forEach(function(val, idx){
if(peerSsrc[idx]){
// Remove ssrc
peerSsrc[idx] = peerSsrc[idx].replace(val, '');
}
});
var oldSDP = new SDP(self.peerconnection.remoteDescription.sdp);
this.modifySources(function(){
// Notify other participants about removed ssrc
var remoteSDP = new SDP(self.peerconnection.remoteDescription.sdp);
var removedSSRCs = remoteSDP.getNewMedia(oldSDP);
self.sendSSRCUpdate(removedSSRCs, fromJid, false);
});
};
ColibriFocus.prototype.setRemoteDescription = function (session, elem, desctype) {
var participant = this.peers.indexOf(session.peerjid);
console.log('Colibri.setRemoteDescription from', session.peerjid, participant);
var self = this;
var remoteSDP = new SDP('');
var tmp;
var channel;
remoteSDP.fromJingle(elem);
......@@ -637,7 +664,7 @@ ColibriFocus.prototype.setRemoteDescription = function (session, elem, desctype)
this.updateChannel(remoteSDP, participant);
// ACT 2: tell anyone else about the new SSRCs
this.sendSSRCUpdate(remoteSDP, session.peerjid, true);
this.sendSSRCUpdate(remoteSDP.getMediaSsrcMap(), session.peerjid, true);
// ACT 3: note the SSRCs
this.remotessrc[session.peerjid] = [];
......@@ -651,9 +678,11 @@ ColibriFocus.prototype.setRemoteDescription = function (session, elem, desctype)
// ACT 4: add new a=ssrc lines to local remotedescription
for (channel = 0; channel < this.channels[participant].length; channel++) {
//if (channel == 0) continue; FIXME: does not work as intended
if (!this.addssrc[channel]) this.addssrc[channel] = '';
if (SDPUtil.find_lines(remoteSDP.media[channel], 'a=ssrc:').length) {
this.addssrc[channel] += SDPUtil.find_lines(remoteSDP.media[channel], 'a=ssrc:').join('\r\n') + '\r\n';
this.peerconnection.enqueueAddSsrc(
channel,
SDPUtil.find_lines(remoteSDP.media[channel], 'a=ssrc:').join('\r\n') + '\r\n'
);
}
}
this.modifySources();
......@@ -671,7 +700,11 @@ ColibriFocus.prototype.addIceCandidate = function (session, elem) {
var channel = name == 'audio' ? 0 : 1; // FIXME: search mlineindex in localdesc
change.c('content', {name: name});
change.c('channel', {id: $(self.channels[participant][channel]).attr('id')});
change.c('channel', {
id: $(self.channels[participant][channel]).attr('id'),
endpoint: $(self.channels[participant][channel]).attr('endpoint'),
expire: '15'
});
$(this).find('>transport').each(function () {
change.c('transport', {
ufrag: $(this).attr('ufrag'),
......@@ -700,7 +733,7 @@ ColibriFocus.prototype.addIceCandidate = function (session, elem) {
console.log('got result');
},
function (err) {
console.warn('got error');
console.error('got error', err);
}
);
};
......@@ -735,7 +768,11 @@ ColibriFocus.prototype.sendIceCandidates = function (candidates) {
var cands = candidates.filter(function (el) { return el.sdpMLineIndex == mid; });
if (cands.length > 0) {
mycands.c('content', {name: cands[0].sdpMid });
mycands.c('channel', {id: $(this.mychannel[cands[0].sdpMLineIndex]).attr('id')});
mycands.c('channel', {
id: $(this.mychannel[cands[0].sdpMLineIndex]).attr('id'),
endpoint: $(this.mychannel[cands[0].sdpMLineIndex]).attr('endpoint'),
expire: '15'
});
mycands.c('transport', {xmlns: 'urn:xmpp:jingle:transports:ice-udp:1'});
for (var i = 0; i < cands.length; i++) {
mycands.c('candidate', SDPUtil.candidateToJingle(cands[i].candidate)).up();
......@@ -751,7 +788,7 @@ ColibriFocus.prototype.sendIceCandidates = function (candidates) {
console.log('got result');
},
function (err) {
console.warn('got error');
console.error('got error', err);
}
);
};
......@@ -764,8 +801,7 @@ ColibriFocus.prototype.terminate = function (session, reason) {
}
var ssrcs = this.remotessrc[session.peerjid];
for (var i = 0; i < ssrcs.length; i++) {
if (!this.removessrc[i]) this.removessrc[i] = '';
this.removessrc[i] += ssrcs[i];
this.peerconnection.enqueueRemoveSsrc(i, ssrcs[i]);
}
// remove from this.peers
this.peers.splice(participant, 1);
......@@ -774,7 +810,11 @@ ColibriFocus.prototype.terminate = function (session, reason) {
change.c('conference', {xmlns: 'http://jitsi.org/protocol/colibri', id: this.confid});
for (var channel = 0; channel < this.channels[participant].length; channel++) {
change.c('content', {name: channel === 0 ? 'audio' : 'video'});
change.c('channel', {id: $(this.channels[participant][channel]).attr('id'), expire: '0'});
change.c('channel', {
id: $(this.channels[participant][channel]).attr('id'),
endpoint: $(this.channels[participant][channel]).attr('endpoint'),
expire: '0'
});
change.up(); // end of channel
change.up(); // end of content
}
......@@ -783,7 +823,7 @@ ColibriFocus.prototype.terminate = function (session, reason) {
console.log('got result');
},
function (err) {
console.log('got error');
console.error('got error', err);
}
);
// and remove from channels
......@@ -796,138 +836,10 @@ ColibriFocus.prototype.terminate = function (session, reason) {
for (var j = 0; j < ssrcs.length; j++) {
sdp.media[j] = 'a=mid:' + contents[j] + '\r\n';
sdp.media[j] += ssrcs[j];
this.removessrc[j] += ssrcs[j];
this.peerconnection.enqueueRemoveSsrc(j, ssrcs[j]);
}
this.sendSSRCUpdate(sdp, session.peerjid, false);
this.sendSSRCUpdate(sdp.getMediaSsrcMap(), session.peerjid, false);
delete this.remotessrc[session.peerjid];
this.modifySources();
};
ColibriFocus.prototype.modifySources = function () {
var self = this;
if (this.peerconnection.signalingState == 'closed') return;
if (!(this.addssrc.length || this.removessrc.length || this.pendingop !== null)) return;
// FIXME: this is a big hack
// https://code.google.com/p/webrtc/issues/detail?id=2688
if (!(this.peerconnection.signalingState == 'stable' && this.peerconnection.iceConnectionState == 'connected')) {
console.warn('modifySources not yet', this.peerconnection.signalingState, this.peerconnection.iceConnectionState);
window.setTimeout(function () { self.modifySources(); }, 250);
this.wait = true;
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 () {
console.log('setModifiedRemoteDescription ok');
self.peerconnection.createAnswer(
function (modifiedAnswer) {
console.log('modifiedAnswer created');
// 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;
}
// FIXME: pushing down an answer while ice connection state
// is still checking is bad...
//console.log(self.peerconnection.iceConnectionState);
// trying to work around another chrome bug
//modifiedAnswer.sdp = modifiedAnswer.sdp.replace(/a=setup:active/g, 'a=setup:actpass');
self.peerconnection.setLocalDescription(modifiedAnswer,
function () {
console.log('setModifiedLocalDescription ok');
$(document).trigger('setLocalDescription.jingle', [self.sid]);
},
function (error) {
console.log('setModifiedLocalDescription failed', error);
}
);
},
function (error) {
console.log('createModifiedAnswer failed', error);
}
);
},
function (error) {
console.log('setModifiedRemoteDescription failed', error);
}
);
/*
* now that we have a passive focus, this way is bad again! :-)
this.peerconnection.createOffer(
function (modifiedOffer) {
console.log('created (un)modified offer');
self.peerconnection.setLocalDescription(modifiedOffer,
function () {
console.log('setModifiedLocalDescription ok');
self.peerconnection.setRemoteDescription(
new RTCSessionDescription({type: 'answer', sdp: sdp.raw }),
function () {
console.log('setModifiedRemoteDescription ok');
},
function (error) {
console.log('setModifiedRemoteDescription failed');
}
);
$(document).trigger('setLocalDescription.jingle', [self.sid]);
},
function (error) {
console.log('setModifiedLocalDescription failed');
}
);
},
function (error) {
console.log('creating (un)modified offerfailed');
}
);
*/
};
ColibriFocus.prototype.hardMuteVideo = function (muted) {
this.pendingop = muted ? 'mute' : 'unmute';
this.modifySources();
this.connection.jingle.localStream.getVideoTracks().forEach(function (track) {
track.enabled = !muted;
});
};
......@@ -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);
};
......
......@@ -7,6 +7,30 @@ function TraceablePeerConnection(ice_config, constraints) {
this.statsinterval = null;
this.maxstats = 300; // limit to 300 values, i.e. 5 minutes; set to 0 to disable
/**
* Array of ssrcs that will be added on next modifySources call.
* @type {Array}
*/
this.addssrc = [];
/**
* Array of ssrcs that will be added on next modifySources call.
* @type {Array}
*/
this.removessrc = [];
/**
* Pending operation that will be done during modifySources call.
* Currently 'mute'/'unmute' operations are supported.
*
* @type {String}
*/
this.pendingop = null;
/**
* Flag indicates that peer connection stream have changed and modifySources should proceed.
* @type {boolean}
*/
this.switchstreams = false;
// override as desired
this.trace = function(what, info) {
//console.warn('WTRACE', what, info);
......@@ -163,6 +187,196 @@ TraceablePeerConnection.prototype.setRemoteDescription = function (description,
*/
};
TraceablePeerConnection.prototype.hardMuteVideo = function (muted) {
this.pendingop = muted ? 'mute' : 'unmute';
};
TraceablePeerConnection.prototype.enqueueAddSsrc = function(channel, ssrcLines) {
if (!this.addssrc[channel]) {
this.addssrc[channel] = '';
}
this.addssrc[channel] += ssrcLines;
}
TraceablePeerConnection.prototype.addSource = function (elem) {
console.log('addssrc', new Date().getTime());
console.log('ice', this.iceConnectionState);
var sdp = new SDP(this.remoteDescription.sdp);
var mySdp = new SDP(this.peerconnection.localDescription.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');
if(mySdp.containsSSRC(ssrc)){
/**
* This happens when multiple participants change their streams at the same time and
* ColibriFocus.modifySources have to wait for stable state. In the meantime multiple
* addssrc are scheduled for update IQ. See
*/
console.warn("Got add stream request for my own ssrc: "+ssrc);
return;
}
$(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;
self.enqueueAddSsrc(idx, lines);
});
sdp.raw = sdp.session + sdp.media.join('');
});
};
TraceablePeerConnection.prototype.enqueueRemoveSsrc = function(channel, ssrcLines) {
if (!this.removessrc[channel]){
this.removessrc[channel] = '';
}
this.removessrc[channel] += ssrcLines;
}
TraceablePeerConnection.prototype.removeSource = function (elem) {
console.log('removessrc', new Date().getTime());
console.log('ice', this.iceConnectionState);
var sdp = new SDP(this.remoteDescription.sdp);
var mySdp = new SDP(this.peerconnection.localDescription.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 should never happen, but can be useful for bug detection
if(mySdp.containsSSRC(ssrc)){
console.error("Got remove stream request for my own ssrc: "+ssrc);
return;
}
$(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;
self.enqueueRemoveSsrc(idx, lines);
});
sdp.raw = sdp.session + sdp.media.join('');
});
};
TraceablePeerConnection.prototype.modifySources = function(successCallback) {
var self = this;
if (this.signalingState == 'closed') return;
if (!(this.addssrc.length || this.removessrc.length || this.pendingop !== null || this.switchstreams)){
// There is nothing to do since scheduled job might have been executed by another succeeding call
if(successCallback){
successCallback();
}
return;
}
// FIXME: this is a big hack
// https://code.google.com/p/webrtc/issues/detail?id=2688
if (!(this.signalingState == 'stable' && this.iceConnectionState == 'connected')) {
console.warn('modifySources not yet', this.signalingState, this.iceConnectionState);
this.wait = true;
window.setTimeout(function() { self.modifySources(successCallback); }, 250);
return;
}
if (this.wait) {
window.setTimeout(function() { self.modifySources(successCallback); }, 2500);
this.wait = false;
return;
}
// Reset switch streams flag
this.switchstreams = false;
var sdp = new SDP(this.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.setRemoteDescription(new RTCSessionDescription({type: 'offer', sdp: sdp.raw}),
function() {
self.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;
}
// FIXME: pushing down an answer while ice connection state
// is still checking is bad...
//console.log(self.peerconnection.iceConnectionState);
// trying to work around another chrome bug
//modifiedAnswer.sdp = modifiedAnswer.sdp.replace(/a=setup:active/g, 'a=setup:actpass');
self.setLocalDescription(modifiedAnswer,
function() {
//console.log('modified setLocalDescription ok');
if(successCallback){
successCallback();
}
},
function(error) {
console.error('modified setLocalDescription failed', error);
}
);
},
function(error) {
console.error('modified answer failed', error);
}
);
},
function(error) {
console.error('modify failed', error);
}
);
};
TraceablePeerConnection.prototype.close = function () {
this.trace('stop');
if (this.statsinterval !== null) {
......@@ -286,7 +500,7 @@ function setupRTC() {
return RTC;
}
function getUserMediaWithConstraints(um, resolution, bandwidth, fps) {
function getUserMediaWithConstraints(um, success_callback, failure_callback, resolution, bandwidth, fps, desktopStream) {
var constraints = {audio: false, video: false};
if (um.indexOf('video') >= 0) {
......@@ -297,11 +511,25 @@ function getUserMediaWithConstraints(um, resolution, bandwidth, fps) {
}
if (um.indexOf('screen') >= 0) {
constraints.video = {
"mandatory": {
"chromeMediaSource": "screen"
mandatory: {
chromeMediaSource: "screen",
maxWidth: window.screen.width,
maxHeight: window.screen.height,
maxFrameRate: 3
}
};
}
if (um.indexOf('desktop') >= 0) {
constraints.video = {
mandatory: {
chromeMediaSource: "desktop",
chromeMediaSourceId: desktopStream,
maxWidth: window.screen.width,
maxHeight: window.screen.height,
maxFrameRate: 3
}
}
}
if (resolution && !constraints.video) {
constraints.video = {mandatory: {}};// same behaviour as true
......@@ -368,14 +596,18 @@ function getUserMediaWithConstraints(um, resolution, bandwidth, fps) {
RTC.getUserMedia(constraints,
function (stream) {
console.log('onUserMediaSuccess');
$(document).trigger('mediaready.jingle', [stream]);
success_callback(stream);
},
function (error) {
console.warn('Failed to get access to local media. Error ', error);
$(document).trigger('mediafailure.jingle');
if(failure_callback) {
failure_callback(error);
}
});
} catch (e) {
console.error('GUM failed: ', e);
$(document).trigger('mediafailure.jingle');
if(failure_callback) {
failure_callback(e);
}
}
}
\ No newline at end of file
......@@ -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;
......
......@@ -11,7 +11,75 @@ function SDP(sdp) {
this.session = this.media.shift() + '\r\n';
this.raw = this.session + this.media.join('');
}
/**
* Returns map of MediaChannel mapped per channel idx.
*/
SDP.prototype.getMediaSsrcMap = function() {
var self = this;
var media_ssrcs = {};
for (channelNum = 0; channelNum < self.media.length; channelNum++) {
modified = true;
tmp = SDPUtil.find_lines(self.media[channelNum], 'a=ssrc:');
var type = SDPUtil.parse_mid(SDPUtil.find_line(self.media[channelNum], 'a=mid:'));
var channel = new MediaChannel(channelNum, type);
media_ssrcs[channelNum] = channel;
tmp.forEach(function (line) {
var linessrc = line.substring(7).split(' ')[0];
// allocate new ChannelSsrc
if(!channel.ssrcs[linessrc]) {
channel.ssrcs[linessrc] = new ChannelSsrc(linessrc, type);
}
channel.ssrcs[linessrc].lines.push(line);
});
}
return media_ssrcs;
}
/**
* Returns <tt>true</tt> if this SDP contains given SSRC.
* @param ssrc the ssrc to check.
* @returns {boolean} <tt>true</tt> if this SDP contains given SSRC.
*/
SDP.prototype.containsSSRC = function(ssrc) {
var channels = this.getMediaSsrcMap();
var contains = false;
Object.keys(channels).forEach(function(chNumber){
var channel = channels[chNumber];
//console.log("Check", channel, ssrc);
if(Object.keys(channel.ssrcs).indexOf(ssrc) != -1){
contains = true;
}
});
return contains;
}
/**
* Returns map of MediaChannel that contains only media not contained in <tt>otherSdp</tt>. Mapped by channel idx.
* @param otherSdp the other SDP to check ssrc with.
*/
SDP.prototype.getNewMedia = function(otherSdp) {
var myMedia = this.getMediaSsrcMap();
var othersMedia = otherSdp.getMediaSsrcMap();
var newMedia = {};
Object.keys(othersMedia).forEach(function(channelNum) {
var myChannel = myMedia[channelNum];
var othersChannel = othersMedia[channelNum];
if(!myChannel && othersChannel) {
// Add whole channel
newMedia[channelNum] = othersChannel;
return;
}
// Look for new ssrcs accross the channel
Object.keys(othersChannel.ssrcs).forEach(function(ssrc) {
if(Object.keys(myChannel.ssrcs).indexOf(ssrc) === -1) {
// Allocate channel if we've found ssrc that doesn't exist in our channel
if(!newMedia[channelNum]){
newMedia[channelNum] = new MediaChannel(othersChannel.chNumber, othersChannel.mediaType);
}
newMedia[channelNum].ssrcs[ssrc] = othersChannel.ssrcs[ssrc];
}
})
});
return newMedia;
}
// remove iSAC and CN from SDP
SDP.prototype.mangle = function () {
var i, j, mline, lines, rtpmap, newdesc;
......@@ -486,316 +554,3 @@ SDP.prototype.jingle2media = function (content) {
}
return media;
};
\ No newline at end of file
SDPUtil = {
iceparams: function (mediadesc, sessiondesc) {
var data = null;
if (SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc) &&
SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc)) {
data = {
ufrag: SDPUtil.parse_iceufrag(SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc)),
pwd: SDPUtil.parse_icepwd(SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc))
};
}
return data;
},
parse_iceufrag: function (line) {
return line.substring(12);
},
build_iceufrag: function (frag) {
return 'a=ice-ufrag:' + frag;
},
parse_icepwd: function (line) {
return line.substring(10);
},
build_icepwd: function (pwd) {
return 'a=ice-pwd:' + pwd;
},
parse_mid: function (line) {
return line.substring(6);
},
parse_mline: function (line) {
var parts = line.substring(2).split(' '),
data = {};
data.media = parts.shift();
data.port = parts.shift();
data.proto = parts.shift();
if (parts[parts.length - 1] === '') { // trailing whitespace
parts.pop();
}
data.fmt = parts;
return data;
},
build_mline: function (mline) {
return 'm=' + mline.media + ' ' + mline.port + ' ' + mline.proto + ' ' + mline.fmt.join(' ');
},
parse_rtpmap: function (line) {
var parts = line.substring(9).split(' '),
data = {};
data.id = parts.shift();
parts = parts[0].split('/');
data.name = parts.shift();
data.clockrate = parts.shift();
data.channels = parts.length ? parts.shift() : '1';
return data;
},
build_rtpmap: function (el) {
var line = 'a=rtpmap:' + el.getAttribute('id') + ' ' + el.getAttribute('name') + '/' + el.getAttribute('clockrate');
if (el.getAttribute('channels') && el.getAttribute('channels') != '1') {
line += '/' + el.getAttribute('channels');
}
return line;
},
parse_crypto: function (line) {
var parts = line.substring(9).split(' '),
data = {};
data.tag = parts.shift();
data['crypto-suite'] = parts.shift();
data['key-params'] = parts.shift();
if (parts.length) {
data['session-params'] = parts.join(' ');
}
return data;
},
parse_fingerprint: function (line) { // RFC 4572
var parts = line.substring(14).split(' '),
data = {};
data.hash = parts.shift();
data.fingerprint = parts.shift();
// TODO assert that fingerprint satisfies 2UHEX *(":" 2UHEX) ?
return data;
},
parse_fmtp: function (line) {
var parts = line.split(' '),
i, key, value,
data = [];
parts.shift();
parts = parts.join(' ').split(';');
for (i = 0; i < parts.length; i++) {
key = parts[i].split('=')[0];
while (key.length && key[0] == ' ') {
key = key.substring(1);
}
value = parts[i].split('=')[1];
if (key && value) {
data.push({name: key, value: value});
} else if (key) {
// rfc 4733 (DTMF) style stuff
data.push({name: '', value: key});
}
}
return data;
},
parse_icecandidate: function (line) {
var candidate = {},
elems = line.split(' ');
candidate.foundation = elems[0].substring(12);
candidate.component = elems[1];
candidate.protocol = elems[2].toLowerCase();
candidate.priority = elems[3];
candidate.ip = elems[4];
candidate.port = elems[5];
// elems[6] => "typ"
candidate.type = elems[7];
candidate.generation = 0; // default value, may be overwritten below
for (var i = 8; i < elems.length; i += 2) {
switch (elems[i]) {
case 'raddr':
candidate['rel-addr'] = elems[i + 1];
break;
case 'rport':
candidate['rel-port'] = elems[i + 1];
break;
case 'generation':
candidate.generation = elems[i + 1];
break;
default: // TODO
console.log('parse_icecandidate not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
}
}
candidate.network = '1';
candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
return candidate;
},
build_icecandidate: function (cand) {
var line = ['a=candidate:' + cand.foundation, cand.component, cand.protocol, cand.priority, cand.ip, cand.port, 'typ', cand.type].join(' ');
line += ' ';
switch (cand.type) {
case 'srflx':
case 'prflx':
case 'relay':
if (cand.hasOwnAttribute('rel-addr') && cand.hasOwnAttribute('rel-port')) {
line += 'raddr';
line += ' ';
line += cand['rel-addr'];
line += ' ';
line += 'rport';
line += ' ';
line += cand['rel-port'];
line += ' ';
}
break;
}
line += 'generation';
line += ' ';
line += cand.hasOwnAttribute('generation') ? cand.generation : '0';
return line;
},
parse_ssrc: function (desc) {
// proprietary mapping of a=ssrc lines
// TODO: see "Jingle RTP Source Description" by Juberti and P. Thatcher on google docs
// and parse according to that
var lines = desc.split('\r\n'),
data = {};
for (var i = 0; i < lines.length; i++) {
if (lines[i].substring(0, 7) == 'a=ssrc:') {
var idx = lines[i].indexOf(' ');
data[lines[i].substr(idx + 1).split(':', 2)[0]] = lines[i].substr(idx + 1).split(':', 2)[1];
}
}
return data;
},
parse_rtcpfb: function (line) {
var parts = line.substr(10).split(' ');
var data = {};
data.pt = parts.shift();
data.type = parts.shift();
data.params = parts;
return data;
},
parse_extmap: function (line) {
var parts = line.substr(9).split(' ');
var data = {};
data.value = parts.shift();
if (data.value.indexOf('/') != -1) {
data.direction = data.value.substr(data.value.indexOf('/') + 1);
data.value = data.value.substr(0, data.value.indexOf('/'));
} else {
data.direction = 'both';
}
data.uri = parts.shift();
data.params = parts;
return data;
},
find_line: function (haystack, needle, sessionpart) {
var lines = haystack.split('\r\n');
for (var i = 0; i < lines.length; i++) {
if (lines[i].substring(0, needle.length) == needle) {
return lines[i];
}
}
if (!sessionpart) {
return false;
}
// search session part
lines = sessionpart.split('\r\n');
for (var j = 0; j < lines.length; j++) {
if (lines[j].substring(0, needle.length) == needle) {
return lines[j];
}
}
return false;
},
find_lines: function (haystack, needle, sessionpart) {
var lines = haystack.split('\r\n'),
needles = [];
for (var i = 0; i < lines.length; i++) {
if (lines[i].substring(0, needle.length) == needle)
needles.push(lines[i]);
}
if (needles.length || !sessionpart) {
return needles;
}
// search session part
lines = sessionpart.split('\r\n');
for (var j = 0; j < lines.length; j++) {
if (lines[j].substring(0, needle.length) == needle) {
needles.push(lines[j]);
}
}
return needles;
},
candidateToJingle: function (line) {
// a=candidate:2979166662 1 udp 2113937151 192.168.2.100 57698 typ host generation 0
// <candidate component=... foundation=... generation=... id=... ip=... network=... port=... priority=... protocol=... type=.../>
if (line.substring(0, 12) != 'a=candidate:') {
console.log('parseCandidate called with a line that is not a candidate line');
console.log(line);
return null;
}
if (line.substring(line.length - 2) == '\r\n') // chomp it
line = line.substring(0, line.length - 2);
var candidate = {},
elems = line.split(' '),
i;
if (elems[6] != 'typ') {
console.log('did not find typ in the right place');
console.log(line);
return null;
}
candidate.foundation = elems[0].substring(12);
candidate.component = elems[1];
candidate.protocol = elems[2].toLowerCase();
candidate.priority = elems[3];
candidate.ip = elems[4];
candidate.port = elems[5];
// elems[6] => "typ"
candidate.type = elems[7];
for (i = 8; i < elems.length; i += 2) {
switch (elems[i]) {
case 'raddr':
candidate['rel-addr'] = elems[i + 1];
break;
case 'rport':
candidate['rel-port'] = elems[i + 1];
break;
case 'generation':
candidate.generation = elems[i + 1];
break;
default: // TODO
console.log('not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
}
}
candidate.network = '1';
candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
return candidate;
},
candidateFromJingle: function (cand) {
var line = 'a=candidate:';
line += cand.getAttribute('foundation');
line += ' ';
line += cand.getAttribute('component');
line += ' ';
line += cand.getAttribute('protocol'); //.toUpperCase(); // chrome M23 doesn't like this
line += ' ';
line += cand.getAttribute('priority');
line += ' ';
line += cand.getAttribute('ip');
line += ' ';
line += cand.getAttribute('port');
line += ' ';
line += 'typ';
line += ' ' + cand.getAttribute('type');
line += ' ';
switch (cand.getAttribute('type')) {
case 'srflx':
case 'prflx':
case 'relay':
if (cand.getAttribute('rel-addr') && cand.getAttribute('rel-port')) {
line += 'raddr';
line += ' ';
line += cand.getAttribute('rel-addr');
line += ' ';
line += 'rport';
line += ' ';
line += cand.getAttribute('rel-port');
line += ' ';
}
break;
}
line += 'generation';
line += ' ';
line += cand.getAttribute('generation') || '0';
return line + '\r\n';
}
};
/**
* Contains utility classes used in SDP class.
*
*/
/**
* Class holds a=ssrc lines and media type a=mid
* @param ssrc synchronization source identifier number(a=ssrc lines from SDP)
* @param type media type eg. "audio" or "video"(a=mid frm SDP)
* @constructor
*/
function ChannelSsrc(ssrc, type) {
this.ssrc = ssrc;
this.type = type;
this.lines = [];
}
/**
* Helper class represents media channel. Is a container for ChannelSsrc, holds channel idx and media type.
* @param channelNumber channel idx in SDP media array.
* @param mediaType media type(a=mid)
* @constructor
*/
function MediaChannel(channelNumber, mediaType) {
/**
* SDP channel number
* @type {*}
*/
this.chNumber = channelNumber;
/**
* Channel media type(a=mid)
* @type {*}
*/
this.mediaType = mediaType;
/**
* The maps of ssrc numbers to ChannelSsrc objects.
*/
this.ssrcs = {};
}
SDPUtil = {
iceparams: function (mediadesc, sessiondesc) {
var data = null;
if (SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc) &&
SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc)) {
data = {
ufrag: SDPUtil.parse_iceufrag(SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc)),
pwd: SDPUtil.parse_icepwd(SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc))
};
}
return data;
},
parse_iceufrag: function (line) {
return line.substring(12);
},
build_iceufrag: function (frag) {
return 'a=ice-ufrag:' + frag;
},
parse_icepwd: function (line) {
return line.substring(10);
},
build_icepwd: function (pwd) {
return 'a=ice-pwd:' + pwd;
},
parse_mid: function (line) {
return line.substring(6);
},
parse_mline: function (line) {
var parts = line.substring(2).split(' '),
data = {};
data.media = parts.shift();
data.port = parts.shift();
data.proto = parts.shift();
if (parts[parts.length - 1] === '') { // trailing whitespace
parts.pop();
}
data.fmt = parts;
return data;
},
build_mline: function (mline) {
return 'm=' + mline.media + ' ' + mline.port + ' ' + mline.proto + ' ' + mline.fmt.join(' ');
},
parse_rtpmap: function (line) {
var parts = line.substring(9).split(' '),
data = {};
data.id = parts.shift();
parts = parts[0].split('/');
data.name = parts.shift();
data.clockrate = parts.shift();
data.channels = parts.length ? parts.shift() : '1';
return data;
},
build_rtpmap: function (el) {
var line = 'a=rtpmap:' + el.getAttribute('id') + ' ' + el.getAttribute('name') + '/' + el.getAttribute('clockrate');
if (el.getAttribute('channels') && el.getAttribute('channels') != '1') {
line += '/' + el.getAttribute('channels');
}
return line;
},
parse_crypto: function (line) {
var parts = line.substring(9).split(' '),
data = {};
data.tag = parts.shift();
data['crypto-suite'] = parts.shift();
data['key-params'] = parts.shift();
if (parts.length) {
data['session-params'] = parts.join(' ');
}
return data;
},
parse_fingerprint: function (line) { // RFC 4572
var parts = line.substring(14).split(' '),
data = {};
data.hash = parts.shift();
data.fingerprint = parts.shift();
// TODO assert that fingerprint satisfies 2UHEX *(":" 2UHEX) ?
return data;
},
parse_fmtp: function (line) {
var parts = line.split(' '),
i, key, value,
data = [];
parts.shift();
parts = parts.join(' ').split(';');
for (i = 0; i < parts.length; i++) {
key = parts[i].split('=')[0];
while (key.length && key[0] == ' ') {
key = key.substring(1);
}
value = parts[i].split('=')[1];
if (key && value) {
data.push({name: key, value: value});
} else if (key) {
// rfc 4733 (DTMF) style stuff
data.push({name: '', value: key});
}
}
return data;
},
parse_icecandidate: function (line) {
var candidate = {},
elems = line.split(' ');
candidate.foundation = elems[0].substring(12);
candidate.component = elems[1];
candidate.protocol = elems[2].toLowerCase();
candidate.priority = elems[3];
candidate.ip = elems[4];
candidate.port = elems[5];
// elems[6] => "typ"
candidate.type = elems[7];
candidate.generation = 0; // default value, may be overwritten below
for (var i = 8; i < elems.length; i += 2) {
switch (elems[i]) {
case 'raddr':
candidate['rel-addr'] = elems[i + 1];
break;
case 'rport':
candidate['rel-port'] = elems[i + 1];
break;
case 'generation':
candidate.generation = elems[i + 1];
break;
default: // TODO
console.log('parse_icecandidate not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
}
}
candidate.network = '1';
candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
return candidate;
},
build_icecandidate: function (cand) {
var line = ['a=candidate:' + cand.foundation, cand.component, cand.protocol, cand.priority, cand.ip, cand.port, 'typ', cand.type].join(' ');
line += ' ';
switch (cand.type) {
case 'srflx':
case 'prflx':
case 'relay':
if (cand.hasOwnAttribute('rel-addr') && cand.hasOwnAttribute('rel-port')) {
line += 'raddr';
line += ' ';
line += cand['rel-addr'];
line += ' ';
line += 'rport';
line += ' ';
line += cand['rel-port'];
line += ' ';
}
break;
}
line += 'generation';
line += ' ';
line += cand.hasOwnAttribute('generation') ? cand.generation : '0';
return line;
},
parse_ssrc: function (desc) {
// proprietary mapping of a=ssrc lines
// TODO: see "Jingle RTP Source Description" by Juberti and P. Thatcher on google docs
// and parse according to that
var lines = desc.split('\r\n'),
data = {};
for (var i = 0; i < lines.length; i++) {
if (lines[i].substring(0, 7) == 'a=ssrc:') {
var idx = lines[i].indexOf(' ');
data[lines[i].substr(idx + 1).split(':', 2)[0]] = lines[i].substr(idx + 1).split(':', 2)[1];
}
}
return data;
},
parse_rtcpfb: function (line) {
var parts = line.substr(10).split(' ');
var data = {};
data.pt = parts.shift();
data.type = parts.shift();
data.params = parts;
return data;
},
parse_extmap: function (line) {
var parts = line.substr(9).split(' ');
var data = {};
data.value = parts.shift();
if (data.value.indexOf('/') != -1) {
data.direction = data.value.substr(data.value.indexOf('/') + 1);
data.value = data.value.substr(0, data.value.indexOf('/'));
} else {
data.direction = 'both';
}
data.uri = parts.shift();
data.params = parts;
return data;
},
find_line: function (haystack, needle, sessionpart) {
var lines = haystack.split('\r\n');
for (var i = 0; i < lines.length; i++) {
if (lines[i].substring(0, needle.length) == needle) {
return lines[i];
}
}
if (!sessionpart) {
return false;
}
// search session part
lines = sessionpart.split('\r\n');
for (var j = 0; j < lines.length; j++) {
if (lines[j].substring(0, needle.length) == needle) {
return lines[j];
}
}
return false;
},
find_lines: function (haystack, needle, sessionpart) {
var lines = haystack.split('\r\n'),
needles = [];
for (var i = 0; i < lines.length; i++) {
if (lines[i].substring(0, needle.length) == needle)
needles.push(lines[i]);
}
if (needles.length || !sessionpart) {
return needles;
}
// search session part
lines = sessionpart.split('\r\n');
for (var j = 0; j < lines.length; j++) {
if (lines[j].substring(0, needle.length) == needle) {
needles.push(lines[j]);
}
}
return needles;
},
candidateToJingle: function (line) {
// a=candidate:2979166662 1 udp 2113937151 192.168.2.100 57698 typ host generation 0
// <candidate component=... foundation=... generation=... id=... ip=... network=... port=... priority=... protocol=... type=.../>
if (line.substring(0, 12) != 'a=candidate:') {
console.log('parseCandidate called with a line that is not a candidate line');
console.log(line);
return null;
}
if (line.substring(line.length - 2) == '\r\n') // chomp it
line = line.substring(0, line.length - 2);
var candidate = {},
elems = line.split(' '),
i;
if (elems[6] != 'typ') {
console.log('did not find typ in the right place');
console.log(line);
return null;
}
candidate.foundation = elems[0].substring(12);
candidate.component = elems[1];
candidate.protocol = elems[2].toLowerCase();
candidate.priority = elems[3];
candidate.ip = elems[4];
candidate.port = elems[5];
// elems[6] => "typ"
candidate.type = elems[7];
for (i = 8; i < elems.length; i += 2) {
switch (elems[i]) {
case 'raddr':
candidate['rel-addr'] = elems[i + 1];
break;
case 'rport':
candidate['rel-port'] = elems[i + 1];
break;
case 'generation':
candidate.generation = elems[i + 1];
break;
default: // TODO
console.log('not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
}
}
candidate.network = '1';
candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
return candidate;
},
candidateFromJingle: function (cand) {
var line = 'a=candidate:';
line += cand.getAttribute('foundation');
line += ' ';
line += cand.getAttribute('component');
line += ' ';
line += cand.getAttribute('protocol'); //.toUpperCase(); // chrome M23 doesn't like this
line += ' ';
line += cand.getAttribute('priority');
line += ' ';
line += cand.getAttribute('ip');
line += ' ';
line += cand.getAttribute('port');
line += ' ';
line += 'typ';
line += ' ' + cand.getAttribute('type');
line += ' ';
switch (cand.getAttribute('type')) {
case 'srflx':
case 'prflx':
case 'relay':
if (cand.getAttribute('rel-addr') && cand.getAttribute('rel-port')) {
line += 'raddr';
line += ' ';
line += cand.getAttribute('rel-addr');
line += ' ';
line += 'rport';
line += ' ';
line += cand.getAttribute('rel-port');
line += ' ';
}
break;
}
line += 'generation';
line += ' ';
line += cand.getAttribute('generation') || '0';
return line + '\r\n';
}
};
/* 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>
<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 service only works with Chrome.</p>
<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>
</div>
<!-- wrap ends here -->
</div>
</body>
<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) {
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"
}
};
}
......@@ -1008,6 +1017,10 @@ function updateChatConversation(nick, message)
if (nickname == nick)
divClassName = "In";
else {
unreadMessages++;
setVisualNotification(true);
}
var content = '<div class="message message' + divClassName + '">'
+'<span class="msgText">' + setEmoticons(message) + '</span>'
......@@ -1378,7 +1391,38 @@ function linkify(inputText)
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
/**
* 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 org.ebml.util.*;
/**
* EBMLReader.java
*
* Created on November 18, 2002, 4:03 PM
*
* @version 1.0
*/
/**
* <h1>JEBML Intro</h1>
* <hr>
* <p>The following are the basic steps of how reading in JEBML works.</p>
* <ul>
* <li>1. The EbmlReader class reads the element header (id+size) and
* looks it up in the supplied DocType class.
* <li>2. The correct element type (Binary, UInteger, String, etc) is
* created using the DocType data, BinaryElement is default
* element type for unknown elements.
* <li>3. The MatroskaDocType has the ids of all the elements staticly
* declared.
* <br>So to easily find out what an element is, you can use some code
* like the following code
* <p>
* <code>
* Element level1; <br>
* // ... fill level1 <br>
* if (level1.equals(MatroskaDocType.SegmentInfo_Id)) { <br>
* // Your Code Here <br>
* } <br>
* </code>
* </p>
* <li>4. To get the actual data for an Element you call the readData
* method, if you just want to skip it use skipData().
* <li>5. MasterElements are special, they have the readNextChild()
* method which returns the next child element, returning null
* when all the children have been read (it keeps track of the
* current inputstream position).
* <li>The usage method for JEBML is very close to libebml/libmatroska.
* </ul>
* <hr>
*
* Reads EBML elements from a <code>DataSource</code> and looks them up
* in the provided <code>DocType</code>.
*
* @author (c) 2002 John Cannon
* @author (c) 2004 Jory Stone
*/
public class EBMLReader {
protected DataSource source;
protected DocType doc;
protected ElementType elementTypes;
protected ElementType lastElementType;
/** 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 EBMLReader(DataSource source, DocType doc) {
this.source = source;
this.doc = doc;
this.elementTypes = doc.getElements();
}
public Element readNextElement() {
//Read the type.
byte[] elementType = readEBMLCodeAsBytes(source);
if (elementType == null)
// Failed to read type id
return null;
//Read the size.
byte[] data = getEBMLCodeAsBytes(source);
long elementSize = parseEBMLCode(data);
if (elementSize == 0)
// Zero sized element is valid
;//return null;
Element elem = null;
//lastElementType = elementTypes.findElement(elementType);
//if (lastElementType == null) {
// lastElementType = new UnknownElementType(elementType);
//}
//elem = doc.createElement(lastElementType);
elem = doc.createElement(elementType);
if (elem == null) {
return null;
}
//Set it's size
elem.setSize(elementSize);
elem.setHeaderSize(data.length);
//Setup a buffer for it's data
//byte[] elementData = new byte[(int)elementSize];
//Read the data
//source.read(elementData, 0, elementData.length);
//Set the data property on the element
//elem.setData(elementData);
//System.out.println("EBMLReader.readNextElement() returning element " + elem.getElementType().name + " with size " + Long.toString(elem.getTotalSize()-elementSize)+" "+Long.toString(elementSize));
//Return the element
return elem;
}
static public byte[] getEBMLCodeAsBytes(DataSource source) {
//Begin loop with byte set to newly read byte.
byte firstByte = source.readByte();
int numBytes = 0;
//Begin by counting the bits unset before the first '1'.
long mask = 0x0080;
for (int i = 0; i < 8; i++) {
//Start at left, shift to right.
if ((firstByte & mask) == mask) { //One found
//Set number of bytes in size = i+1 ( we must count the 1 too)
numBytes = i + 1;
//exit loop by pushing i out of the limit
i = 8;
}
mask >>>= 1;
}
if (numBytes == 0)
// Invalid size
return null;
//Setup space to store the bits
byte[] data = new byte[numBytes];
//Clear the 1 at the front of this byte, all the way to the beginning of the size
data[0] = (byte)(firstByte & ((0xFF >>> (numBytes))));
if (numBytes > 1) {
//Read the rest of the size.
source.read(data, 1, numBytes - 1);
}
return data;
}
/**
* Reads an (Unsigned) EBML code from the DataSource and encodes it into a long. This size should be
* cast into an int for actual use as Java only allows upto 32-bit file I/O operations.
*
* @return ebml size
*/
static public long readEBMLCode(DataSource source) {
//Begin loop with byte set to newly read byte.
byte firstByte = source.readByte();
int numBytes = 0;
//Begin by counting the bits unset before the first '1'.
long mask = 0x0080;
for (int i = 0; i < 8; i++) {
//Start at left, shift to right.
if ((firstByte & mask) == mask) { //One found
//Set number of bytes in size = i+1 ( we must count the 1 too)
numBytes = i + 1;
//exit loop by pushing i out of the limit
i = 8;
}
mask >>>= 1;
}
if (numBytes == 0)
// Invalid size
return 0;
//Setup space to store the bits
byte[] data = new byte[numBytes];
//Clear the 1 at the front of this byte, all the way to the beginning of the size
data[0] = (byte)(firstByte & ((0xFF >>> (numBytes))));
if (numBytes > 1) {
//Read the rest of the size.
source.read(data, 1, numBytes - 1);
}
//Put this into a long
long size = 0;
long n = 0;
for (int i = 0; i < numBytes; i++) {
n = ((long)data[numBytes - 1 - i] << 56) >>> 56;
size = size | (n << (8 * i));
}
return size;
}
public static long parseEBMLCode(byte[] data) {
if(data==null)
return 0;
//Put this into a long
long size = 0;
long n = 0;
for (int i = 0; i < data.length; i++) {
n = ((long)data[data.length - 1 - i] << 56) >>> 56;
size = size | (n << (8 * i));
}
return size;
}
/**
* Reads an (Unsigned) EBML code from the DataSource and encodes it into a long. This size should be
* cast into an int for actual use as Java only allows upto 32-bit file I/O operations.
*
* @return ebml size
*/
static public long readEBMLCode(byte [] source)
{
return readEBMLCode(source, 0);
}
/**
* Reads an (Unsigned) EBML code from the DataSource and encodes it into a long. This size should be
* cast into an int for actual use as Java only allows upto 32-bit file I/O operations.
*
* @return ebml size
*/
static public long readEBMLCode(byte [] source, int offset)
{
//Begin loop with byte set to newly read byte.
byte firstByte = source[offset];
int numBytes = 0;
//Begin by counting the bits unset before the first '1'.
long mask = 0x0080;
for (int i = 0; i < 8; i++)
{
//Start at left, shift to right.
if ((firstByte & mask) == mask)
{ //One found
//Set number of bytes in size = i+1 ( we must count the 1 too)
numBytes = i + 1;
//exit loop by pushing i out of the limit
i = 8;
}
mask >>>= 1;
}
if (numBytes == 0)
// Invalid size
return 0;
//Setup space to store the bits
byte[] data = new byte[numBytes];
//Clear the 1 at the front of this byte, all the way to the beginning of the size
data[0] = (byte)(firstByte & ((0xFF >>> (numBytes))));
if (numBytes > 1)
{
//Read the rest of the size.
ArrayCopy.arraycopy(data, 1, source, offset+1, numBytes - 1);
}
//Put this into a long
long size = 0;
long n = 0;
for (int i = 0; i < numBytes; i++)
{
n = ((long)data[numBytes - 1 - i] << 56) >>> 56;
size = size | (n << (8 * i));
}
return size;
}
/**
* Reads an Signed EBML code from the DataSource and encodes it into a long. This size should be
* cast into an int for actual use as Java only allows upto 32-bit file I/O operations.
*
* @return ebml size
*/
static public long readSignedEBMLCode(byte [] source)
{
return readSignedEBMLCode(source, 0);
}
/**
* Reads an Signed EBML code from the DataSource and encodes it into a long. This size should be
* cast into an int for actual use as Java only allows upto 32-bit file I/O operations.
*
* @return ebml size
*/
static public long readSignedEBMLCode(byte [] source, int offset)
{
//Begin loop with byte set to newly read byte.
byte firstByte = source[offset];
int numBytes = 0;
//Begin by counting the bits unset before the first '1'.
long mask = 0x0080;
for (int i = 0; i < 8; i++)
{
//Start at left, shift to right.
if ((firstByte & mask) == mask)
{ //One found
//Set number of bytes in size = i+1 ( we must count the 1 too)
numBytes = i + 1;
//exit loop by pushing i out of the limit
i = 8;
}
mask >>>= 1;
}
if (numBytes == 0)
// Invalid size
return 0;
//Setup space to store the bits
byte[] data = new byte[numBytes];
//Clear the 1 at the front of this byte, all the way to the beginning of the size
data[0] = (byte)(firstByte & ((0xFF >>> (numBytes))));
if (numBytes > 1)
{
//Read the rest of the size.
ArrayCopy.arraycopy(data, 1, source, offset+1, numBytes - 1);
}
//Put this into a long
long size = 0;
long n = 0;
for (int i = 0; i < numBytes; i++)
{
n = ((long)data[numBytes - 1 - i] << 56) >>> 56;
size = size | (n << (8 * i));
}
// Sign it ;)
if (numBytes == 1)
{
size -= 63;
}
else if (numBytes == 2)
{
size -= 8191;
}
else if (numBytes == 3)
{
size -= 1048575;
}
else if (numBytes == 4)
{
size -= 134217727;
}
return size;
}
/**
* Reads an Signed EBML code from the DataSource and encodes it into a long. This size should be
* cast into an int for actual use as Java only allows upto 32-bit file I/O operations.
*
* @return ebml size
*/
static public long readSignedEBMLCode(DataSource source) {
//Begin loop with byte set to newly read byte.
byte firstByte = source.readByte();
int numBytes = 0;
//Begin by counting the bits unset before the first '1'.
long mask = 0x0080;
for (int i = 0; i < 8; i++) {
//Start at left, shift to right.
if ((firstByte & mask) == mask) { //One found
//Set number of bytes in size = i+1 ( we must count the 1 too)
numBytes = i + 1;
//exit loop by pushing i out of the limit
i = 8;
}
mask >>>= 1;
}
if (numBytes == 0)
// Invalid size
return 0;
//Setup space to store the bits
byte[] data = new byte[numBytes];
//Clear the 1 at the front of this byte, all the way to the beginning of the size
data[0] = (byte)(firstByte & ((0xFF >>> (numBytes))));
if (numBytes > 1) {
//Read the rest of the size.
source.read(data, 1, numBytes - 1);
}
//Put this into a long
long size = 0;
long n = 0;
for (int i = 0; i < numBytes; i++) {
n = ((long)data[numBytes - 1 - i] << 56) >>> 56;
size = size | (n << (8 * i));
}
// Sign it ;)
if (numBytes == 1) {
size -= 63;
} else if (numBytes == 2) {
size -= 8191;
} else if (numBytes == 3) {
size -= 1048575;
} else if (numBytes == 4) {
size -= 134217727;
}
return size;
}
/**
*Reads an EBML code from the DataSource.
*
* @return byte array filled with the ebml size, (size bits included)
*/
static public byte[] readEBMLCodeAsBytes(DataSource source) {
//Begin loop with byte set to newly read byte.
byte firstByte = source.readByte();
int numBytes = 0;
//Begin by counting the bits unset before the first '1'.
long mask = 0x0080;
for (int i = 0; i < 8; i++) {
//Start at left, shift to right.
if ((firstByte & mask) == mask) { //One found
//Set number of bytes in size = i+1 ( we must count the 1 too)
numBytes = i + 1;
//exit loop by pushing i out of the limit
i = 8;
}
mask >>>= 1;
}
if (numBytes == 0)
// Invalid element
return null;
//Setup space to store the bits
byte[] data = new byte[numBytes];
//Clear the 1 at the front of this byte, all the way to the beginning of the size
data[0] = (byte)((firstByte));// & ((0xFF >>> (numBytes))));
if (numBytes > 1) {
//Read the rest of the size.
source.read(data, 1, numBytes - 1);
}
return data;
}
}
/**
* 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);
}
/**
* 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;
import java.io.*;
public class FileDataSource implements DataSource {
RandomAccessFile file = null;
public FileDataSource(String filename) throws FileNotFoundException, IOException {
file = new RandomAccessFile(filename, "r");
}
public FileDataSource(String filename, String mode) throws FileNotFoundException, IOException {
file = new RandomAccessFile(filename, mode);
}
public byte readByte() {
try {
return file.readByte();
} catch (IOException ex) {
return 0;
}
}
public int read(byte[] buff) {
try {
return file.read(buff);
} catch (IOException ex) {
return 0;
}
}
public int read(byte[] buff, int offset, int length) {
try {
return file.read(buff, offset, length);
} catch (IOException ex) {
return 0;
}
}
public long skip(long offset) {
try {
return file.skipBytes((int)offset);
} catch (IOException ex) {
return 0;
}
}
public long length() {
try {
return file.length();
} catch (IOException ex) {
return -1;
}
}
public long getFilePointer() {
try {
return file.getFilePointer();
} catch (IOException ex) {
return -1;
}
}
public boolean isSeekable() {
return true;
}
public long seek(long pos) {
try {
file.seek(pos);
return file.getFilePointer();
} catch (IOException ex) {
return -1;
}
}
}
\ 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.io;
import java.io.*;
public class FileDataWriter implements DataWriter
{
RandomAccessFile file = null;
public FileDataWriter(String filename) throws FileNotFoundException, IOException
{
file = new RandomAccessFile(filename, "rw");
}
public FileDataWriter(String filename, String mode) throws FileNotFoundException, IOException
{
file = new RandomAccessFile(filename, mode);
}
public int write(byte b)
{
try
{
file.write(b);
return 1;
}
catch (IOException ex)
{
return 0;
}
}
public int write(byte[] buff)
{
try
{
file.write(buff);
return buff.length;
}
catch (IOException ex)
{
return 0;
}
}
public int write(byte[] buff, int offset, int length)
{
try
{
file.write(buff, offset, length);
return length;
}
catch (IOException ex)
{
return 0;
}
}
public long length()
{
try
{
return file.length();
}
catch (IOException ex)
{
return -1;
}
}
public long getFilePointer()
{
try
{
return file.getFilePointer();
}
catch (IOException ex)
{
return -1;
}
}
public boolean isSeekable()
{
return true;
}
public void close()
{
try
{
file.close();
}
catch (IOException ex)
{
}
}
public long seek(long pos)
{
try
{
file.seek(pos);
return file.getFilePointer();
}
catch (IOException ex)
{
return -1;
}
}
}
/**
* 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;
import java.io.*;
/*
* InputStreamDataSource
*
* Created on November 19, 2002, 9:35 PM
*
* @author John Cannon
*/
public class InputStreamDataSource
implements DataSource {
protected InputStream in = null;
protected long pos = 0;
protected byte[] buffer = new byte[1];
public InputStream getInputStream() {
return in;
}
/** Creates a new instance of InputStreamDataSource */
public InputStreamDataSource(InputStream in) {
this.in = in;
}
public byte readByte() {
try {
int l = in.read(buffer);
pos += l;
return buffer[0];
}
catch (IOException e) {
e.printStackTrace();
return -1;
}
}
public int read(byte[] buff) {
try {
int l = in.read(buff);
pos += l;
return l;
}
catch (IOException e) {
e.printStackTrace();
return -1;
}
}
public int read(byte[] buff, int offset, int length) {
try {
int l = in.read(buff, offset, length);
pos += l;
return l;
}
catch (IOException e) {
e.printStackTrace();
return -1;
}
}
public long skip(long offset) {
try {
long l = in.skip(offset);
pos += l;
return l;
}
catch (IOException e) {
e.printStackTrace();
return -1;
}
}
public long length() {
try {
return pos + in.available();
}
catch (IOException e) {
e.printStackTrace();
return -1;
}
}
public long getFilePointer() {
return pos;
}
public boolean isSeekable() {
return false;
}
public long seek(long pos) {
return 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;
import java.io.*;
public class StreamDataWriter implements DataWriter
{
OutputStream stream = null;
public StreamDataWriter(OutputStream stream) throws IOException
{
this.stream = stream;
}
public int write(byte b)
{
try
{
stream.write(b);
stream.flush();
return 1;
}
catch (IOException ex)
{
return 0;
}
}
public int write(byte[] buff)
{
try
{
stream.write(buff);
stream.flush();
return buff.length;
}
catch (IOException ex)
{
return 0;
}
}
public int write(byte[] buff, int offset, int length)
{
try
{
stream.write(buff, offset, length);
stream.flush();
return length;
}
catch (IOException ex)
{
return 0;
}
}
public boolean isSeekable()
{
return false;
}
public long seek(long pos)
{
return -1;
}
public long getFilePointer()
{
return -1;
}
public long length()
{
return -1;
}
}
/**
* 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.matroska;
import org.ebml.*;
import org.ebml.util.*;
public class MatroskaBlock extends BinaryElement {
protected int [] Sizes = null;
protected int HeaderSize = 0;
protected int BlockTimecode = 0;
protected int TrackNo = 0;
private boolean keyFrame;
public MatroskaBlock(byte[] type) {
super(type);
}
//public void readData(DataSource source) {
// parseBlock();
//}
public void parseBlock() {
int index = 0;
TrackNo = (int)EBMLReader.readEBMLCode(data);
index = Element.codedSizeLength(TrackNo);
HeaderSize += index;
short BlockTimecode1 = (short)(data[index++] & 0xFF);
short BlockTimecode2 = (short)(data[index++] & 0xFF);
if (BlockTimecode1 != 0 || BlockTimecode2 != 0) {
BlockTimecode = (BlockTimecode1 << 8) | BlockTimecode2;
}
int keyFlag = data[index] & 0x80;
if(keyFlag > 0)
this.keyFrame = true;
else
this.keyFrame = false;
int LaceFlag = data[index] & 0x06;
index++;
// Increase the HeaderSize by the number of bytes we have read
HeaderSize += 3;
if (LaceFlag != 0x00) {
// We have lacing
byte LaceCount = data[index++];
HeaderSize += 1;
if (LaceFlag == 0x02) { // Xiph Lacing
Sizes = readXiphLaceSizes(index, LaceCount);
} else if (LaceFlag == 0x06) { // EBML Lacing
Sizes = readEBMLLaceSizes(index, LaceCount);
} else if (LaceFlag == 0x04) { // Fixed Size Lacing
Sizes = new int[LaceCount+1];
Sizes[0] = (int)(this.getSize() - HeaderSize) / (LaceCount+1);
for (int s = 0; s < LaceCount; s++)
Sizes[s+1] = Sizes[0];
} else {
throw new RuntimeException("Unsupported lacing type flag.");
}
}
//data = new byte[(int)(this.getSize() - HeaderSize)];
//source.read(data, 0, data.length);
//this.dataRead = true;
}
public int[] readEBMLLaceSizes(int index, short LaceCount) {
int [] LaceSizes = new int[LaceCount+1];
LaceSizes[LaceCount] = (int)this.getSize();
// This uses the DataSource.getBytePosition() for finding the header size
// because of the trouble of finding the byte size of sized ebml coded integers
//long ByteStartPos = source.getFilePointer();
int startIndex = index;
LaceSizes[0] = (int)EBMLReader.readEBMLCode(data, index);
index += Element.codedSizeLength(LaceSizes[0]);
LaceSizes[LaceCount] -= LaceSizes[0];
long FirstEBMLSize = LaceSizes[0];
long LastEBMLSize = 0;
for (int l = 0; l < LaceCount-1; l++) {
LastEBMLSize = EBMLReader.readSignedEBMLCode(data, index);
index += Element.codedSizeLength(LastEBMLSize);
FirstEBMLSize += LastEBMLSize;
LaceSizes[l+1] = (int)FirstEBMLSize;
// Update the size of the last block
LaceSizes[LaceCount] -= LaceSizes[l+1];
}
//long ByteEndPos = source.getFilePointer();
//HeaderSize = HeaderSize + (int)(ByteEndPos - ByteStartPos);
HeaderSize = HeaderSize + (int)(index - startIndex);
LaceSizes[LaceCount] -= HeaderSize;
return LaceSizes;
}
public int[] readXiphLaceSizes(int index, short LaceCount) {
int [] LaceSizes = new int[LaceCount+1];
LaceSizes[LaceCount] = (int)this.getSize();
//long ByteStartPos = source.getFilePointer();
for (int l = 0; l < LaceCount; l++) {
short LaceSizeByte = 255;
while (LaceSizeByte == 255) {
LaceSizeByte = (short)(data[index++] & 0xFF);
HeaderSize += 1;
LaceSizes[l] += LaceSizeByte;
}
// Update the size of the last block
LaceSizes[LaceCount] -= LaceSizes[l];
}
//long ByteEndPos = source.getFilePointer();
LaceSizes[LaceCount] -= HeaderSize;
return LaceSizes;
}
public int getFrameCount() {
if (Sizes == null) {
return 1;
}
return Sizes.length;
}
public byte [] getFrame(int frame) {
if (Sizes == null) {
if (frame != 0) {
throw new IllegalArgumentException("Tried to read laced frame on non-laced Block. MatroskaBlock.getFrame(frame > 0)");
}
byte [] FrameData = new byte[data.length-HeaderSize];
ArrayCopy.arraycopy(data, HeaderSize, FrameData, 0, FrameData.length);
return FrameData;
}
byte [] FrameData = new byte[Sizes[frame]];
// Calc the frame data offset
int StartOffset = HeaderSize;
for (int s = 0; s < frame; s++) {
StartOffset += Sizes[s];
}
// Copy the frame data
ArrayCopy.arraycopy(data, StartOffset, FrameData, 0, FrameData.length);
return FrameData;
}
public long getAdjustedBlockTimecode(long ClusterTimecode, long TimecodeScale) {
return ClusterTimecode + (BlockTimecode);// * TimecodeScale);
}
public int getTrackNo() {
return TrackNo;
}
public int getBlockTimecode() {
return BlockTimecode;
}
public void setFrameData(int trackNo, long timecode, byte [] frameData)
{
this.data = new byte[4 + frameData.length];
this.data[0] = (byte) (trackNo | 0x80);
this.data[1] = (byte) (timecode >> 8);
this.data[2] = (byte) (timecode & 0xff);
this.data[3] = 0; // flags
ArrayCopy.arraycopy(frameData, 0, this.data, 4, frameData.length);
setSize(4 + frameData.length);
}
public boolean isKeyFrame() {
return keyFrame;
}
}
/**
* 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.matroska;
import org.ebml.*;
import org.ebml.util.*;
/**
* Summary description for MatroskaCluster.
*/
public class MatroskaCluster extends MasterElement
{
static public int NO_LACING = 0;
static public int XIPH_LACING = 1;
static public int EBML_LACING = 2;
protected int [] laceMode = null;
protected TLinkedList frames = new TLinkedList();
protected long clusterTimecode = 0;
public MatroskaCluster(byte[] type)
{
super(type);
}
/**
* Set the current lacing mode.
*
* @param trackNo Track Number for the track to enable lacing for. 1-based index
* @param laceMode The lacing moe to use. See NO_LACING, XIPH_LACING, and EBML_LACING.
*/
void setLaceMode(short trackNo, int laceMode)
{
if (this.laceMode == null)
{
this.laceMode = new int[trackNo];
}
if (this.laceMode.length < trackNo)
{
int [] oldLaceMode = this.laceMode;
this.laceMode = new int[trackNo];;
ArrayCopy.arraycopy(this.laceMode, 0, oldLaceMode, 0, oldLaceMode.length);
}
this.laceMode[trackNo-1] = laceMode;
}
/**
* Get the current lacing mode.
*
* @param trackNo Track Number for the track to enable lacing for. 1-based index
* @return -1 if the track no is invalid
*/
int getLaceMode(short trackNo)
{
if (this.laceMode == null)
{
return -1;
}
if (this.laceMode.length < trackNo)
{
return -1;
}
return this.laceMode[trackNo-1];
}
public void AddFrame(MatroskaFileFrame frame)
{
// Is this the earliest timecode?
if (frame.Timecode < clusterTimecode)
{
clusterTimecode = frame.Timecode;
}
frames.add(frame);
}
public void FlushFrames()
{
TLinkedList.IteratorImpl iter = frames.first();
while (iter.hasNext())
{
//MatroskaFileFrame frame = (MatroskaFileFrame)
iter.next();
}
}
}
/**
* 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.matroska;
import org.ebml.*;
import java.util.*;
/**
* DocType for Matroska files.
* This has all the element type id declares and also is in charge of
* creating the correct element classes from the type id.
*/
public class MatroskaDocType implements DocType
{
// Custom Element Types
static public short BLOCK_ELEMENT = (short)(ElementType.LAST_ELEMENT_TYPE + 1);
static public short SEGMENT_ELEMENT = (short)(ElementType.LAST_ELEMENT_TYPE + 2);
static public short CLUSTER_ELEMENT = (short)(ElementType.LAST_ELEMENT_TYPE + 3);
// EBML Id's
static public byte [] Void_Id = {(byte)0xEC};
static public byte [] EBMLHeader_Id = {0x1A, 0x45, (byte)0xDF, (byte)0xA3};
static public byte [] EBMLVersion_Id = {0x42, (byte)0x86};
static public byte [] DocTypeReadVersion_Id = {0x42, (byte)0x85};
static public byte [] EBMLReadVersion_Id = {0x42, (byte)0xF7};
static public byte [] EBMLMaxIDLength_Id = {0x42, (byte)0xF2};
static public byte [] EBMLMaxSizeLength_Id = {0x42, (byte)0xF3};
static public byte [] DocType_Id = {0x42, (byte)0x82};
static public byte [] DocTypeVersion_Id = {0x42, (byte)0x87};
static public byte [] Segment_Id = {0x18, 0x53, (byte)0x80, 0x67};
static public byte [] SeekHead_Id = {0x11, 0x4D, (byte)0x9B, 0x74};
static public byte [] SeekEntry_Id = {0x4D, (byte)0xBB};
static public byte [] SeekID_Id = {0x53, (byte)0xAB};
static public byte [] SeekPosition_Id = {0x53, (byte)0xAC};
static public byte [] SegmentInfo_Id = {0x15, (byte)0x49, (byte)0xA9, (byte)0x66};
static public byte [] SegmentUID_Id = {0x73, (byte)0xA4};
static public byte [] SegmentFilename_Id = {0x73, (byte)0x84};
static public byte [] TimecodeScale_Id = {0x2A, (byte)0xD7, (byte)0xB1};
static public byte [] Duration_Id = {0x44, (byte)0x89};
static public byte [] DateUTC_Id = {0x44, (byte)0x61};
static public byte [] Title_Id = {0x7B, (byte)0xA9};
static public byte [] MuxingApp_Id = {0x4D, (byte)0x80};
static public byte [] WritingApp_Id = {0x57, 0x41};
static public byte [] Tracks_Id = {0x16, (byte)0x54, (byte)0xAE, (byte)0x6B};
static public byte [] TrackEntry_Id = {(byte)0xAE};
static public byte [] TrackNumber_Id = {(byte)0xD7};
static public byte [] TrackUID_Id = {0x73, (byte)0xC5};
static public byte [] TrackType_Id = {(byte)0x83};
static public byte [] TrackDefaultDuration_Id = {0x23, (byte)0xE3, (byte)0x83};
static public byte [] TrackName_Id = {0x53, 0x6E};
static public byte [] TrackLanguage_Id = {0x22, (byte)0xB5, (byte)0x9C};
static public byte [] TrackCodecID_Id = {(byte)0x86};
static public byte [] TrackCodecPrivate_Id = {(byte)0x63, (byte)0xA2};
static public byte [] TrackVideo_Id = {(byte)0xE0};
static public byte [] PixelWidth_Id = {(byte)0xB0};
static public byte [] PixelHeight_Id = {(byte)0xBA};
static public byte [] DisplayWidth_Id = {0x54, (byte)0xB0};
static public byte [] DisplayHeight_Id = {0x54, (byte)0xBA};
static public byte [] TrackAudio_Id = {(byte)0xE1};
static public byte [] SamplingFrequency_Id = {(byte)0xB5};
static public byte [] OutputSamplingFrequency_Id = {0x78, (byte)0xB5};
static public byte [] Channels_Id = {(byte)0x9F};
static public byte [] BitDepth_Id = {0x62, 0x64};
static public byte [] Attachments_Id = {0x19, 0x41, (byte)0xA4, 0x69};
static public byte [] AttachedFile_Id = {0x61, (byte)0xA7};
static public byte [] AttachedFileDescription_Id = {0x46, (byte)0x7E};
static public byte [] AttachedFileName_Id = {0x46, (byte)0x6E};
static public byte [] AttachedFileMimeType_Id = {0x46, (byte)0x60};
static public byte [] AttachedFileData_Id = {0x46, (byte)0x5C};
static public byte [] AttachedFileUID_Id = {0x46, (byte)0xAE};
static public byte [] Tags_Id = {0x12, (byte)0x54, (byte)0xC3, (byte)0x67};
static public byte [] Tag_Id = {0x73, (byte)0x73};
static public byte [] TagTargets_Id = {0x63, (byte)0xC0};
static public byte [] TagTargetTrackUID_Id = {0x63, (byte)0xC5};
static public byte [] TagTargetChapterUID_Id = {0x63, (byte)0xC4};
static public byte [] TagTargetAttachmentUID_Id = {0x63, (byte)0xC6};
static public byte [] TagSimpleTag_Id = {0x67, (byte)0xC8};
static public byte [] TagSimpleTagName_Id = {0x45, (byte)0xA3};
static public byte [] TagSimpleTagString_Id = {0x44, (byte)0x87};
static public byte [] TagSimpleTagBinary_Id = {0x44, (byte)0x85};
static public byte [] Cluster_Id = {0x1F, (byte)0x43, (byte)0xB6, (byte)0x75};
static public byte[] ClusterTimecode_Id = {(byte)0xE7};
static public byte[] ClusterBlockGroup_Id = {(byte)0xA0};
static public byte[] ClusterBlock_Id = {(byte)0xA1};
static public byte[] ClusterSimpleBlock_Id = {(byte)0xA3};
static public byte[] ClusterBlockDuration_Id = {(byte)0x9B};
static public byte[] ClusterReferenceBlock_Id = {(byte)0xFB};
static public byte[] Chapters_Id = {0x10, (byte)0x43, (byte)0xA7, (byte)0x70};
static public byte [] ChapterEditionEntry_Id = {(byte)0x45, (byte)0xB9};
static public byte [] ChapterEditionUID_Id = {(byte)0x45, (byte)0xBC};
static public byte [] ChapterEditionFlagHidden_Id = {(byte)0x45, (byte)0xBD};
static public byte [] ChapterEditionFlagDefault_Id = {(byte)0x45, (byte)0xDB};
static public byte [] ChapterEditionManaged_Id = {(byte)0x45, (byte)0xDD};
static public byte [] ChapterAtom_Id = {(byte)0xB6};
static public byte [] ChapterAtomChapterUID_Id = {(byte)0x73, (byte)0xC4};
static public byte [] ChapterAtomChapterTimeStart_Id = {(byte)0x91};
static public byte [] ChapterAtomChapterTimeEnd_Id = {(byte)0x92};
static public byte [] ChapterAtomChapterFlagHidden_Id = {(byte)0x98};
static public byte [] ChapterAtomChapterFlagEnabled_Id = {(byte)0x45, (byte)0x98};
static public byte [] ChapterAtomChapterPhysicalEquiv_Id = {(byte)0x63, (byte)0xC3};
static public byte [] ChapterAtomChapterTrack_Id = {(byte)0x8F};
static public byte [] ChapterAtomChapterTrackNumber_Id = {(byte)0x89};
static public byte [] ChapterAtomChapterDisplay_Id = {(byte)0x80};
static public byte [] ChapterAtomChapString_Id = {(byte)0x85};
static public byte [] ChapterAtomChapLanguage_Id = {(byte)0x43, (byte)0x7C};
static public byte [] ChapterAtomChapCountry_Id = {(byte)0x43, (byte)0x7E};
// Track Types
static public byte track_video = 0x01; ///< Rectangle-shaped non-transparent pictures aka video
static public byte track_audio = 0x02; ///< Anything you can hear
static public byte track_complex = 0x03; ///< Audio and video in same track, used by DV
static public byte track_logo = 0x10; ///< Overlay-pictures, displayed over video
static public byte track_subtitle = 0x11; ///< Text-subtitles. One track contains one language and only one track can be active (player-side configuration)
static public byte track_control = 0x20; ///< Control-codes for menus and other stuff
/**
* Converts a integer track type to String form.
*
* @param trackType Integer Track Type
* @return String <code>trackType</code> in String form
*/
static public String TrackTypeToString(byte trackType) {
if (trackType == track_video)
return "Video";
if (trackType == track_audio)
return "Audio";
if (trackType == track_complex)
return "Complex";
if (trackType == track_logo)
return "Logo";
if (trackType == track_subtitle)
return "Subtitle";
if (trackType == track_control)
return "Control";
return "";
}
protected ElementType type;
static public MatroskaDocType obj = new MatroskaDocType();
public MatroskaDocType() {
//long start = java.lang.System.currentTimeMillis();
init();
//System.out.println("MatroskaDocType Loaded in " + java.lang.System.currentTimeMillis() - start + " milliseconds");
}
protected void init() {
try {
ElementType baseLevel = new ElementType("", (short)0, (byte[])null,
(short)0, new ArrayList<ElementType>());
ElementType level0 = null;
ElementType level1 = null;
ElementType level2 = null;
ElementType level3 = null;
ElementType level4 = null;
ElementType level5 = null;
level0 = new ElementType("Void",
(short)1,
Void_Id,
ElementType.BINARY_ELEMENT,
(ArrayList<ElementType>)null);
baseLevel.children.add(level0);
level0 = new ElementType("EBMLHeader",
(short)0,
EBMLHeader_Id,
ElementType.MASTER_ELEMENT,
new ArrayList<ElementType>());
level1 = new ElementType("EBMLVersion",
(short)1,
EBMLVersion_Id,
ElementType.UINTEGER_ELEMENT,
(ArrayList<ElementType>)null);
level0.children.add(level1);
level1 = new ElementType("EBMLReadVersion",
(short)1,
EBMLReadVersion_Id,
ElementType.UINTEGER_ELEMENT,
(ArrayList<ElementType>)null);
level0.children.add(level1);
level1 = new ElementType("EBMLMaxIDLength",
(short)1,
EBMLMaxIDLength_Id,
ElementType.UINTEGER_ELEMENT,
(ArrayList<ElementType>)null);
level0.children.add(level1);
level1 = new ElementType("EBMLMaxSizeLength",
(short)1,
EBMLMaxSizeLength_Id,
ElementType.UINTEGER_ELEMENT,
(ArrayList<ElementType>)null);
level0.children.add(level1);
level1 = new ElementType("DocType",
(short)1,
DocType_Id,
ElementType.STRING_ELEMENT,
(ArrayList<ElementType>)null);
level0.children.add(level1);
level1 = new ElementType("DocTypeVersion",
(short)1,
DocTypeVersion_Id,
ElementType.UINTEGER_ELEMENT,
(ArrayList<ElementType>)null);
level0.children.add(level1);
level1 = new ElementType("DocTypeReadVersion",
(short)1,
DocTypeReadVersion_Id,
ElementType.UINTEGER_ELEMENT,
(ArrayList<ElementType>)null);
level0.children.add(level1);
baseLevel.children.add(level0);
level0 = new ElementType("Segment",
(short)0,
Segment_Id,
MatroskaDocType.SEGMENT_ELEMENT,
new ArrayList<ElementType>());
level1 = new ElementType("SeekHead",
(short)1,
SeekHead_Id,
ElementType.MASTER_ELEMENT,
new ArrayList<ElementType>());
level2 = new ElementType("SeekEntry",
(short)2,
SeekEntry_Id,
ElementType.MASTER_ELEMENT,
new ArrayList<ElementType>());
level3 = new ElementType("SeekID",
(short)3,
SeekID_Id,
ElementType.BINARY_ELEMENT,
(ArrayList<ElementType>)null);
level2.children.add(level3);
level3 = new ElementType("SeekPosition",
(short)3,
SeekPosition_Id,
ElementType.UINTEGER_ELEMENT,
(ArrayList<ElementType>)null);
level2.children.add(level3);
// Add Seek Element
level1.children.add(level2);
// Add SeekHead Element
level0.children.add(level1);
level1 = new ElementType("SegmentInfo",
(short)1,
SegmentInfo_Id,
ElementType.MASTER_ELEMENT,
new ArrayList<ElementType>());
level2 = new ElementType("SegmentUID",
(short)2,
SegmentUID_Id,
ElementType.BINARY_ELEMENT,
(ArrayList<ElementType>)null);
level1.children.add(level2);
level2 = new ElementType("SegmentFilename",
(short)2,
SegmentFilename_Id,
ElementType.STRING_ELEMENT,
(ArrayList<ElementType>)null);
level1.children.add(level2);
level2 = new ElementType("TimecodeScale",
(short)2,
TimecodeScale_Id,
ElementType.UINTEGER_ELEMENT,
(ArrayList<ElementType>)null);
level1.children.add(level2);
level2 = new ElementType("Duration",
(short)2,
Duration_Id,
ElementType.FLOAT_ELEMENT,
(ArrayList<ElementType>)null);
level1.children.add(level2);
level2 = new ElementType("DateUTC",
(short)2,
DateUTC_Id,
ElementType.DATE_ELEMENT,
(ArrayList<ElementType>)null);
level1.children.add(level2);
level2 = new ElementType("Title",
(short)2,
Title_Id,
ElementType.STRING_ELEMENT,
(ArrayList<ElementType>)null);
level1.children.add(level2);
level2 = new ElementType("MuxingApp",
(short)2,
MuxingApp_Id,
ElementType.STRING_ELEMENT,
(ArrayList<ElementType>)null);
level1.children.add(level2);
level2 = new ElementType("WritingApp",
(short)2,
WritingApp_Id,
ElementType.STRING_ELEMENT,
(ArrayList<ElementType>)null);
level1.children.add(level2);
// Add Segment Infomation Element
level0.children.add(level1);
level1 = new ElementType("Tracks",
(short)1,
Tracks_Id,
ElementType.MASTER_ELEMENT,
new ArrayList<ElementType>());
level2 = new ElementType("TrackEntry",
(short)2,
TrackEntry_Id,
ElementType.MASTER_ELEMENT,
new ArrayList<ElementType>());
level3 = new ElementType("TrackNumber",
(short)3,
TrackNumber_Id,
ElementType.UINTEGER_ELEMENT,
(ArrayList<ElementType>)null);
level2.children.add(level3);
level3 = new ElementType("TrackUID",
(short)3,
TrackUID_Id,
ElementType.UINTEGER_ELEMENT,
(ArrayList<ElementType>)null);
level2.children.add(level3);
level3 = new ElementType("TrackType",
(short)3,
TrackType_Id,
ElementType.UINTEGER_ELEMENT,
(ArrayList<ElementType>)null);
level2.children.add(level3);
level3 = new ElementType("TrackDefaultDuration",
(short)3,
TrackDefaultDuration_Id,
ElementType.UINTEGER_ELEMENT,
(ArrayList<ElementType>)null);
level2.children.add(level3);
level3 = new ElementType("TrackName",
(short)3,
TrackName_Id,
ElementType.STRING_ELEMENT,
(ArrayList<ElementType>)null);
level2.children.add(level3);
level3 = new ElementType("TrackLanguage",
(short)3,
TrackLanguage_Id,
ElementType.ASCII_STRING_ELEMENT,
(ArrayList<ElementType>)null);
level2.children.add(level3);
level3 = new ElementType("TrackCodecID",
(short)3,
TrackCodecID_Id,
ElementType.ASCII_STRING_ELEMENT,
(ArrayList<ElementType>)null);
level2.children.add(level3);
level3 = new ElementType("TrackCodecPrivate",
(short)3,
TrackCodecPrivate_Id,
ElementType.BINARY_ELEMENT,
(ArrayList<ElementType>)null);
level2.children.add(level3);
level3 = new ElementType("TrackVideo",
(short)3,
TrackVideo_Id,
ElementType.MASTER_ELEMENT,
new ArrayList<ElementType>());
level4 = new ElementType("PixelWidth",
(short)4,
PixelWidth_Id,
ElementType.UINTEGER_ELEMENT,
(ArrayList<ElementType>)null);
level3.children.add(level4);
level4 = new ElementType("PixelHeight",
(short)4,
PixelHeight_Id,
ElementType.UINTEGER_ELEMENT,
(ArrayList<ElementType>)null);
level3.children.add(level4);
level4 = new ElementType("DisplayWidth",
(short)4,
DisplayWidth_Id,
ElementType.UINTEGER_ELEMENT,
(ArrayList<ElementType>)null);
level3.children.add(level4);
level4 = new ElementType("DisplayHeight",
(short)4,
DisplayHeight_Id,
ElementType.UINTEGER_ELEMENT,
(ArrayList<ElementType>)null);
level3.children.add(level4);
// Add TrackVideo Element
level2.children.add(level3);
level3 = new ElementType("TrackAudio",
(short)3,
TrackAudio_Id,
ElementType.MASTER_ELEMENT,
new ArrayList<ElementType>());
level4 = new ElementType("SamplingFrequency",
(short)4,
SamplingFrequency_Id,
ElementType.FLOAT_ELEMENT,
(ArrayList<ElementType>)null);
level3.children.add(level4);
level4 = new ElementType("OutputSamplingFrequency",
(short)4,
OutputSamplingFrequency_Id,
ElementType.FLOAT_ELEMENT,
(ArrayList<ElementType>)null);
level3.children.add(level4);
level4 = new ElementType("Channels",
(short)4,
Channels_Id,
ElementType.UINTEGER_ELEMENT,
(ArrayList<ElementType>)null);
level3.children.add(level4);
level4 = new ElementType("BitDepth",
(short)4,
BitDepth_Id,
ElementType.UINTEGER_ELEMENT,
(ArrayList<ElementType>)null);
level3.children.add(level4);
// Add TrackAudio Element
level2.children.add(level3);
// Add TrackEntry Element
level1.children.add(level2);
// Add Tracks Element
level0.children.add(level1);
level1 = new ElementType("Attachments",
(short)1,
Attachments_Id,
ElementType.MASTER_ELEMENT,
new ArrayList<ElementType>());
level2 = new ElementType("AttachedFile",
(short)2,
AttachedFile_Id,
ElementType.MASTER_ELEMENT,
new ArrayList<ElementType>());
level3 = new ElementType("AttachedFileDescription",
(short)3,
AttachedFileDescription_Id,
ElementType.STRING_ELEMENT,
(ArrayList<ElementType>)null);
level2.children.add(level3);
level3 = new ElementType("AttachedFileName",
(short)3,
AttachedFileName_Id,
ElementType.STRING_ELEMENT,
(ArrayList<ElementType>)null);
level2.children.add(level3);
level3 = new ElementType("AttachedFileMimeType",
(short)3,
AttachedFileMimeType_Id,
ElementType.ASCII_STRING_ELEMENT,
(ArrayList<ElementType>)null);
level2.children.add(level3);
level3 = new ElementType("AttachedFileData",
(short)3,
AttachedFileData_Id,
ElementType.BINARY_ELEMENT,
(ArrayList<ElementType>)null);
level2.children.add(level3);
level3 = new ElementType("AttachedFileUID",
(short)3,
AttachedFileUID_Id,
ElementType.UINTEGER_ELEMENT,
(ArrayList<ElementType>)null);
level2.children.add(level3);
// Add AttachedFile Element
level1.children.add(level2);
// Add Attachments Element
level0.children.add(level1);
level1 = new ElementType("Tags",
(short)1,
Tags_Id,
ElementType.MASTER_ELEMENT,
new ArrayList<ElementType>());
level2 = new ElementType("Tag",
(short)2,
Tag_Id,
ElementType.MASTER_ELEMENT,
new ArrayList<ElementType>());
level3 = new ElementType("TagTargets",
(short)3,
TagTargets_Id,
ElementType.MASTER_ELEMENT,
new ArrayList<ElementType>());
level4 = new ElementType("TagTargetTrackUID",
(short)4,
TagTargetTrackUID_Id,
ElementType.UINTEGER_ELEMENT,
(ArrayList<ElementType>)null);
level3.children.add(level4);
level4 = new ElementType("TagTargetChapterUID",
(short)4,
TagTargetChapterUID_Id,
ElementType.UINTEGER_ELEMENT,
(ArrayList<ElementType>)null);
level3.children.add(level4);
level4 = new ElementType("TagTargetAttachmentUID",
(short)4,
TagTargetAttachmentUID_Id,
ElementType.UINTEGER_ELEMENT,
(ArrayList<ElementType>)null);
level3.children.add(level4);
// Add Targets
level2.children.add(level3);
level3 = new ElementType("TagSimpleTag",
(short)3,
TagSimpleTag_Id,
ElementType.MASTER_ELEMENT,
new ArrayList<ElementType>());
level4 = new ElementType("TagSimpleTagName",
(short)4,
TagSimpleTagName_Id,
ElementType.STRING_ELEMENT,
(ArrayList<ElementType>)null);
level3.children.add(level4);
level4 = new ElementType("TagSimpleTagString",
(short)4,
TagSimpleTagString_Id,
ElementType.STRING_ELEMENT,
(ArrayList<ElementType>)null);
level3.children.add(level4);
level4 = new ElementType("TagSimpleTagBinary",
(short)4,
TagSimpleTagBinary_Id,
ElementType.BINARY_ELEMENT,
(ArrayList<ElementType>)null);
level3.children.add(level4);
// Add SimpleTag
level2.children.add(level3);
// Add Tag Element
level1.children.add(level2);
// Add Tags Element
level0.children.add(level1);
level1 = new ElementType("Cluster",
(short)1,
Cluster_Id,
MatroskaDocType.CLUSTER_ELEMENT,
new ArrayList<ElementType>());
level2 = new ElementType("ClusterTimecode",
(short)2,
ClusterTimecode_Id,
ElementType.UINTEGER_ELEMENT,
(ArrayList<ElementType>)null);
level1.children.add(level2);
level2 = new ElementType("ClusterBlockGroup",
(short)2,
ClusterBlockGroup_Id,
ElementType.MASTER_ELEMENT,
new ArrayList<ElementType>());
level3 = new ElementType("ClusterBlock",
(short)3,
ClusterBlock_Id,
MatroskaDocType.BLOCK_ELEMENT,
(ArrayList<ElementType>)null);
level2.children.add(level3);
level3 = new ElementType("ClusterBlockDuration",
(short)3,
ClusterBlockDuration_Id,
ElementType.UINTEGER_ELEMENT,
(ArrayList<ElementType>)null);
level2.children.add(level3);
level3 = new ElementType("ClusterReferenceBlock",
(short)3,
ClusterReferenceBlock_Id,
ElementType.SINTEGER_ELEMENT,
(ArrayList<ElementType>)null);
level2.children.add(level3);
// Add ClusterBlockGroup Element
level1.children.add(level2);
level2 = new ElementType("SimpleBlock",
(short)2,
ClusterSimpleBlock_Id,
MatroskaDocType.BLOCK_ELEMENT,
new ArrayList<ElementType>());
// Add SimpleBlock Element
level1.children.add(level2);
// Add Cluster Element
level0.children.add(level1);
level1 = new ElementType("Chapters",
(short)1,
Chapters_Id,
ElementType.MASTER_ELEMENT,
new ArrayList<ElementType>());
level2 = new ElementType("ChapterEditionEntry",
(short)2,
ChapterEditionEntry_Id,
ElementType.MASTER_ELEMENT,
new ArrayList<ElementType>());
level3 = new ElementType("ChapterEditionUID",
(short)3,
ChapterEditionUID_Id,
ElementType.UINTEGER_ELEMENT,
(ArrayList<ElementType>)null);
level2.children.add(level3);
level3 = new ElementType("ChapterEditionFlagHidden",
(short)3,
ChapterEditionFlagHidden_Id,
ElementType.UINTEGER_ELEMENT,
(ArrayList<ElementType>)null);
level2.children.add(level3);
level3 = new ElementType("ChapterEditionFlagDefault",
(short)3,
ChapterEditionFlagDefault_Id,
ElementType.UINTEGER_ELEMENT,
(ArrayList<ElementType>)null);
level2.children.add(level3);
level3 = new ElementType("ChapterEditionManaged",
(short)3,
ChapterEditionManaged_Id,
ElementType.UINTEGER_ELEMENT,
(ArrayList<ElementType>)null);
level2.children.add(level3);
level3 = new ElementType("ChapterAtom",
(short)3,
ChapterAtom_Id,
ElementType.MASTER_ELEMENT,
new ArrayList<ElementType>());
level4 = new ElementType("ChapterAtomChapterUID",
(short)4,
ChapterAtomChapterUID_Id,
ElementType.UINTEGER_ELEMENT,
(ArrayList<ElementType>)null);
level3.children.add(level4);
level4 = new ElementType("ChapterAtomChapterTimeStart",
(short)4,
ChapterAtomChapterTimeStart_Id,
ElementType.UINTEGER_ELEMENT,
(ArrayList<ElementType>)null);
level3.children.add(level4);
level4 = new ElementType("ChapterAtomChapterTimeEnd",
(short)4,
ChapterAtomChapterTimeEnd_Id,
ElementType.UINTEGER_ELEMENT,
(ArrayList<ElementType>)null);
level3.children.add(level4);
level4 = new ElementType("ChapterAtomChapterFlagHidden",
(short)4,
ChapterAtomChapterFlagHidden_Id,
ElementType.UINTEGER_ELEMENT,
(ArrayList<ElementType>)null);
level3.children.add(level4);
level4 = new ElementType("ChapterAtomChapterFlagEnabled",
(short)4,
ChapterAtomChapterFlagEnabled_Id,
ElementType.UINTEGER_ELEMENT,
(ArrayList<ElementType>)null);
level3.children.add(level4);
level4 = new ElementType("ChapterAtomChapterPhysicalEquiv",
(short)4,
ChapterAtomChapterPhysicalEquiv_Id,
ElementType.UINTEGER_ELEMENT,
(ArrayList<ElementType>)null);
level3.children.add(level4);
level4 = new ElementType("ChapterAtomChapterTrack",
(short)4,
ChapterAtomChapterTrack_Id,
ElementType.MASTER_ELEMENT,
new ArrayList<ElementType>());
level5 = new ElementType("ChapterAtomChapterTrackNumber",
(short)5,
ChapterAtomChapterTrackNumber_Id,
ElementType.UINTEGER_ELEMENT,
(ArrayList<ElementType>)null);
level4.children.add(level5);
// Add ChapterAtomChapterTrack Element
level3.children.add(level4);
level4 = new ElementType("ChapterAtomChapterDisplay",
(short)4,
ChapterAtomChapterDisplay_Id,
ElementType.MASTER_ELEMENT,
new ArrayList<ElementType>());
level5 = new ElementType("ChapterAtomChapString",
(short)5,
ChapterAtomChapString_Id,
ElementType.STRING_ELEMENT,
(ArrayList<ElementType>)null);
level4.children.add(level5);
level5 = new ElementType("ChapterAtomChapLanguage",
(short)5,
ChapterAtomChapLanguage_Id,
ElementType.ASCII_STRING_ELEMENT,
(ArrayList<ElementType>)null);
level4.children.add(level5);
level5 = new ElementType("ChapterAtomChapCountry",
(short)5,
ChapterAtomChapCountry_Id,
ElementType.ASCII_STRING_ELEMENT,
(ArrayList<ElementType>)null);
level4.children.add(level5);
// Add ChapterAtomChapterDisplay Element
level3.children.add(level4);
// Add ChapterAtom Element
level2.children.add(level3);
// Add ChapterEditionEntry Element
level1.children.add(level2);
// Add Chapters Element
level0.children.add(level1);
// Add Segment Element
baseLevel.children.add(level0);
type = baseLevel;
} catch (java.lang.Exception ex) {
ex.printStackTrace();
}
}
/**
* Get the base ElementType tree.
*
* @return An ElementType that is filled with all the valid ElementType(s) for the MatroskaDocType
*/
public ElementType getElements() {
return type;
}
/**
* Creates an Element sub-class based on the ElementType.
*
* @param type ElementType to use for creation of Element.
* @return new Element sub-class, BinaryElement is the default.
* @throws RuntimeException if the ElementType has an unknown type field.
*/
public Element createElement(ElementType type) {
Element elem = null;
elem = type.createElement();
if (elem == null) {
if (type.type == MatroskaDocType.BLOCK_ELEMENT)
{
elem = new MatroskaBlock(type.id);
}
else if (type.type == MatroskaDocType.SEGMENT_ELEMENT)
{
elem = new MatroskaSegment(type.id);
}
else if (type.type == MatroskaDocType.CLUSTER_ELEMENT)
{
elem = new MatroskaCluster(type.id);
}
else if (type.type == ElementType.UNKNOWN_ELEMENT)
{
elem = new BinaryElement(type.id);
}
else
{
throw new java.lang.RuntimeException("Error: Unknown Element Type");
}
elem.setElementType(type);
}
return elem;
}
/**
* Creates an Element sub-class based on the element id.
*
* @param type id to use for creation of Element.
* @return new Element sub-class, BinaryElement is the default.
* @throws RuntimeException if the ElementType has an unknown type field.
*/
public Element createElement(byte [] type)
{
ElementType elementType = getElements().findElement(type);
if (elementType == null)
{
elementType = new UnknownElementType(type);
}
return createElement(elementType);
}
}
/**
* 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.matroska;
import java.util.*;
import org.ebml.*;
import org.ebml.io.*;
import org.ebml.util.*;
public class MatroskaFile {
/**
* Number of Clusters to search before assuming that a track has ended
*/
public static int CLUSTER_TRACK_SEARCH_COUNT = 4;
protected DataSource ioDS;
protected EBMLReader reader;
protected Element level0 = null;
protected String SegmentTitle;
protected Date SegmentDate;
protected String MuxingApp;
protected String WritingApp;
protected long TimecodeScale = 1000000;
protected double Duration;
public ArrayList<MatroskaFileTrack> TrackList = new ArrayList<MatroskaFileTrack>();
protected ArrayList<MatroskaFileTagEntry> TagList = new ArrayList<MatroskaFileTagEntry>();
protected TLinkedList FrameQueue = new TLinkedList();
protected boolean ScanFirstCluster = true;
public long ClusterTimecode = 0;
/**
* Primary Constructor for Matroska File class.
*
* @param inputDataSource DataSource to read the Matroska file from
*/
public MatroskaFile(DataSource inputDataSource) {
ioDS = inputDataSource;
reader = new EBMLReader(ioDS, MatroskaDocType.obj);
}
/**
* Read / Parse the Matroska file.
* Call this before any other method.
* @throws RuntimeException On various errors
*/
public void readFile() {
Element level1 = null;
Element level2 = null;
// Element level3 = null;
// Element level4 = null;
level0 = reader.readNextElement();
if (level0 == null) {
throw new java.lang.RuntimeException("Error: Unable to scan for EBML elements");
}
if (level0.equals(MatroskaDocType.EBMLHeader_Id)) {
level1 = ((MasterElement)level0).readNextChild(reader);
while (level1 != null) {
level1.readData(ioDS);
if (level1.equals(MatroskaDocType.DocType_Id)) {
String DocType = ((StringElement)level1).getValue();
if (DocType.compareTo("matroska") != 0 && DocType.compareTo("webm") != 0) {
throw new java.lang.RuntimeException("Error: DocType is not matroska, \"" + ((StringElement)level1).getValue() + "\"");
}
}
level1 = ((MasterElement)level0).readNextChild(reader);
}
} else {
throw new java.lang.RuntimeException("Error: EBML Header not the first element in the file");
}
level0 = reader.readNextElement();
if (level0.equals(MatroskaDocType.Segment_Id)) {
level1 = ((MasterElement)level0).readNextChild(reader);
while (level1 != null) {
if (level1.equals(MatroskaDocType.SegmentInfo_Id)) {
_parseSegmentInfo(level1, level2);
} else if (level1.equals(MatroskaDocType.Tracks_Id)) {
_parseTracks(level1, level2);
} else if (level1.equals(MatroskaDocType.Cluster_Id)) {
if (ScanFirstCluster)
{
_parseNextCluster(level1);
}
// Break out of this loop, we should only parse the first cluster
break;
} else if (level1.equals(MatroskaDocType.Tags_Id)) {
_parseTags(level1, level2);
}
level1.skipData(ioDS);
level1 = ((MasterElement)level0).readNextChild(reader);
}
} else {
throw new java.lang.RuntimeException("Error: Segment not the second element in the file");
}
}
/**
* Get the Next MatroskaFileFrame
*
* @return The next MatroskaFileFrame in the queue, or null if the file has ended
*/
public MatroskaFileFrame getNextFrame() {
if (FrameQueue.isEmpty()) {
_fillFrameQueue();
}
// If FrameQueue is still empty, must be the end of the file
if (FrameQueue.isEmpty()) {
return null;
}
return (MatroskaFileFrame)FrameQueue.removeFirst();
}
/**
* Get the Next MatroskaFileFrame, limited by TrackNo
*
* @param TrackNo The track number to only get MatroskaFileFrame(s) from
* @return The next MatroskaFileFrame in the queue, or null if there are no more frames for the TrackNo track
*/
public MatroskaFileFrame getNextFrame(int TrackNo) {
if (FrameQueue.isEmpty()) {
_fillFrameQueue();
}
// If FrameQueue is still empty, must be the end of the file
if (FrameQueue.isEmpty()) {
return null;
}
int tryCount = 0;
MatroskaFileFrame frame = null;
try {
TLinkedList.IteratorImpl iter = FrameQueue.first();
while (frame == null) {
if (iter.hasNext()) {
frame = (MatroskaFileFrame)iter.next();
if (frame.TrackNo == TrackNo) {
synchronized (FrameQueue) {
iter.remove();
}
return frame;
}
frame = null;
} else {
_fillFrameQueue();
// Update Iterator
int index = iter.nextIndex();
iter = FrameQueue.listIterator(index);
if (++tryCount > CLUSTER_TRACK_SEARCH_COUNT) {
// If we have not found any frames belonging to a track in 4 clusters
// there is a good chance that the track is over
return null;
}
}
}
} catch (RuntimeException ex) {
ex.printStackTrace();
return null;
}
return frame;
}
public boolean isSeekable() {
return this.ioDS.isSeekable();
}
/**
* Seek to the requested timecode, rescaning clusters and/or discarding frames
* until we reach the nearest possible timecode, rounded down.
*
* <p>
* For example<br>
* Say we have a file with 10 frames
* <table><tr><th>Frame No</th><th>Timecode</th></tr>
* <tr><td>Frame 1</td> <td>0ms</td></tr>
* <tr><td>Frame 2</td> <td>50ms</td></tr>
* <tr><td>Frame 3</td> <td>100ms</td></tr>
* <tr><td>Frame 4</td> <td>150ms</td></tr>
* <tr><td>Frame 5</td> <td>200ms</td></tr>
* <tr><td>Frame 6</td> <td>250ms</td></tr>
* <tr><td>Frame 7</td> <td>300ms</td></tr>
* <tr><td>Frame 8</td> <td>350ms</td></tr>
* <tr><td>Frame 9</td> <td>400ms</td></tr>
* <tr><td>Frame 10</td> <td>450ms</td></tr>
* </table>
* We are requested to seek to 333ms, so we discard frames until we hit an
* timecode larger than the requested. We would seek to Frame 7 at 300ms.
* </p>
*
* @param timecode Timecode to seek to in millseconds
* @return Actual timecode we seeked to
*/
public long seek(long timecode) {
return 0;
}
private void _fillFrameQueue() {
if (level0 == null)
throw new java.lang.IllegalStateException("Call readFile() before reading frames");
synchronized (level0) {
Element level1 = ((MasterElement)level0).readNextChild(reader);
while (level1 != null) {
if (level1.equals(MatroskaDocType.Cluster_Id)) {
_parseNextCluster(level1);
}
level1.skipData(ioDS);
level1 = ((MasterElement)level0).readNextChild(reader);
}
}
}
private void _parseNextCluster(Element level1) {
Element level2 = null;
Element level3 = null;
level2 = ((MasterElement)level1).readNextChild(reader);
while (level2 != null) {
if (level2.equals(MatroskaDocType.ClusterTimecode_Id)) {
level2.readData(ioDS);
ClusterTimecode = ((UnsignedIntegerElement)level2).getValue();
}else if(level2.equals(MatroskaDocType.ClusterSimpleBlock_Id)) {
MatroskaBlock block = null;
long BlockDuration = 0;
long BlockReference = 0;
block = (MatroskaBlock)level2;
block.readData(ioDS);
block.parseBlock();
MatroskaFileFrame frame = new MatroskaFileFrame();
frame.TrackNo = block.getTrackNo();
frame.Timecode = block.getAdjustedBlockTimecode(ClusterTimecode, this.TimecodeScale);
frame.Duration = BlockDuration;
frame.Reference = BlockReference;
frame.Data = block.getFrame(0);
frame.KeyFrame = block.isKeyFrame();
synchronized (FrameQueue) {
FrameQueue.addLast(new MatroskaFileFrame(frame));
}
if (block.getFrameCount() > 1) {
for (int f = 1; f < block.getFrameCount(); f++) {
frame.Data = block.getFrame(f);
synchronized (FrameQueue) {
FrameQueue.addLast(new MatroskaFileFrame(frame));
}
}
}
level2.skipData(ioDS);
} else if (level2.equals(MatroskaDocType.ClusterBlockGroup_Id)) {
MatroskaBlock block = null;
long BlockDuration = 0;
long BlockReference = 0;
level3 = ((MasterElement)level2).readNextChild(reader);
while (level3 != null) {
if (level3.equals(MatroskaDocType.ClusterBlock_Id)) {
block = (MatroskaBlock)level3;
block.readData(ioDS);
block.parseBlock();
} else if (level3.equals(MatroskaDocType.ClusterBlockDuration_Id)) {
level3.readData(ioDS);
BlockDuration = ((UnsignedIntegerElement)level3).getValue();
} else if (level3.equals(MatroskaDocType.ClusterReferenceBlock_Id)) {
level3.readData(ioDS);
BlockReference = ((SignedIntegerElement)level3).getValue();
}
level3.skipData(ioDS);
level3 = ((MasterElement)level2).readNextChild(reader);
}
if (block == null)
throw new java.lang.NullPointerException("BlockGroup element with no child Block!");
MatroskaFileFrame frame = new MatroskaFileFrame();
frame.TrackNo = block.getTrackNo();
frame.Timecode = block.getAdjustedBlockTimecode(ClusterTimecode, this.TimecodeScale);
frame.Duration = BlockDuration;
frame.Reference = BlockReference;
frame.Data = block.getFrame(0);
synchronized (FrameQueue) {
FrameQueue.addLast(new MatroskaFileFrame(frame));
}
if (block.getFrameCount() > 1) {
for (int f = 1; f < block.getFrameCount(); f++) {
frame.Data = block.getFrame(f);
/*if (badMP3Headers()) {
throw new RuntimeException("Bad Data!");
}*/
synchronized (FrameQueue) {
FrameQueue.addLast(new MatroskaFileFrame(frame));
}
/*if (badMP3Headers()) {
throw new RuntimeException("Bad Data!");
}*/
}
}
}
level2.skipData(ioDS);
level2 = ((MasterElement)level1).readNextChild(reader);
}
}
protected boolean badMP3Headers() {
TLinkedList.IteratorImpl iter = FrameQueue.listIterator();
while (iter.hasNext()) {
MatroskaFileFrame frame = (MatroskaFileFrame)iter.next();
if (frame.TrackNo == 2
&& frame.Data[3] != 0x54)
{
throw new RuntimeException("Bad MP3 Header! Index: " + iter.nextIndex());
}
}
return false;
}
private void _parseSegmentInfo(Element level1, Element level2) {
level2 = ((MasterElement)level1).readNextChild(reader);
while (level2 != null) {
if (level2.equals(MatroskaDocType.Title_Id)) {
level2.readData(ioDS);
SegmentTitle = ((StringElement)level2).getValue();
} else if (level2.equals(MatroskaDocType.DateUTC_Id)) {
level2.readData(ioDS);
SegmentDate = ((DateElement)level2).getDate();
} else if (level2.equals(MatroskaDocType.MuxingApp_Id)) {
level2.readData(ioDS);
MuxingApp = ((StringElement)level2).getValue();
} else if (level2.equals(MatroskaDocType.WritingApp_Id)) {
level2.readData(ioDS);
WritingApp = ((StringElement)level2).getValue();
} else if (level2.equals(MatroskaDocType.Duration_Id)) {
level2.readData(ioDS);
Duration = ((FloatElement)level2).getValue();
} else if (level2.equals(MatroskaDocType.TimecodeScale_Id)) {
level2.readData(ioDS);
TimecodeScale = ((UnsignedIntegerElement)level2).getValue();
}
level2.skipData(ioDS);
level2 = ((MasterElement)level1).readNextChild(reader);
}
}
private void _parseTracks(Element level1, Element level2) {
Element level3 = null;
Element level4 = null;
level2 = ((MasterElement)level1).readNextChild(reader);
while (level2 != null) {
if (level2.equals(MatroskaDocType.TrackEntry_Id)) {
MatroskaFileTrack track = new MatroskaFileTrack();
level3 = ((MasterElement)level2).readNextChild(reader);
while (level3 != null) {
if (level3.equals(MatroskaDocType.TrackNumber_Id)) {
level3.readData(ioDS);
track.TrackNo = (short)((UnsignedIntegerElement)level3).getValue();
} else if (level3.equals(MatroskaDocType.TrackUID_Id)) {
level3.readData(ioDS);
track.TrackUID = ((UnsignedIntegerElement)level3).getValue();
} else if (level3.equals(MatroskaDocType.TrackType_Id)) {
level3.readData(ioDS);
track.TrackType = (byte)((UnsignedIntegerElement)level3).getValue();
} else if (level3.equals(MatroskaDocType.TrackDefaultDuration_Id)) {
level3.readData(ioDS);
track.DefaultDuration = ((UnsignedIntegerElement)level3).getValue();
} else if (level3.equals(MatroskaDocType.TrackName_Id)) {
level3.readData(ioDS);
track.Name = ((StringElement)level3).getValue();
} else if (level3.equals(MatroskaDocType.TrackLanguage_Id)) {
level3.readData(ioDS);
track.Language = ((StringElement)level3).getValue();
} else if (level3.equals(MatroskaDocType.TrackCodecID_Id)) {
level3.readData(ioDS);
track.CodecID = ((StringElement)level3).getValue();
} else if (level3.equals(MatroskaDocType.TrackCodecPrivate_Id)) {
level3.readData(ioDS);
track.CodecPrivate = ((BinaryElement)level3).getData();
} else if (level3.equals(MatroskaDocType.TrackVideo_Id)) {
level4 = ((MasterElement)level3).readNextChild(reader);
while (level4 != null) {
if (level4.equals(MatroskaDocType.PixelWidth_Id)) {
level4.readData(ioDS);
track.Video_PixelWidth = (short)((UnsignedIntegerElement)level4).getValue();
} else if (level4.equals(MatroskaDocType.PixelHeight_Id)) {
level4.readData(ioDS);
track.Video_PixelHeight = (short)((UnsignedIntegerElement)level4).getValue();
} else if (level4.equals(MatroskaDocType.DisplayWidth_Id)) {
level4.readData(ioDS);
track.Video_DisplayWidth = (short)((UnsignedIntegerElement)level4).getValue();
} else if (level4.equals(MatroskaDocType.DisplayHeight_Id)) {
level4.readData(ioDS);
track.Video_DisplayHeight = (short)((UnsignedIntegerElement)level4).getValue();
}
level4.skipData(ioDS);
level4 = ((MasterElement)level3).readNextChild(reader);
}
} else if (level3.equals(MatroskaDocType.TrackAudio_Id)) {
level4 = ((MasterElement)level3).readNextChild(reader);
while (level4 != null) {
if (level4.equals(MatroskaDocType.SamplingFrequency_Id)) {
level4.readData(ioDS);
track.Audio_SamplingFrequency = (float)((FloatElement)level4).getValue();
} else if (level4.equals(MatroskaDocType.OutputSamplingFrequency_Id)) {
level4.readData(ioDS);
track.Audio_OutputSamplingFrequency = (float)((FloatElement)level4).getValue();
} else if (level4.equals(MatroskaDocType.Channels_Id)) {
level4.readData(ioDS);
track.Audio_Channels = (short)((UnsignedIntegerElement)level4).getValue();
} else if (level4.equals(MatroskaDocType.BitDepth_Id)) {
level4.readData(ioDS);
track.Audio_BitDepth = (byte)((UnsignedIntegerElement)level4).getValue();
}
level4.skipData(ioDS);
level4 = ((MasterElement)level3).readNextChild(reader);
}
}
level3.skipData(ioDS);
level3 = ((MasterElement)level2).readNextChild(reader);
}
TrackList.add(track);
}
level2.skipData(ioDS);
level2 = ((MasterElement)level1).readNextChild(reader);
}
}
private void _parseTags(Element level1, Element level2) {
Element level3 = null;
Element level4 = null;
level2 = ((MasterElement)level1).readNextChild(reader);
while (level2 != null) {
if (level2.equals(MatroskaDocType.Tag_Id)) {
MatroskaFileTagEntry tag = new MatroskaFileTagEntry();
level3 = ((MasterElement)level2).readNextChild(reader);
while (level3 != null) {
if (level3.equals(MatroskaDocType.TagTargets_Id)) {
level4 = ((MasterElement)level3).readNextChild(reader);
while (level4 != null) {
if (level4.equals(MatroskaDocType.TagTargetTrackUID_Id)) {
level4.readData(ioDS);
tag.TrackUID.add(new Long(((UnsignedIntegerElement)level4).getValue()));
} else if (level4.equals(MatroskaDocType.TagTargetChapterUID_Id)) {
level4.readData(ioDS);
tag.ChapterUID.add(new Long(((UnsignedIntegerElement)level4).getValue()));
} else if (level4.equals(MatroskaDocType.TagTargetAttachmentUID_Id)) {
level4.readData(ioDS);
tag.AttachmentUID.add(new Long(((UnsignedIntegerElement)level4).getValue()));
}
level4.skipData(ioDS);
level4 = ((MasterElement)level3).readNextChild(reader);
}
} else if (level3.equals(MatroskaDocType.TagSimpleTag_Id)) {
tag.SimpleTags.add(_parseTagsSimpleTag(level3, level4));
}
level3.skipData(ioDS);
level3 = ((MasterElement)level2).readNextChild(reader);
}
TagList.add(tag);
}
level2.skipData(ioDS);
level2 = ((MasterElement)level1).readNextChild(reader);
}
}
private MatroskaFileSimpleTag _parseTagsSimpleTag(Element level3, Element level4) {
MatroskaFileSimpleTag SimpleTag = new MatroskaFileSimpleTag();
level4 = ((MasterElement)level3).readNextChild(reader);
while (level4 != null) {
if (level4.equals(MatroskaDocType.TagSimpleTagName_Id)) {
level4.readData(ioDS);
SimpleTag.Name = ((StringElement)level4).getValue();
} else if (level4.equals(MatroskaDocType.TagSimpleTagString_Id)) {
level4.readData(ioDS);
SimpleTag.Value = ((StringElement)level4).getValue();
} else if (level4.equals(MatroskaDocType.TagSimpleTag_Id)) {
SimpleTag.Children.add(_parseTagsSimpleTag(level3, level4));
}
level4.skipData(ioDS);
level4 = ((MasterElement)level3).readNextChild(reader);
}
return SimpleTag;
}
/**
* Get a String report for the Matroska file.
* Call readFile() before this method, else the report will be empty.
*
* @return String Report
*/
public String getReport() {
java.io.StringWriter s = new java.io.StringWriter();
int t;
s.write("MatroskaFile report\n");
s.write("Infomation Segment \n");
s.write("\tSegment Title: " + SegmentTitle + "\n");
s.write("\tSegment Date: " + SegmentDate + "\n");
s.write("\tMuxing App : " + MuxingApp + "\n");
s.write("\tWriting App : " + WritingApp + "\n");
s.write("\tDuration : " + Duration/1000 + "sec \n");
s.write("\tTimecodeScale : " + TimecodeScale + "\n");
s.write("Track Count: " + TrackList.size() + "\n");
for (t = 0; t < TrackList.size(); t++) {
s.write("\tTrack " + t + "\n");
s.write(TrackList.get(t).toString());
}
s.write("Tag Count: " + TagList.size() + "\n");
for (t = 0; t < TagList.size(); t++) {
s.write("\tTag Entry \n");
s.write(TagList.get(t).toString());
}
s.write("End report\n");
return s.getBuffer().toString();
}
public String getWritingApp() {
return WritingApp;
}
/**
* Returns an array of the tracks.
* If there are no MatroskaFileTracks to return the returned array
* will have a size of 0.
*
* @return Array of MatroskaFileTrack's
*/
public MatroskaFileTrack [] getTrackList() {
if (TrackList.size() > 0) {
MatroskaFileTrack [] tracks = new MatroskaFileTrack[TrackList.size()];
for (int t = 0; t < TrackList.size(); t++) {
tracks[t] = (MatroskaFileTrack)TrackList.get(t);
}
return tracks;
} else {
return new MatroskaFileTrack[0];
}
}
/**
* <p>
* This differs from the getTrackList method in that this method scans
* each track and returns the one that has the same track number as TrackNo.
* </p>
*
* <p>Note: TrackNo != track index</p>
*
* @param TrackNo The actual track number of the MatroskaFileTrack you would like to get
* @return null if no MatroskaFileTrack is found with the requested TrackNo
*/
public MatroskaFileTrack getTrack(int TrackNo) {
for (int t = 0; t < TrackList.size(); t++) {
MatroskaFileTrack track = (MatroskaFileTrack)TrackList.get(t);
if (track.TrackNo == TrackNo)
return track;
}
return null;
}
/**
* Get the timecode scale for this MatroskaFile.
* In Matroska the timecodes are stored scaled by this value.
* However any MatroskaFileFrame you get through the methods of this class
* will already have the timecodes correctly scaled to millseconds.
*
* @return TimecodeScale
*/
public long getTimecodeScale() {
return TimecodeScale;
}
public String getSegmentTitle() {
return SegmentTitle;
}
public String getMuxingApp() {
return MuxingApp;
}
/**
* Get the duration for this MatroskaFile.
* This is the duration value stored in the segment info.
* Which may or may not be the exact length of all, some, or one of the tracks.
*
* @return Duration in seconds
*/
public double getDuration() {
return Duration;
}
/**
* Sets if the readFile() method should scan the first cluster for infomation.
* Set to false for faster parsing.
*/
public void setScanFirstCluster(boolean scanFirstCluster)
{
ScanFirstCluster = scanFirstCluster;
}
/**
* Gets if the readFile() method should scan the first cluster for infomation.
* When set to false parsing is slightly faster.
*/
public boolean getScanFirstCluster()
{
return ScanFirstCluster;
}
}
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
/**
* 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.matroska;
import org.ebml.util.*;
/**
* Matroska Frame, holds a Matroska frame timecode, duration, and data
*/
public class MatroskaFileFrame
{
/**
* Matroska Frame Puller interface
*/
public interface MatroskaFramePuller
{
public void PushNewMatroskaFrame(MatroskaFileFrame frame);
};
/**
* The track this frame belongs to
*/
public int TrackNo;
/**
* A timecode, it should be in ms
*/
public long Timecode;
/**
* The duration of this frame, it should also be in ms
*/
public long Duration;
/**
* The first reference this frame has, set to 0 for no reference
*/
public long Reference;
/**
* More references, can be null if there are no more references
*/
public long [] References;
/**
* The frame data
*/
public byte [] Data;
public boolean KeyFrame;
/**
* MatroskaFrame Default constructor
*/
public MatroskaFileFrame()
{
//System.out.println("new " + this);
}
/**
* MatroskaFrame Copy constructor
* @param copy MatroskaFrame to copy
*/
public MatroskaFileFrame(MatroskaFileFrame copy)
{
//System.out.println("MatroskaFrame copy " + this);
this.TrackNo = copy.TrackNo;
this.Timecode = copy.Timecode;
this.Duration = copy.Duration;
this.Reference = copy.Reference;
this.KeyFrame = copy.KeyFrame;
if (copy.References != null)
{
this.References = new long[copy.References.length];
ArrayCopy.arraycopy(copy.References, 0, this.References, 0, copy.References.length);
}
if (copy.Data != null)
{
this.Data = new byte[copy.Data.length];
ArrayCopy.arraycopy(copy.Data, 0, this.Data, 0, copy.Data.length);
}
}
public boolean isKeyFrame() {
return KeyFrame;
}
}
/**
* 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.matroska;
import java.util.ArrayList;
public class MatroskaFileSimpleTag
{
public String Name;
public String Value;
public ArrayList<MatroskaFileSimpleTag> Children = new ArrayList<MatroskaFileSimpleTag>();
public String toString(int depth)
{
String s = new String();
String depthIndent = new String();
for (int d = 0; d < depth; d++)
depthIndent += "\t";
s += depthIndent + "SimpleTag\n";
s += depthIndent + "\tName: " + Name + "\n";
s += depthIndent + "\tValue: " + Value + "\n";
depth++;
for (int t = 0; t < Children.size(); t++)
{
s += ((MatroskaFileSimpleTag)Children.get(t)).toString(depth);
}
return s;
}
}
\ 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.matroska;
import java.util.ArrayList;
public class MatroskaFileTagEntry
{
public ArrayList<Long> TrackUID = new ArrayList<Long>();
public ArrayList<Long> ChapterUID = new ArrayList<Long>();
public ArrayList<Long> AttachmentUID = new ArrayList<Long>();
public ArrayList<MatroskaFileSimpleTag> SimpleTags = new ArrayList<MatroskaFileSimpleTag>();
public String toString()
{
String s = new String();
if (TrackUID.size() > 0)
{
s += "\t\t" + "TrackUID: " + TrackUID.toArray().toString() + "\n";
}
if (ChapterUID.size() > 0)
{
s += "\t\t" + "ChapterUID: " + ChapterUID.toArray().toString() + "\n";
}
if (AttachmentUID.size() > 0)
{
s += "\t\t" + "AttachmentUID: " + AttachmentUID.toArray().toString() + "\n";
}
for (int t = 0; t < SimpleTags.size(); t++)
{
s += ((MatroskaFileSimpleTag)SimpleTags.get(t)).toString(2);
}
return s;
}
}
/**
* 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.matroska;
/**
* Matroska Track Class
*/
public class MatroskaFileTrack
{
public short TrackNo;
public long TrackUID;
public byte TrackType;
public long DefaultDuration;
public String Name;
public String Language;
public String CodecID;
public byte [] CodecPrivate;
public short Video_PixelWidth;
public short Video_PixelHeight;
public short Video_DisplayWidth;
public short Video_DisplayHeight;
public float Audio_SamplingFrequency;
public float Audio_OutputSamplingFrequency;
public short Audio_Channels;
public byte Audio_BitDepth;
/**
* Converts the Track to String form
* @return String form of MatroskaFileTrack data
*/
public String toString()
{
String s = new String();
s += "\t\t" + "TrackNo: " + TrackNo + "\n";
s += "\t\t" + "TrackUID: " + TrackUID + "\n";
s += "\t\t" + "TrackType: " + MatroskaDocType.TrackTypeToString(TrackType) + "\n";
s += "\t\t" + "DefaultDuration: " + DefaultDuration + "\n";
s += "\t\t" + "Name: " + Name + "\n";
s += "\t\t" + "Language: " + Language + "\n";
s += "\t\t" + "CodecID: " + CodecID + "\n";
if (CodecPrivate != null)
s += "\t\t" + "CodecPrivate: " + CodecPrivate.length + " byte(s)" + "\n";
if (TrackType == MatroskaDocType.track_video)
{
s += "\t\t" + "PixelWidth: " + Video_PixelWidth + "\n";
s += "\t\t" + "PixelHeight: " + Video_PixelHeight + "\n";
s += "\t\t" + "DisplayWidth: " + Video_DisplayWidth + "\n";
s += "\t\t" + "DisplayHeight: " + Video_DisplayHeight + "\n";
}
if (TrackType == MatroskaDocType.track_audio)
{
s += "\t\t" + "SamplingFrequency: " + Audio_SamplingFrequency + "\n";
if (Audio_OutputSamplingFrequency != 0)
s += "\t\t" + "OutputSamplingFrequency: " + Audio_OutputSamplingFrequency + "\n";
s += "\t\t" + "Channels: " + Audio_Channels + "\n";
if (Audio_BitDepth != 0)
s += "\t\t" + "BitDepth: " + Audio_BitDepth + "\n";
}
return s;
}
}
/**
* 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.matroska;
import java.util.ArrayList;
import java.util.Date;
import org.ebml.BinaryElement;
import org.ebml.DateElement;
import org.ebml.FloatElement;
import org.ebml.MasterElement;
import org.ebml.StringElement;
import org.ebml.UnsignedIntegerElement;
import org.ebml.io.DataWriter;
/**
* Summary description for MatroskaFileWriter.
*/
public class MatroskaFileWriter
{
protected DataWriter ioDW;
private MatroskaCluster clusterElem = null;
private long clusterTimecode;
protected MatroskaDocType doc = new MatroskaDocType();
public long TimecodeScale = 1000000;
public double Duration = 60.0;
public Date SegmentDate = new Date();
public ArrayList<MatroskaFileTrack> TrackList = new ArrayList<MatroskaFileTrack>();
public MatroskaFileWriter(DataWriter outputDataWriter)
{
ioDW = outputDataWriter;
}
public void writeEBMLHeader()
{
MasterElement ebmlHeaderElem = (MasterElement)doc.createElement(MatroskaDocType.EBMLHeader_Id);
UnsignedIntegerElement ebml1 = (UnsignedIntegerElement)doc.createElement(MatroskaDocType.EBMLVersion_Id);
ebml1.setValue(1);
UnsignedIntegerElement ebml2 = (UnsignedIntegerElement)doc.createElement(MatroskaDocType.EBMLReadVersion_Id);
ebml2.setValue(1);
UnsignedIntegerElement ebml3 = (UnsignedIntegerElement)doc.createElement(MatroskaDocType.EBMLMaxIDLength_Id);
ebml3.setValue(4);
UnsignedIntegerElement ebml4 = (UnsignedIntegerElement)doc.createElement(MatroskaDocType.EBMLMaxSizeLength_Id);
ebml4.setValue(8);
StringElement docTypeElem = (StringElement)doc.createElement(MatroskaDocType.DocType_Id);
docTypeElem.setValue("webm");
UnsignedIntegerElement docTypeVersionElem = (UnsignedIntegerElement)doc.createElement(MatroskaDocType.DocTypeVersion_Id);
docTypeVersionElem.setValue(2);
UnsignedIntegerElement docTypeReadVersionElem = (UnsignedIntegerElement)doc.createElement(MatroskaDocType.DocTypeReadVersion_Id);
docTypeReadVersionElem.setValue(2);
ebmlHeaderElem.addChildElement(ebml1);
ebmlHeaderElem.addChildElement(ebml2);
ebmlHeaderElem.addChildElement(ebml3);
ebmlHeaderElem.addChildElement(ebml4);
ebmlHeaderElem.addChildElement(docTypeElem);
ebmlHeaderElem.addChildElement(docTypeVersionElem);
ebmlHeaderElem.addChildElement(docTypeReadVersionElem);
ebmlHeaderElem.writeElement(ioDW);
}
public void writeSegmentHeader()
{
MatroskaSegment segmentElem = (MatroskaSegment)doc.createElement(MatroskaDocType.Segment_Id);
//segmentElem.setSize(-1);
segmentElem.setUnknownSize(true);
segmentElem.writeHeaderData(ioDW);
}
public void writeSegmentInfo()
{
MasterElement segmentInfoElem = (MasterElement)doc.createElement(MatroskaDocType.SegmentInfo_Id);
StringElement writingAppElem = (StringElement)doc.createElement(MatroskaDocType.WritingApp_Id);
writingAppElem.setValue("Matroska File Writer v1.0");
StringElement muxingAppElem = (StringElement)doc.createElement(MatroskaDocType.MuxingApp_Id);
muxingAppElem.setValue("JEBML v1.0");
DateElement dateElem = (DateElement)doc.createElement(MatroskaDocType.DateUTC_Id);
dateElem.setDate(SegmentDate);
//Add timecode scale
UnsignedIntegerElement timecodescaleElem = (UnsignedIntegerElement)doc.createElement(MatroskaDocType.TimecodeScale_Id);
timecodescaleElem.setValue(TimecodeScale);
FloatElement durationElem = (FloatElement)doc.createElement(MatroskaDocType.Duration_Id);
durationElem.setValue(Duration * 1000.0);
segmentInfoElem.addChildElement(dateElem);
segmentInfoElem.addChildElement(timecodescaleElem);
segmentInfoElem.addChildElement(durationElem);
segmentInfoElem.addChildElement(writingAppElem);
segmentInfoElem.addChildElement(muxingAppElem);
segmentInfoElem.writeElement(ioDW);
}
public void writeTracks()
{
MasterElement tracksElem = (MasterElement)doc.createElement(MatroskaDocType.Tracks_Id);
for (int i = 0; i < TrackList.size(); i++)
{
MatroskaFileTrack track = (MatroskaFileTrack)TrackList.get(i);
MasterElement trackEntryElem = (MasterElement)doc.createElement(MatroskaDocType.TrackEntry_Id);
UnsignedIntegerElement trackNoElem = (UnsignedIntegerElement)doc.createElement(MatroskaDocType.TrackNumber_Id);
trackNoElem.setValue(track.TrackNo);
UnsignedIntegerElement trackUIDElem = (UnsignedIntegerElement)doc.createElement(MatroskaDocType.TrackUID_Id);
trackUIDElem.setValue(track.TrackUID);
UnsignedIntegerElement trackTypeElem = (UnsignedIntegerElement)doc.createElement(MatroskaDocType.TrackType_Id);
trackTypeElem.setValue(track.TrackType);
StringElement trackNameElem = (StringElement)doc.createElement(MatroskaDocType.TrackName_Id);
trackNameElem.setValue(track.Name);
StringElement trackLangElem = (StringElement)doc.createElement(MatroskaDocType.TrackLanguage_Id);
trackLangElem.setValue(track.Language);
StringElement trackCodecIDElem = (StringElement)doc.createElement(MatroskaDocType.TrackCodecID_Id);
trackCodecIDElem.setValue(track.CodecID);
BinaryElement trackCodecPrivateElem = (BinaryElement)doc.createElement(MatroskaDocType.TrackCodecPrivate_Id);
trackCodecPrivateElem.setData(track.CodecPrivate);
UnsignedIntegerElement trackDefaultDurationElem = (UnsignedIntegerElement)doc.createElement(MatroskaDocType.TrackDefaultDuration_Id);
trackDefaultDurationElem.setValue(track.DefaultDuration);
trackEntryElem.addChildElement(trackNoElem);
trackEntryElem.addChildElement(trackUIDElem);
trackEntryElem.addChildElement(trackTypeElem);
trackEntryElem.addChildElement(trackNameElem);
trackEntryElem.addChildElement(trackLangElem);
trackEntryElem.addChildElement(trackCodecIDElem);
trackEntryElem.addChildElement(trackCodecPrivateElem);
trackEntryElem.addChildElement(trackDefaultDurationElem);
// Now we add the audio/video dependant sub-elements
if (track.TrackType == MatroskaDocType.track_video)
{
MasterElement trackVideoElem = (MasterElement)doc.createElement(MatroskaDocType.TrackVideo_Id);
UnsignedIntegerElement trackVideoPixelWidthElem = (UnsignedIntegerElement)doc.createElement(MatroskaDocType.PixelWidth_Id);
trackVideoPixelWidthElem.setValue(track.Video_PixelWidth);
UnsignedIntegerElement trackVideoPixelHeightElem = (UnsignedIntegerElement)doc.createElement(MatroskaDocType.PixelHeight_Id);
trackVideoPixelHeightElem.setValue(track.Video_PixelHeight);
UnsignedIntegerElement trackVideoDisplayWidthElem = (UnsignedIntegerElement)doc.createElement(MatroskaDocType.DisplayWidth_Id);
trackVideoDisplayWidthElem.setValue(track.Video_DisplayWidth);
UnsignedIntegerElement trackVideoDisplayHeightElem = (UnsignedIntegerElement)doc.createElement(MatroskaDocType.DisplayHeight_Id);
trackVideoDisplayHeightElem.setValue(track.Video_DisplayHeight);
trackVideoElem.addChildElement(trackVideoPixelWidthElem);
trackVideoElem.addChildElement(trackVideoPixelHeightElem);
trackVideoElem.addChildElement(trackVideoDisplayWidthElem);
trackVideoElem.addChildElement(trackVideoDisplayHeightElem);
trackEntryElem.addChildElement(trackVideoElem);
}
else if (track.TrackType == MatroskaDocType.track_audio)
{
MasterElement trackAudioElem = (MasterElement)doc.createElement(MatroskaDocType.TrackVideo_Id);
UnsignedIntegerElement trackAudioChannelsElem = (UnsignedIntegerElement)doc.createElement(MatroskaDocType.Channels_Id);
trackAudioChannelsElem.setValue(track.Audio_Channels);
UnsignedIntegerElement trackAudioBitDepthElem = (UnsignedIntegerElement)doc.createElement(MatroskaDocType.BitDepth_Id);
trackAudioBitDepthElem.setValue(track.Audio_BitDepth);
FloatElement trackAudioSamplingRateElem = (FloatElement)doc.createElement(MatroskaDocType.SamplingFrequency_Id);
trackAudioSamplingRateElem.setValue(track.Audio_SamplingFrequency);
FloatElement trackAudioOutputSamplingFrequencyElem = (FloatElement)doc.createElement(MatroskaDocType.OutputSamplingFrequency_Id);
trackAudioOutputSamplingFrequencyElem.setValue(track.Audio_OutputSamplingFrequency);
trackAudioElem.addChildElement(trackAudioChannelsElem);
trackAudioElem.addChildElement(trackAudioBitDepthElem);
trackAudioElem.addChildElement(trackAudioSamplingRateElem);
trackAudioElem.addChildElement(trackAudioOutputSamplingFrequencyElem);
trackEntryElem.addChildElement(trackAudioElem);
}
tracksElem.addChildElement(trackEntryElem);
}
tracksElem.writeElement(ioDW);
}
public void startCluster(long clusterTimecode)
{
clusterElem = (MatroskaCluster)doc.createElement(MatroskaDocType.Cluster_Id);
UnsignedIntegerElement clusterTimecodeElem = (UnsignedIntegerElement)doc.createElement(MatroskaDocType.ClusterTimecode_Id);
clusterTimecodeElem.setValue(clusterTimecode);
clusterElem.addChildElement(clusterTimecodeElem);
this.clusterTimecode = clusterTimecode;
}
public void endCluster()
{
if (clusterElem != null) clusterElem.writeElement(ioDW);
}
/**
* Add a frame
*
* @param frame The frame to add
*/
public void addFrame(MatroskaFileFrame frame)
{
MatroskaBlock simpleBlockElem = (MatroskaBlock)doc.createElement(MatroskaDocType.ClusterSimpleBlock_Id);
simpleBlockElem.setFrameData(frame.TrackNo, frame.Timecode - clusterTimecode, frame.Data);
clusterElem.addChildElement(simpleBlockElem);
}
}
/**
* 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.matroska;
import org.ebml.*;
import org.ebml.io.*;
/**
* Summary description for MatroskaSegment.
*/
public class MatroskaSegment extends MasterElement
{
protected boolean bUnknownSize = false;
public MatroskaSegment(byte[] type)
{
super(type);
}
/** Write the element header data.
* The override will write the size as unknown if the flag is set.
*/
public long writeHeaderData(DataWriter writer)
{
long len = 0;
byte [] type = getType();
len += type.length;
writer.write(type);
byte [] size;
if (bUnknownSize)
{
size = new byte[5];
size[0] = (byte)(0xFF >>> (size.length-1));
for (int i = 1; i < size.length; i++)
size[i] = (byte)0xFF;
}
else
{
size = Element.makeEbmlCodedSize(getSize());
}
len += size.length;
writer.write(size);
return len;
}
/**
* Setter for Unknown Size flag.
* This is a special case for ebml. The size value is filled with 1's.
*/
public void setUnknownSize(boolean bUnknownSize)
{
this.bUnknownSize = bUnknownSize;
}
/**
* Getter for Unknown Size flag.
*/
public boolean getUnknownSize()
{
return bUnknownSize;
}
}
package org.ebml.sample;
import java.io.FileOutputStream;
import java.io.IOException;
import org.ebml.io.FileDataSource;
import org.ebml.io.FileDataWriter;
import org.ebml.matroska.MatroskaFile;
import org.ebml.matroska.MatroskaFileFrame;
import org.ebml.matroska.MatroskaFileTrack;
import org.ebml.matroska.MatroskaFileWriter;
/**
* <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 CommandLineSample {
public static void main(String[] args) {
System.out.println("JEBML CommandLineSample - (c) 2004 Jory 'jcsston' Stone <jcsston@toughguy.net>");
if (args.length < 3) {
System.out.println("Please provide a command and matroska filename on the command-line");
return;
}
try {
String mode = args[1];
if (mode.compareTo("-i") == 0)
{
readFile(args[2]);
}
else if (mode.compareTo("-o") == 0)
{
writeFile(args[2]);
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
static void readFile(String filename) throws IOException
{
System.out.println("Scanning file: " + filename);
long startTime = System.currentTimeMillis();
FileDataSource iFS = new FileDataSource(filename);
MatroskaFile mF = new MatroskaFile(iFS);
mF.setScanFirstCluster(true);
mF.readFile();
System.out.println(mF.getReport());
MatroskaFileTrack track = mF.getTrack(1);
if (track.CodecID.compareTo("A_MPEG/L3") == 0)
{
System.out.println("Extracting mp3 track");
String outputFilename = filename + ".mp3";// + ".wav";
FileOutputStream oFS = new FileOutputStream(outputFilename);
/*
WavLib.WaveFormatEx wfx = new WavLib.WaveFormatEx();
wfx.wFormatTag = 0x55; // MP3
wfx.nSamplesPerSec = (int)track.Audio_SamplingFrequency;
if (track.Audio_Channels == 0) {
wfx.nChannels = 1;
} else {
wfx.nChannels = track.Audio_Channels;
}
if (track.Audio_BitDepth == 0) {
wfx.wBitsPerSample = 16;
} else {
wfx.wBitsPerSample = track.Audio_BitDepth;
}
//wfx.nBlockAlign = 4;
WavLib.WavWriter writer = new WavLib.WavWriter();
writer.Open(outputFilename, wfx);
*/
MatroskaFileFrame frame = mF.getNextFrame();
while (frame != null)
{
oFS.write(frame.Data);
//writer.WriteSampleData(frame.Data, 0, frame.Data.length);
frame = mF.getNextFrame();
}
//writer.Close();
}
long endTime = System.currentTimeMillis();
System.out.println("Scan complete. Took: " + ((endTime - startTime) / 1000.0) + " seconds");
}
static void writeFile(String filename) throws IOException
{
System.out.println("Write file: " + filename);
FileDataWriter iFW = new FileDataWriter(filename);
MatroskaFileWriter mFW = new MatroskaFileWriter(iFW);
mFW.writeEBMLHeader();
mFW.writeSegmentHeader();
mFW.writeSegmentInfo();
for (int i = 0; i < 5; i++)
{
MatroskaFileTrack track = new MatroskaFileTrack();
track.TrackNo = (short)i;
track.TrackUID = new java.util.Random().nextLong();
track.TrackType = 1;
track.Name = "Track " + Integer.toString(i);
track.Video_PixelWidth = 320;
track.Video_PixelHeight = 240;
mFW.TrackList.add(track);
}
mFW.writeTracks();
System.out.println("Write complete");
}
}
package org.ebml.sample;
import java.awt.*;
import javax.swing.*;
/**
* <p>Title: EBMLReader</p>
* <p>Description: Java Classes to Read EBML Elements</p>
* <p>Copyright: Copyright (c) 2004 Jory Stone <jcsston@toughguy.net></p>
* <p>Company: </p>
* @author jcsston
* @version 1.0
*/
public class EbmlSampleApp {
boolean packFrame = false;
//Construct the application
public EbmlSampleApp() {
EbmlSampleAppFrame frame = new EbmlSampleAppFrame();
//Validate frames that have preset sizes
//Pack frames that have useful preferred size info, e.g. from their layout
if (packFrame) {
frame.pack();
}
else {
frame.validate();
}
//Center the window
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
Dimension frameSize = frame.getSize();
if (frameSize.height > screenSize.height) {
frameSize.height = screenSize.height;
}
if (frameSize.width > screenSize.width) {
frameSize.width = screenSize.width;
}
frame.setLocation((screenSize.width - frameSize.width) / 2,
(screenSize.height - frameSize.height) / 2);
frame.setVisible(true);
}
//Main method
public static void main(String[] args) {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
}
catch (Exception e) {
e.printStackTrace();
}
new EbmlSampleApp();
}
}
\ No newline at end of file
package org.ebml.sample;
import java.io.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import org.ebml.io.*;
import org.ebml.matroska.*;
/**
* <p>Title: EBMLReader</p>
* <p>Description: Java Classes to Read EBML Elements</p>
* <p>Copyright: Copyright (c) 2004 Jory Stone <jcsston@toughguy.net></p>
* <p>Company: </p>
* @author jcsston
* @version 1.0
*/
public class EbmlSampleAppFrame
extends JFrame {
private static final long serialVersionUID = 1L;
JPanel contentPane;
JMenuBar jMenuBar1 = new JMenuBar();
JMenu jMenuFile = new JMenu();
JMenuItem jMenuFileExit = new JMenuItem();
BorderLayout borderLayout1 = new BorderLayout();
JMenuItem jMenuItemOpen = new JMenuItem();
JFileChooser jFileChooser1 = new JFileChooser();
JTextArea jTextArea1 = new JTextArea();
//Construct the frame
public EbmlSampleAppFrame() {
enableEvents(AWTEvent.WINDOW_EVENT_MASK);
try {
jbInit();
}
catch (Exception e) {
e.printStackTrace();
}
}
//Component initialization
private void jbInit() throws Exception {
contentPane = (JPanel)this.getContentPane();
contentPane.setLayout(borderLayout1);
this.setSize(new Dimension(400, 300));
this.setTitle("Ebml Sample App");
jMenuFile.setText("File");
jMenuFileExit.setText("Exit");
jMenuFileExit.addActionListener(new
EbmlSampleAppFrame_jMenuFileExit_ActionAdapter(this));
jMenuItemOpen.setText("Open");
jMenuItemOpen.addActionListener(new
EbmlSampleAppFrame_jMenuItemOpen_actionAdapter(this));
jFileChooser1.setAcceptAllFileFilterUsed(true);
jFileChooser1.setDialogTitle("Select a Matroska File");
jFileChooser1.setFileFilter(null);
jTextArea1.setTabSize(8);
jMenuFile.add(jMenuItemOpen);
jMenuFile.add(jMenuFileExit);
jMenuBar1.add(jMenuFile);
contentPane.add(jTextArea1, BorderLayout.CENTER);
this.setJMenuBar(jMenuBar1);
}
//File | Exit action performed
public void jMenuFileExit_actionPerformed(ActionEvent e) {
System.exit(0);
}
//Overridden so we can exit when window is closed
protected void processWindowEvent(WindowEvent e) {
super.processWindowEvent(e);
if (e.getID() == WindowEvent.WINDOW_CLOSING) {
jMenuFileExit_actionPerformed(null);
}
}
void jMenuItemOpen_actionPerformed(ActionEvent e) {
try {
jFileChooser1.setFileFilter(new org.ebml.matroska.MatroskaFileFilter());
int ret = jFileChooser1.showOpenDialog(this);
if (ret == JFileChooser.APPROVE_OPTION) {
/*
FileInputStream inFS1 = new FileInputStream("I:\\videos\\111-Videos_111\\Linkin Park - Crawling-remux.mkvmerge.mp3");
FileOutputStream outFS1 = new FileOutputStream("I:\\videos\\111-Videos_111\\Linkin Park - Crawling-remux.mkvmerge.java.mp3");
byte [] buffer = new byte[64];
int len = 64;
while (len > 0) {
len = inFS1.read(buffer);
outFS1.write(buffer, 0, len);
}
*/
//FileOutputStream outFS = new FileOutputStream(jFileChooser1.getSelectedFile() + ".mp3");
FileInputStream ioF = new FileInputStream(jFileChooser1.getSelectedFile());
jTextArea1.append("Scanning file: " + jFileChooser1.getSelectedFile().toString() + "\n");
MatroskaFile mF = new MatroskaFile(new InputStreamDataSource(ioF));
mF.readFile();
jTextArea1.append(mF.getReport());
/*MatroskaFile.MatroskaFrame frame = mF.getNextFrame(2);
while (frame != null) {
frame = mF.getNextFrame(2);
if (frame != null)
outFS.write(frame.Data);
}*/
jTextArea1.append("Scan complete.\n");
}
} catch (java.io.FileNotFoundException ex) {
jTextArea1.append("File Not Found!\n");
ex.printStackTrace();
} catch (java.lang.RuntimeException ex) {
jTextArea1.append("Error: " + ex.toString() + ex.getMessage() + "\"\n");
ex.printStackTrace();
}
}
}
class EbmlSampleAppFrame_jMenuFileExit_ActionAdapter
implements ActionListener {
EbmlSampleAppFrame adaptee;
EbmlSampleAppFrame_jMenuFileExit_ActionAdapter(EbmlSampleAppFrame adaptee) {
this.adaptee = adaptee;
}
public void actionPerformed(ActionEvent e) {
adaptee.jMenuFileExit_actionPerformed(e);
}
}
class EbmlSampleAppFrame_jMenuItemOpen_actionAdapter
implements java.awt.event.ActionListener {
EbmlSampleAppFrame adaptee;
EbmlSampleAppFrame_jMenuItemOpen_actionAdapter(EbmlSampleAppFrame adaptee) {
this.adaptee = adaptee;
}
public void actionPerformed(ActionEvent e) {
adaptee.jMenuItemOpen_actionPerformed(e);
}
}
package org.ebml.sample;
import java.io.FileOutputStream;
import java.io.IOException;
import org.ebml.io.FileDataSource;
import org.ebml.matroska.MatroskaFile;
import org.ebml.matroska.MatroskaFileFrame;
import org.ebml.matroska.MatroskaFileTrack;
/**
* <p>Title: JEBML</p>
* <p>Description: Java Classes to Extract keyframes from WebM Files as WebP Images</p>
* <p>Copyright: Copyright (c) 2011 Brooss</p>
* <p>Company: </p>
* @author brooss
* @version 1.0
*/
public class WebMExtract {
public static void main(String[] args) {
System.out.println("JEBML WebMExtract");
if (args.length < 1) {
System.out.println("Please provide a WebM filename on the command-line");
return;
}
try {
readFile(args[0]);
} catch (IOException ex) {
ex.printStackTrace();
}
}
static void readFile(String filename) throws IOException
{
System.out.println("Scanning file: " + filename);
long startTime = System.currentTimeMillis();
FileDataSource iFS = new FileDataSource(filename);
MatroskaFile mF = new MatroskaFile(iFS);
mF.setScanFirstCluster(true);
mF.readFile();
System.out.println(mF.getReport());
MatroskaFileTrack track=null;
for(MatroskaFileTrack t : mF.getTrackList() ) {
if(t.CodecID.compareTo("V_VP8")==0)
track = t;
}
if (track!=null)
{
MatroskaFileFrame frame = mF.getNextFrame(track.TrackNo);
int count=0;
while (frame != null)
{
if(frame.isKeyFrame()) {
System.out.println("Extracting VP8 frame "+count);
String outputFilename = filename + ""+count+".webp";// + ".wav";
FileOutputStream oFS = new FileOutputStream(outputFilename);
oFS.write("RIFF".getBytes());
writeIntLE(oFS, frame.Data.length+20-8);
oFS.write("WEBPVP8".getBytes());
oFS.write(0x20);
writeIntLE(oFS, (frame.Data.length+20-8)-0xc);
oFS.write(frame.Data);
}
frame = mF.getNextFrame(track.TrackNo);
count++;
}
}
long endTime = System.currentTimeMillis();
System.out.println("Scan complete. Took: " + ((endTime - startTime) / 1000.0) + " seconds");
}
public static void writeIntLE(FileOutputStream out, int value) {
try {
out.write(value & 0xFF);
out.write((value >> 8) & 0xFF);
out.write((value >> 16) & 0xFF);
out.write((value >> 24) & 0xFF);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/**
* 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.util;
/**
* Static methods for copying arrays
*/
public class ArrayCopy
{
/**
* Private construct as this class only has static methods
*/
private ArrayCopy()
{
}
public static void arraycopy(byte [] src, int src_offset, byte [] dest, int dest_offset, int count)
{
for (int i = 0; i < count; i++)
{
dest[dest_offset + i] = src[src_offset + i];
}
}
public static void arraycopy(char [] src, int src_offset, char [] dest, int dest_offset, int count)
{
for (int i = 0; i < count; i++)
{
dest[dest_offset + i] = src[src_offset + i];
}
}
public static void arraycopy(short [] src, int src_offset, short [] dest, int dest_offset, int count)
{
for (int i = 0; i < count; i++)
{
dest[dest_offset + i] = src[src_offset + i];
}
}
public static void arraycopy(int [] src, int src_offset, int [] dest, int dest_offset, int count)
{
for (int i = 0; i < count; i++)
{
dest[dest_offset + i] = src[src_offset + i];
}
}
public static void arraycopy(long [] src, int src_offset, long [] dest, int dest_offset, int count)
{
for (int i = 0; i < count; i++)
{
dest[dest_offset + i] = src[src_offset + i];
}
}
public static void arraycopy(Object [] src, int src_offset, Object [] dest, int dest_offset, int count)
{
for (int i = 0; i < count; i++)
{
dest[dest_offset + i] = src[src_offset + i];
}
}
}
package org.ebml.util;
/**
* A LinkedList implementation which holds instances of type
* <tt>TLinkable</tt>.
*
* <p>Using this implementation allows you to get java.util.LinkedList
* behavior (a doubly linked list, with Iterators that support insert
* and delete operations) without incurring the overhead of creating
* <tt>Node</tt> wrapper objects for every element in your list.</p>
*
* <p>The requirement to achieve this time/space gain is that the
* Objects stored in the List implement the <tt>TLinkable</tt>
* interface.</p>
*
* <p>The limitations are that you cannot put the same object into
* more than one list or more than once in the same list. You must
* also ensure that you only remove objects that are actually in the
* list. That is, if you have an object A and lists l1 and l2, you
* must ensure that you invoke List.remove(A) on the correct list. It
* is also forbidden to invoke List.remove() with an unaffiliated
* TLinkable (one that belongs to no list): this will destroy the list
* you invoke it on.</p>
*
* <p>
* Created: Sat Nov 10 15:25:10 2001
* </p>
*
* @author Eric D. Friedman
* @version $Id: TLinkedList.java,v 1.4 2002/04/08 02:02:28 ericdf Exp $
* @see gnu.trove.TLinkable
*/
public class TLinkedList
{
class TLinkable
{
protected TLinkable _next;
protected TLinkable _prev;
protected Object _value;
public TLinkable(Object value)
{
_value = value;
}
/**
* Returns the linked list node after this one.
*
* @return a <code>TLinkable</code> value
*/
public TLinkable getNext()
{
return _next;
}
/**
* Returns the linked list node before this one.
*
* @return a <code>TLinkable</code> value
*/
public TLinkable getPrevious()
{
return _prev;
}
/**
* Sets the linked list node after this one.
*
* @param linkable a <code>TLinkable</code> value
*/
public void setNext(TLinkable linkable)
{
_next = linkable;
}
/**
* Sets the linked list node before this one.
*
* @param linkable a <code>TLinkable</code> value
*/
public void setPrevious(TLinkable linkable)
{
_prev = linkable;
}
/**
* Sets the value of this list entry
*
* @param value a <code>Object</code> value
*/
public void setValue(Object value)
{
_value = value;
}
/**
* Gets the value of this list entry
*
* @param value a <code>Object</code> value
*/
public Object getValue()
{
return _value;
}
}// TLinkable
/** the head of the list */
protected TLinkable _head;
/** the tail of the list */
protected TLinkable _tail;
/** the number of elements in the list */
protected int _size = 0;
/**
* Creates a new <code>TLinkedList</code> instance.
*
*/
public TLinkedList()
{
}
public boolean isEmpty()
{
return (_size == 0);
}
public IteratorImpl first()
{
return listIterator();
}
public IteratorImpl listIterator()
{
return listIterator(0);
}
/**
* Returns an iterator positioned at <tt>index</tt>. Assuming
* that the list has a value at that index, calling next() will
* retrieve and advance the iterator. Assuming that there is a
* value before <tt>index</tt> in the list, calling previous()
* will retrieve it (the value at index - 1) and move the iterator
* to that position. So, iterating from front to back starts at
* 0; iterating from back to front starts at <tt>size()</tt>.
*
* @param index an <code>int</code> value
* @return a <code>ListIterator</code> value
*/
public IteratorImpl listIterator(int index)
{
return new IteratorImpl(index);
}
/**
* Returns the number of elements in the list.
*
* @return an <code>int</code> value
*/
public int size()
{
return _size;
}
/**
* Inserts <tt>linkable</tt> at index <tt>index</tt> in the list.
* All values > index are shifted over one position to accomodate
* the new addition.
*
* @param index an <code>int</code> value
* @param linkable an object of type TLinkable
*/
public void add(int index, Object linkable)
{
if (index < 0 || index > size())
{
throw new IndexOutOfBoundsException("index:" + index);
}
insert(index,linkable);
}
/**
* Appends <tt>linkable</tt> to the end of the list.
*
* @param linkable an object of type TLinkable
* @return always true
*/
public boolean add(Object linkable)
{
insert(_size, linkable);
return true;
}
/**
* Inserts <tt>linkable</tt> at the head of the list.
*
* @param linkable an object of type TLinkable
*/
public void addFirst(Object linkable)
{
insert(0, linkable);
}
/**
* Adds <tt>linkable</tt> to the end of the list.
*
* @param linkable an object of type TLinkable
*/
public void addLast(Object linkable)
{
insert(size(), linkable);
}
/**
* Empties the list.
*
*/
public void clear()
{
if (null != _head)
{
for (TLinkable link = _head.getNext();
link != null;
link = link.getNext())
{
TLinkable prev = link.getPrevious();
prev.setNext(null);
link.setPrevious(null);
}
_head = _tail = null;
}
_size = 0;
}
/**
* Copies the list's contents into a native array. This will be a
* shallow copy: the Tlinkable instances in the Object[] array
* have links to one another: changing those will put this list
* into an unpredictable state. Holding a reference to one
* element in the list will prevent the others from being garbage
* collected unless you clear the next/previous links. <b>Caveat
* programmer!</b>
*
* @return an <code>Object[]</code> value
*/
public Object[] toArray()
{
Object[] o = new Object[_size];
int i = 0;
for (TLinkable link = _head; link != null; link = link.getNext())
{
o[i++] = link.getValue();
}
return o;
}
/**
* Copies the list to a native array, destroying the next/previous
* links as the copy is made. This list will be emptied after the
* copy (as if clear() had been invoked). The Object[] array
* returned will contain TLinkables that do <b>not</b> hold
* references to one another and so are less likely to be the
* cause of memory leaks.
*
* @return an <code>Object[]</code> value
*/
public Object[] toUnlinkedArray()
{
Object[] o = new Object[_size];
int i = 0;
for (TLinkable link = _head, tmp = null; link != null; i++)
{
o[i] = link.getValue();
tmp = link;
link = link.getNext();
tmp.setNext(null); // clear the links
tmp.setPrevious(null);
}
_size = 0; // clear the list
_head = _tail = null;
return o;
}
/**
* A linear search for <tt>o</tt> in the list.
*
* @param o an <code>Object</code> value
* @return a <code>boolean</code> value
*/
public boolean contains(Object o)
{
for (TLinkable link = _head; link != null; link = link.getNext())
{
if (o.equals(link))
{
return true;
}
}
return false;
}
/**
* Returns the head of the list
*
* @return an <code>Object</code> value
*/
public Object getFirst()
{
return (_head != null) ? _head.getValue() : null;
}
/**
* Returns the tail of the list.
*
* @return an <code>Object</code> value
*/
public Object getLast()
{
return (_tail != null) ? _tail.getValue() : null;
}
/**
* Remove and return the first element in the list.
*
* @return an <code>Object</code> value
*/
public Object removeFirst()
{
TLinkable o = _head;
TLinkable n = o.getNext();
o.setNext(null);
if (null != n)
{
n.setPrevious(null);
}
_head = n;
if (--_size == 0)
{
_tail = null;
}
return (o != null) ? o.getValue() : null;
}
/**
* Remove and return the last element in the list.
*
* @return an <code>Object</code> value
*/
public Object removeLast()
{
TLinkable o = _tail;
TLinkable prev = o.getPrevious();
o.setPrevious(null);
if (null != prev)
{
prev.setNext(null);
}
_tail = prev;
if (--_size == 0)
{
_head = null;
}
return (o != null) ? o.getValue() : null;
}
/**
* Implementation of index-based list insertions.
*
* @param index an <code>int</code> value
* @param linkable an object of type TLinkable
*/
protected void insert(int index, Object linkable)
{
TLinkable newLink = new TLinkable(linkable);
if (_size == 0)
{
_head = _tail = newLink; // first insertion
}
else if (index == 0)
{
newLink.setNext(_head); // insert at front
_head.setPrevious(newLink);
_head = newLink;
}
else if (index == _size)
{ // insert at back
_tail.setNext(newLink);
newLink.setPrevious(_tail);
_tail = newLink;
}
else
{
TLinkable prior = null, post = null;
// looking at the size of the list, we decide whether
// it's faster to reach `index' by traversing the
// list from the front or the back.
if (index > (_size >> 1))
{ // insert in 2nd half
// work from the tail
int pos = _size -1;
for (prior = _tail; pos > index; pos--)
{
prior = prior.getPrevious();
}
}
else
{ // insert in 1st half
// work from the head
int pos = 0;
for (prior = _head; pos < index; pos++)
{
prior = prior.getNext();
}
}
post = prior.getNext();
// insert newlink
newLink.setNext(post);
newLink.setPrevious(prior);
// adjust adjacent pointers
post.setPrevious(newLink);
prior.setNext(newLink);
}
_size++;
}
/**
* Removes the specified element from the list. Note that
* it is the caller's responsibility to ensure that the
* element does, in fact, belong to this list and not another
* instance of TLinkedList.
*
* @param o a TLinkable element already inserted in this list.
* @return true if the element was a TLinkable and removed
*/
public boolean remove(Object o)
{
TLinkable link = null;
for (link = _head; link != null; link = link.getNext())
{
if (o.equals(link))
{
break;
}
}
if (link != null)
{
TLinkable p, n;
p = link.getPrevious();
n = link.getNext();
if (n == null && p == null)
{ // emptying the list
_head = _tail = null;
}
else if (n == null)
{ // this is the tail
// make previous the new tail
link.setPrevious(null);
p.setNext(null);
_tail = p;
}
else if (p == null)
{ // this is the head
// make next the new head
link.setNext(null);
n.setPrevious(null);
_head = n;
}
else
{ // somewhere in the middle
p.setNext(n);
n.setPrevious(p);
link.setNext(null);
link.setPrevious(null);
}
_size--; // reduce size of list
return true;
}
else
{
return false;
}
}
/**
* Inserts newElement into the list immediately before current.
* All elements to the right of and including current are shifted
* over.
*
* @param current a <code>TLinkable</code> value currently in the list.
* @param newElement a <code>TLinkable</code> value to be added to
* the list.
*/
public void addBefore(TLinkable current, TLinkable newElement)
{
if (current == _head)
{
addFirst(newElement);
}
else if (current == null)
{
addLast(newElement);
}
else
{
TLinkable p = current.getPrevious();
newElement.setNext(current);
p.setNext(newElement);
newElement.setPrevious(p);
current.setPrevious(newElement);
_size++;
}
}
/**
* A ListIterator that supports additions and deletions.
*
*/
public class IteratorImpl
{
private int _nextIndex = 0;
private TLinkable _next;
private TLinkable _lastReturned;
/**
* Creates a new <code>Iterator</code> instance positioned at
* <tt>index</tt>.
*
* @param position an <code>int</code> value
*/
IteratorImpl(int position)
{
if (position < 0 || position > _size)
{
throw new IndexOutOfBoundsException();
}
_nextIndex = position;
if (position == 0)
{
_next = _head;
}
else if (position == _size)
{
_next = null;
}
else if (position < (_size >> 1))
{
int pos = 0;
for (_next = _head; pos < position; pos++)
{
_next = _next.getNext();
}
}
else
{
int pos = _size - 1;
for (_next = _tail; pos > position; pos--)
{
_next = _next.getPrevious();
}
}
}
/**
* Insert <tt>linkable</tt> at the current position of the iterator.
* Calling next() after add() will return the added object.
*
* @param linkable an object of type TLinkable
*/
public final void add(Object linkable)
{
_lastReturned = null;
_nextIndex++;
if (_size == 0)
{
TLinkedList.this.add(linkable);
}
else
{
TLinkedList.this.addBefore(_next, new TLinkable(linkable));
}
}
/**
* True if a call to next() will return an object.
*
* @return a <code>boolean</code> value
*/
public final boolean hasNext()
{
return _nextIndex != _size;
}
/**
* True if a call to previous() will return a value.
*
* @return a <code>boolean</code> value
*/
public final boolean hasPrevious()
{
return _nextIndex != 0;
}
/**
* Returns the value at the Iterator's index and advances the
* iterator.
*
* @return an <code>Object</code> value
* @exception NoSuchElementException if there is no next element
*/
public final Object next()
{
if (_nextIndex == _size)
{
throw new IndexOutOfBoundsException("NoSuchElementException");
}
_lastReturned = _next;
_next = _next.getNext();
_nextIndex++;
return (_lastReturned != null) ? _lastReturned.getValue() : null;
}
/**
* returns the index of the next node in the list (the
* one that would be returned by a call to next()).
*
* @return an <code>int</code> value
*/
public final int nextIndex()
{
return _nextIndex;
}
/**
* Returns the value before the Iterator's index and moves the
* iterator back one index.
*
* @return an <code>Object</code> value
* @exception NoSuchElementException if there is no previous element.
*/
public final Object previous()
{
if (_nextIndex == 0)
{
throw new IndexOutOfBoundsException("NoSuchElementException");
}
if (_nextIndex == _size)
{
_lastReturned = _next = _tail;
}
else
{
_lastReturned = _next = _next.getPrevious();
}
_nextIndex--;
return (_lastReturned != null) ? _lastReturned.getValue() : null;
}
/**
* Returns the previous element's index.
*
* @return an <code>int</code> value
*/
public final int previousIndex()
{
return _nextIndex - 1;
}
/**
* Removes the current element in the list and shrinks its
* size accordingly.
*
* @exception IllegalStateException neither next nor previous
* have been invoked, or remove or add have been invoked after
* the last invocation of next or previous.
*/
public final void remove()
{
if (_lastReturned == null)
{
throw new IllegalStateException("must invoke next or previous before invoking remove");
}
if (_lastReturned != _next)
{
_nextIndex--;
}
_next = _lastReturned.getNext();
TLinkedList.this.remove(_lastReturned);
_lastReturned = null;
}
/**
* Replaces the current element in the list with
* <tt>linkable</tt>
*
* @param linkable an object of type TLinkable
*/
public final void set(Object linkable)
{
if (_lastReturned == null)
{
throw new IllegalStateException();
}
TLinkable l = new TLinkable(linkable);
// need to check both, since this could be the only
// element in the list.
if (_lastReturned == _head)
{
_head = l;
}
if (_lastReturned == _tail)
{
_tail = l;
}
swap(_lastReturned, l);
_lastReturned = l;
}
/**
* Replace from with to in the list.
*
* @param from a <code>TLinkable</code> value
* @param to a <code>TLinkable</code> value
*/
private void swap(TLinkable from, TLinkable to)
{
TLinkable p = from.getPrevious();
TLinkable n = from.getNext();
if (null != p)
{
to.setPrevious(p);
p.setNext(to);
}
if (null != n)
{
to.setNext(n);
n.setPrevious(to);
}
from.setNext(null);
from.setPrevious(null);
}
}
} // TLinkedList
\ No newline at end of file
/*
var vid = new Whammy.Video();
vid.add(canvas or data url)
vid.compile()
*/
var Whammy = (function(){
// in this case, frames has a very specific meaning, which will be
// detailed once i finish writing the code
function toWebM(frames){
var info = checkFrames(frames);
var counter = 0;
var EBML = [
{
"id": 0x1a45dfa3, // EBML
"data": [
{
"data": 1,
"id": 0x4286 // EBMLVersion
},
{
"data": 1,
"id": 0x42f7 // EBMLReadVersion
},
{
"data": 4,
"id": 0x42f2 // EBMLMaxIDLength
},
{
"data": 8,
"id": 0x42f3 // EBMLMaxSizeLength
},
{
"data": "webm",
"id": 0x4282 // DocType
},
{
"data": 2,
"id": 0x4287 // DocTypeVersion
},
{
"data": 2,
"id": 0x4285 // DocTypeReadVersion
}
]
},
{
"id": 0x18538067, // Segment
"data": [
{
"id": 0x1549a966, // Info
"data": [
{
"data": 1e6, //do things in millisecs (num of nanosecs for duration scale)
"id": 0x2ad7b1 // TimecodeScale
},
{
"data": "whammy",
"id": 0x4d80 // MuxingApp
},
{
"data": "whammy",
"id": 0x5741 // WritingApp
},
{
"data": doubleToString(info.duration),
"id": 0x4489 // Duration
}
]
},
{
"id": 0x1654ae6b, // Tracks
"data": [
{
"id": 0xae, // TrackEntry
"data": [
{
"data": 1,
"id": 0xd7 // TrackNumber
},
{
"data": 1,
"id": 0x63c5 // TrackUID
},
{
"data": 0,
"id": 0x9c // FlagLacing
},
{
"data": "und",
"id": 0x22b59c // Language
},
{
"data": "V_VP8",
"id": 0x86 // CodecID
},
{
"data": "VP8",
"id": 0x258688 // CodecName
},
{
"data": 1,
"id": 0x83 // TrackType
},
{
"id": 0xe0, // Video
"data": [
{
"data": info.width,
"id": 0xb0 // PixelWidth
},
{
"data": info.height,
"id": 0xba // PixelHeight
}
]
}
]
}
]
},
{
"id": 0x1f43b675, // Cluster
"data": [
{
"data": 0,
"id": 0xe7 // Timecode
}
].concat(frames.map(function(webp){
var block = makeSimpleBlock({
discardable: 0,
frame: webp.data.slice(4),
invisible: 0,
keyframe: 1,
lacing: 0,
trackNum: 1,
timecode: Math.round(counter)
});
counter += webp.duration;
return {
data: block,
id: 0xa3
};
}))
}
]
}
];
return generateEBML(EBML)
}
// sums the lengths of all the frames and gets the duration, woo
function checkFrames(frames){
var width = frames[0].width,
height = frames[0].height,
duration = frames[0].duration;
for(var i = 1; i < frames.length; i++){
if(frames[i].width != width) throw "Frame " + (i + 1) + " has a different width";
if(frames[i].height != height) throw "Frame " + (i + 1) + " has a different height";
if(frames[i].duration < 0) throw "Frame " + (i + 1) + " has a weird duration";
duration += frames[i].duration;
}
return {
duration: duration,
width: width,
height: height
};
}
function numToBuffer(num){
var parts = [];
while(num > 0){
parts.push(num & 0xff)
num = num >> 8
}
return new Uint8Array(parts.reverse());
}
function strToBuffer(str){
// return new Blob([str]);
var arr = new Uint8Array(str.length);
for(var i = 0; i < str.length; i++){
arr[i] = str.charCodeAt(i)
}
return arr;
// this is slower
// return new Uint8Array(str.split('').map(function(e){
// return e.charCodeAt(0)
// }))
}
//sorry this is ugly, and sort of hard to understand exactly why this was done
// at all really, but the reason is that there's some code below that i dont really
// feel like understanding, and this is easier than using my brain.
function bitsToBuffer(bits){
var data = [];
var pad = (bits.length % 8) ? (new Array(1 + 8 - (bits.length % 8))).join('0') : '';
bits = pad + bits;
for(var i = 0; i < bits.length; i+= 8){
data.push(parseInt(bits.substr(i,8),2))
}
return new Uint8Array(data);
}
function generateEBML(json){
var ebml = [];
for(var i = 0; i < json.length; i++){
var data = json[i].data;
// console.log(data);
if(typeof data == 'object') data = generateEBML(data);
if(typeof data == 'number') data = bitsToBuffer(data.toString(2));
if(typeof data == 'string') data = strToBuffer(data);
// console.log(data)
var len = data.size || data.byteLength;
var zeroes = Math.ceil(Math.ceil(Math.log(len)/Math.log(2))/8);
var size_str = len.toString(2);
var padded = (new Array((zeroes * 7 + 7 + 1) - size_str.length)).join('0') + size_str;
var size = (new Array(zeroes)).join('0') + '1' + padded;
//i actually dont quite understand what went on up there, so I'm not really
//going to fix this, i'm probably just going to write some hacky thing which
//converts that string into a buffer-esque thing
ebml.push(numToBuffer(json[i].id));
ebml.push(bitsToBuffer(size));
ebml.push(data)
}
return new Blob(ebml, {
type: "video/webm"
});
}
//OKAY, so the following two functions are the string-based old stuff, the reason they're
//still sort of in here, is that they're actually faster than the new blob stuff because
//getAsFile isn't widely implemented, or at least, it doesn't work in chrome, which is the
// only browser which supports get as webp
//Converting between a string of 0010101001's and binary back and forth is probably inefficient
//TODO: get rid of this function
function toBinStr_old(bits){
var data = '';
var pad = (bits.length % 8) ? (new Array(1 + 8 - (bits.length % 8))).join('0') : '';
bits = pad + bits;
for(var i = 0; i < bits.length; i+= 8){
data += String.fromCharCode(parseInt(bits.substr(i,8),2))
}
return data;
}
function generateEBML_old(json){
var ebml = '';
for(var i = 0; i < json.length; i++){
var data = json[i].data;
if(typeof data == 'object') data = generateEBML_old(data);
if(typeof data == 'number') data = toBinStr_old(data.toString(2));
var len = data.length;
var zeroes = Math.ceil(Math.ceil(Math.log(len)/Math.log(2))/8);
var size_str = len.toString(2);
var padded = (new Array((zeroes * 7 + 7 + 1) - size_str.length)).join('0') + size_str;
var size = (new Array(zeroes)).join('0') + '1' + padded;
ebml += toBinStr_old(json[i].id.toString(2)) + toBinStr_old(size) + data;
}
return ebml;
}
//woot, a function that's actually written for this project!
//this parses some json markup and makes it into that binary magic
//which can then get shoved into the matroska comtainer (peaceably)
function makeSimpleBlock(data){
var flags = 0;
if (data.keyframe) flags |= 128;
if (data.invisible) flags |= 8;
if (data.lacing) flags |= (data.lacing << 1);
if (data.discardable) flags |= 1;
if (data.trackNum > 127) {
throw "TrackNumber > 127 not supported";
}
var out = [data.trackNum | 0x80, data.timecode >> 8, data.timecode & 0xff, flags].map(function(e){
return String.fromCharCode(e)
}).join('') + data.frame;
return out;
}
// here's something else taken verbatim from weppy, awesome rite?
function parseWebP(riff){
var VP8 = riff.RIFF[0].WEBP[0];
var frame_start = VP8.indexOf('\x9d\x01\x2a'); //A VP8 keyframe starts with the 0x9d012a header
for(var i = 0, c = []; i < 4; i++) c[i] = VP8.charCodeAt(frame_start + 3 + i);
var width, horizontal_scale, height, vertical_scale, tmp;
//the code below is literally copied verbatim from the bitstream spec
tmp = (c[1] << 8) | c[0];
width = tmp & 0x3FFF;
horizontal_scale = tmp >> 14;
tmp = (c[3] << 8) | c[2];
height = tmp & 0x3FFF;
vertical_scale = tmp >> 14;
return {
width: width,
height: height,
data: VP8,
riff: riff
}
}
// i think i'm going off on a riff by pretending this is some known
// idiom which i'm making a casual and brilliant pun about, but since
// i can't find anything on google which conforms to this idiomatic
// usage, I'm assuming this is just a consequence of some psychotic
// break which makes me make up puns. well, enough riff-raff (aha a
// rescue of sorts), this function was ripped wholesale from weppy
function parseRIFF(string){
var offset = 0;
var chunks = {};
while (offset < string.length) {
var id = string.substr(offset, 4);
var len = parseInt(string.substr(offset + 4, 4).split('').map(function(i){
var unpadded = i.charCodeAt(0).toString(2);
return (new Array(8 - unpadded.length + 1)).join('0') + unpadded
}).join(''),2);
var data = string.substr(offset + 4 + 4, len);
offset += 4 + 4 + len;
chunks[id] = chunks[id] || [];
if (id == 'RIFF' || id == 'LIST') {
chunks[id].push(parseRIFF(data));
} else {
chunks[id].push(data);
}
}
return chunks;
}
// here's a little utility function that acts as a utility for other functions
// basically, the only purpose is for encoding "Duration", which is encoded as
// a double (considerably more difficult to encode than an integer)
function doubleToString(num){
return [].slice.call(
new Uint8Array(
(
new Float64Array([num]) //create a float64 array
).buffer) //extract the array buffer
, 0) // convert the Uint8Array into a regular array
.map(function(e){ //since it's a regular array, we can now use map
return String.fromCharCode(e) // encode all the bytes individually
})
.reverse() //correct the byte endianness (assume it's little endian for now)
.join('') // join the bytes in holy matrimony as a string
}
function WhammyVideo(speed, quality){ // a more abstract-ish API
this.frames = [];
this.duration = 1000 / speed;
this.quality = quality || 0.8;
}
WhammyVideo.prototype.add = function(frame, duration){
if(typeof duration != 'undefined' && this.duration) throw "you can't pass a duration if the fps is set";
if('canvas' in frame){ //CanvasRenderingContext2D
frame = frame.canvas;
}
if('toDataURL' in frame){
frame = frame.toDataURL('image/webp', this.quality)
}else if(typeof frame != "string"){
throw "frame must be a a HTMLCanvasElement, a CanvasRenderingContext2D or a DataURI formatted string"
}
if (!(/^data:image\/webp;base64,/ig).test(frame)) {
throw "Input must be formatted properly as a base64 encoded DataURI of type image/webp";
}
this.frames.push({
image: frame,
duration: duration || this.duration
})
}
WhammyVideo.prototype.compile = function(){
return new toWebM(this.frames.map(function(frame){
var webp = parseWebP(parseRIFF(atob(frame.image.slice(23))));
webp.duration = frame.duration;
return webp;
}))
}
return {
Video: WhammyVideo,
fromImageArray: function(images, fps){
return toWebM(images.map(function(image){
var webp = parseWebP(parseRIFF(atob(image.slice(23))))
webp.duration = 1000 / fps;
return webp;
}))
},
toWebM: toWebM
// expose methods of madness
}
})()
\ No newline at end of file
......@@ -23,7 +23,7 @@ public class BitAssistant
{
byte bytes[] = new byte[arrayList.size()];
for(int i = 0; i < arrayList.size(); i++)
bytes[i] = ((Byte)arrayList.get(i)).byteValue();
if (arrayList.get(i) != null) bytes[i] = ((Byte)arrayList.get(i)).byteValue();
return bytes;
}
......@@ -40,8 +40,9 @@ public class BitAssistant
public static byte[] bytesFromArray(Byte array[])
{
byte bytes[] = new byte[array.length];
for(int i = 0; i < array.length; i++)
bytes[i] = array[i].byteValue();
if (array[i] != null) bytes[i] = array[i].byteValue();
return bytes;
}
......
package org.ifsoft;
import java.util.ArrayList;
import java.util.Iterator;
public class ByteCollection
{
private ArrayList _list;
public void add(byte b)
{
_list.add(Byte.valueOf(b));
}
public void add(int b)
{
_list.add(Byte.valueOf((byte)b));
}
public void addRange(byte buffer[])
{
byte arr$[] = buffer;
int len$ = arr$.length;
for(int i$ = 0; i$ < len$; i$++)
{
byte b = arr$[i$];
add(b);
}
}
public void addRange(ByteCollection collection)
{
byte b;
for(Iterator i$ = collection.getList().iterator(); i$.hasNext(); add(b))
b = ((Byte)i$.next()).byteValue();
}
public ByteCollection()
{
_list = new ArrayList();
}
public ByteCollection(byte buffer[])
{
_list = new ArrayList();
addRange(buffer);
}
public byte get(Integer index)
{
return ((Byte)_list.get(index.intValue())).byteValue();
}
public Integer getCount()
{
return Integer.valueOf(_list.size());
}
ArrayList getList()
{
return _list;
}
public byte[] getRange(Integer index, Integer count)
{
byte range[] = new byte[count.intValue()];
for(int i = 0; i < count.intValue(); i++)
range[i] = ((Byte)_list.get(index.intValue() + i)).byteValue();
return range;
}
public void insertRange(Integer index, byte buffer[])
{
for(int i = 0; i < buffer.length; i++)
_list.add(index.intValue() + i, Byte.valueOf(buffer[i]));
}
public void insertRange(Integer index, ByteCollection collection)
{
for(int i = 0; i < collection.getCount().intValue(); i++)
_list.add(index.intValue() + i, Byte.valueOf(collection.get(Integer.valueOf(i))));
}
public void removeRange(Integer index, Integer count)
{
ArrayListExtensions.removeRange(_list, index.intValue(), count.intValue());
}
public byte[] toArray()
{
byte array[] = new byte[_list.size()];
for(int i = 0; i < array.length; i++)
array[i] = ((Byte)_list.get(i)).byteValue();
return array;
}
}
package org.ifsoft;
public class Math
{
public Math()
{
}
public static Integer max(Integer val1, Integer val2)
{
return Integer.valueOf(Math.max(val1.intValue(), val2.intValue()));
}
public static Long max(Long val1, Long val2)
{
return Long.valueOf(Math.max(val1.longValue(), val2.longValue()));
}
public static Integer min(Integer val1, Integer val2)
{
return Integer.valueOf(Math.min(val1.intValue(), val2.intValue()));
}
public static Long min(Long val1, Long val2)
{
return Long.valueOf(Math.min(val1.longValue(), val2.longValue()));
}
public static Double pow(Double val, Double exp)
{
return Double.valueOf(Math.pow(val.doubleValue(), exp.doubleValue()));
}
public static Double ceiling(Double val)
{
return Double.valueOf(java.lang.Math.ceil(val.doubleValue()));
}
}
package org.ifsoft.rtp;
import org.ifsoft.*;
import java.util.ArrayList;
public class Vp8Accumulator
{
public void add(Vp8Packet packet)
{
//if(packet.getStartOfPartition().booleanValue() || ArrayListExtensions.getCount(packets).intValue() > 0)
packets.add(packet);
}
public Vp8Accumulator()
{
packets = new ArrayList();
}
public Vp8Packet[] getPackets()
{
return (Vp8Packet[])packets.toArray(new Vp8Packet[0]);
}
public void reset()
{
packets = new ArrayList();
}
private ArrayList packets;
}
package org.ifsoft.rtp;
import org.ifsoft.*;
import java.util.ArrayList;
import org.slf4j.*;
import org.slf4j.Logger;
public class Vp8Packet
{
private static final Logger Log = LoggerFactory.getLogger(Vp8Packet.class);
private Boolean _extendedControlBitsPresent;
private Byte _keyIndex;
private Boolean _keyIndexPresent;
private Boolean _layerSync;
public static Integer _maxPacketSize = Integer.valueOf(0);
private Boolean _nonReferenceFrame;
private Byte _partitionId;
private Byte _payload[];
private Short _pictureID;
private Boolean _pictureIDPresent;
private Boolean _startOfPartition;
private Byte _temporalLayerIndex;
private Boolean _temporalLayerIndexPresent;
private Byte _temporalLevelZeroIndex;
private Boolean _temporalLevelZeroIndexPresent;
static
{
_maxPacketSize = Integer.valueOf(1050);
}
public Vp8Packet()
{
_extendedControlBitsPresent = Boolean.valueOf(false);
_keyIndex = Byte.valueOf((byte)0);
_keyIndexPresent = Boolean.valueOf(false);
_layerSync = Boolean.valueOf(false);
_nonReferenceFrame = Boolean.valueOf(false);
_partitionId = Byte.valueOf((byte)0);
_pictureID = Short.valueOf((short)0);
_pictureIDPresent = Boolean.valueOf(false);
_startOfPartition = Boolean.valueOf(false);
_temporalLayerIndex = Byte.valueOf((byte)0);
_temporalLayerIndexPresent = Boolean.valueOf(false);
_temporalLevelZeroIndex = Byte.valueOf((byte)0);
_temporalLevelZeroIndexPresent = Boolean.valueOf(false);
}
public static Byte[] depacketize(Vp8Packet packets[])
{
Integer num = Integer.valueOf(0);
Vp8Packet arr[] = packets;
int len = arr.length;
for(int i = 0; i < len; i++)
{
Vp8Packet packet = arr[i];
num = Integer.valueOf(num.intValue() + ArrayExtensions.getLength(packet.getPayload()).intValue());
}
Integer destinationIndex = Integer.valueOf(0);
Byte destinationArray[] = new Byte[num.intValue()];
arr = packets;
len = arr.length;
for(int i = 0; i < len; i++)
{
Vp8Packet packet = arr[i];
ArrayExtensions.copy(packet.getPayload(), 0, destinationArray, destinationIndex.intValue(), ArrayExtensions.getLength(packet.getPayload()).intValue());
destinationIndex = Integer.valueOf(destinationIndex.intValue() + ArrayExtensions.getLength(packet.getPayload()).intValue());
}
return destinationArray;
}
public Byte[] getBytes()
{
ArrayList list = new ArrayList();
list.add(new Byte((new Byte((new Integer((getExtendedControlBitsPresent().booleanValue() ? 0x80 : 0) | (getNonReferenceFrame().booleanValue() ? 0x20 : 0) | (getStartOfPartition().booleanValue() ? 0x10 : 0) | getPartitionId().byteValue() & 0xf)).byteValue())).byteValue()));
if(getExtendedControlBitsPresent().booleanValue())
{
list.add(new Byte((new Byte((new Integer((getPictureIDPresent().booleanValue() ? 0x80 : 0) | (getTemporalLevelZeroIndexPresent().booleanValue() ? 0x40 : 0) | (getTemporalLayerIndexPresent().booleanValue() ? 0x20 : 0) | (getKeyIndexPresent().booleanValue() ? 0x10 : 0))).byteValue())).byteValue()));
if(getPictureIDPresent().booleanValue())
{
Byte shortBytesNetwork[] = BitAssistant.getShortBytesNetwork(getPictureID());
list.add(new Byte((new Byte((new Integer(0x80 | shortBytesNetwork[0].byteValue() & 0x7f)).byteValue())).byteValue()));
list.add(new Byte(shortBytesNetwork[1].byteValue()));
}
if(getTemporalLevelZeroIndexPresent().booleanValue())
list.add(new Byte(getTemporalLevelZeroIndex().byteValue()));
if(getTemporalLayerIndexPresent().booleanValue() || getKeyIndexPresent().booleanValue())
list.add(new Byte((new Byte((byte)(getTemporalLayerIndex().byteValue() << 6 & 0xc0 | (getLayerSync().booleanValue() ? 0x20 : 0) | getKeyIndex().byteValue() & 0x1f))).byteValue()));
}
ArrayListExtensions.addRange(list, getPayload());
return (Byte[])list.toArray(new Byte[0]);
}
public Boolean getExtendedControlBitsPresent()
{
return _extendedControlBitsPresent;
}
public Byte getKeyIndex()
{
return _keyIndex;
}
public Boolean getKeyIndexPresent()
{
return _keyIndexPresent;
}
public Boolean getLayerSync()
{
return _layerSync;
}
public Boolean getNonReferenceFrame()
{
return _nonReferenceFrame;
}
public Byte getPartitionId()
{
return _partitionId;
}
public Byte[] getPayload()
{
return _payload;
}
public Short getPictureID()
{
return _pictureID;
}
public Boolean getPictureIDPresent()
{
return _pictureIDPresent;
}
public Boolean getStartOfPartition()
{
return _startOfPartition;
}
public Byte getTemporalLayerIndex()
{
return _temporalLayerIndex;
}
public Boolean getTemporalLayerIndexPresent()
{
return _temporalLayerIndexPresent;
}
public Byte getTemporalLevelZeroIndex()
{
return _temporalLevelZeroIndex;
}
public Boolean getTemporalLevelZeroIndexPresent()
{
return _temporalLevelZeroIndexPresent;
}
public static Vp8Packet[] packetize(Byte encodedData[])
{
Integer offset = Integer.valueOf(0);
ArrayList list = new ArrayList();
Integer num2 = Integer.valueOf(_maxPacketSize.intValue() - 1);
Integer num3 = new Integer((new Double(org.ifsoft.Math.ceiling(new Double((new Double((new Integer(ArrayExtensions.getLength(encodedData).intValue())).doubleValue())).doubleValue() / (new Double((new Integer(num2.intValue())).doubleValue())).doubleValue())).doubleValue())).intValue());
Integer num4 = Integer.valueOf(ArrayExtensions.getLength(encodedData).intValue() / num3.intValue());
Integer num5 = Integer.valueOf(ArrayExtensions.getLength(encodedData).intValue() - num3.intValue() * num4.intValue());
for(Integer i = Integer.valueOf(0); i.intValue() < num3.intValue();)
{
Integer count = num4;
if(i.intValue() < num5.intValue())
{
Integer integer = count;
Integer integer1 = count = Integer.valueOf(count.intValue() + 1);
Integer _tmp = integer;
}
Vp8Packet item = new Vp8Packet();
item.setStartOfPartition(Boolean.valueOf(i.intValue() == 0));
item.setPayload(BitAssistant.subArray(encodedData, offset, count));
list.add(item);
offset = Integer.valueOf(offset.intValue() + count.intValue());
count = i;
i = Integer.valueOf(i.intValue() + 1);
Integer _tmp1 = count;
}
return (Vp8Packet[])list.toArray(new Vp8Packet[0]);
}
public static Vp8Packet parse(Byte packetBytes[])
{
Integer index = Integer.valueOf(0);
Vp8Packet packet = new Vp8Packet();
Byte num2 = packetBytes[index.intValue()];
packet.setExtendedControlBitsPresent(Boolean.valueOf((num2.byteValue() & 0x80) == 128));
packet.setNonReferenceFrame(Boolean.valueOf((num2.byteValue() & 0x20) == 32));
packet.setStartOfPartition(Boolean.valueOf((num2.byteValue() & 0x10) == 16));
packet.setPartitionId(new Byte((byte)(num2.byteValue() & 0xf)));
Integer integer = index;
Integer integer1 = index = Integer.valueOf(index.intValue() + 1);
Integer _tmp = integer;
if(packet.getExtendedControlBitsPresent().booleanValue())
{
Byte num3 = packetBytes[index.intValue()];
packet.setPictureIDPresent(Boolean.valueOf((num3.byteValue() & 0x80) == 128));
packet.setTemporalLevelZeroIndexPresent(Boolean.valueOf((num3.byteValue() & 0x40) == 64));
packet.setTemporalLayerIndexPresent(Boolean.valueOf((num3.byteValue() & 0x20) == 32));
packet.setKeyIndexPresent(Boolean.valueOf((num3.byteValue() & 0x10) == 16));
Integer integer2 = index;
Integer integer5 = index = Integer.valueOf(index.intValue() + 1);
Integer _tmp1 = integer2;
if(packet.getPictureIDPresent().booleanValue())
if((packetBytes[index.intValue()].byteValue() & 0x80) == 128)
{
Byte buffer[] = BitAssistant.subArray(packetBytes, index, Integer.valueOf(2));
buffer[0] = new Byte((byte)(buffer[0].byteValue() & 0x7f));
packet.setPictureID(BitAssistant.toShortNetwork(buffer, Integer.valueOf(0)));
index = Integer.valueOf(index.intValue() + 2);
} else
{
Byte buffer2[] = new Byte[2];
buffer2[1] = packetBytes[index.intValue()];
packet.setPictureID(BitAssistant.toShortNetwork(buffer2, Integer.valueOf(0)));
Integer integer6 = index;
Integer integer9 = index = Integer.valueOf(index.intValue() + 1);
Integer _tmp2 = integer6;
}
if(packet.getTemporalLevelZeroIndexPresent().booleanValue())
{
packet.setTemporalLevelZeroIndex(packetBytes[index.intValue()]);
Integer integer3 = index;
Integer integer7 = index = Integer.valueOf(index.intValue() + 1);
Integer _tmp3 = integer3;
}
if(packet.getTemporalLayerIndexPresent().booleanValue() || packet.getKeyIndexPresent().booleanValue())
{
packet.setTemporalLayerIndex(new Byte((byte)(packetBytes[index.intValue()].byteValue() >> 6 & 3)));
packet.setLayerSync(Boolean.valueOf((packetBytes[index.intValue()].byteValue() & 0x20) == 32));
packet.setKeyIndex(new Byte((byte)(packetBytes[index.intValue()].byteValue() & 0x1f)));
Integer integer4 = index;
Integer integer8 = index = Integer.valueOf(index.intValue() + 1);
Integer _tmp4 = integer4;
}
}
packet.setPayload(BitAssistant.subArray(packetBytes, index));
return packet;
}
private void setExtendedControlBitsPresent(Boolean value)
{
_extendedControlBitsPresent = value;
}
private void setKeyIndex(Byte value)
{
_keyIndex = value;
}
private void setKeyIndexPresent(Boolean value)
{
_keyIndexPresent = value;
}
private void setLayerSync(Boolean value)
{
_layerSync = value;
}
private void setNonReferenceFrame(Boolean value)
{
_nonReferenceFrame = value;
}
private void setPartitionId(Byte value)
{
_partitionId = value;
}
private void setPayload(Byte value[])
{
_payload = value;
}
private void setPictureID(Short value)
{
_pictureID = value;
}
private void setPictureIDPresent(Boolean value)
{
_pictureIDPresent = value;
}
private void setStartOfPartition(Boolean value)
{
_startOfPartition = value;
}
private void setTemporalLayerIndex(Byte value)
{
_temporalLayerIndex = value;
}
private void setTemporalLayerIndexPresent(Boolean value)
{
_temporalLayerIndexPresent = value;
}
private void setTemporalLevelZeroIndex(Byte value)
{
_temporalLevelZeroIndex = value;
}
private void setTemporalLevelZeroIndexPresent(Boolean value)
{
_temporalLevelZeroIndexPresent = value;
}
}
......@@ -968,6 +968,21 @@ public class PluginImpl implements Plugin, PropertyEventListener
*/
public class Participant
{
/**
*
*
*/
private Vp8Accumulator vp8Accumulator;
/**
*
*
*/
private Integer lastSequenceNumber;
/**
*
*
*/
private Boolean sequenceNumberingViolated;
/**
*
*
......@@ -1030,17 +1045,7 @@ public class PluginImpl implements Plugin, PropertyEventListener
public boolean accept(DatagramPacket p)
{
byte[] data = p.getData();
try{
RTPPacket packet = RTPPacket.parseBytes(BitAssistant.bytesToArray(data));
byte[] videoFrame = BitAssistant.bytesFromArray(packet.getPayload());
//Log.info("Video media " + " " + packet.getPayloadType() + " " + packet.getSequenceNumber() + " " + packet.getTimestamp() + " " + packet.getPayload());
if (recorder != null) recorder.write(videoFrame, 0, videoFrame.length);
} catch (Exception e) {
}
recordVideo(data);
return true;
}
};
......@@ -1054,6 +1059,47 @@ public class PluginImpl implements Plugin, PropertyEventListener
}
}
};
/**
*
*
*/
private void recordVideo(byte[] data)
{
Byte[] encodedFrame = null;
boolean isKeyframe = false;
try {
RTPPacket packet = RTPPacket.parseBytes(BitAssistant.bytesToArray(data));
if (packet != null)
{
Vp8Packet packet2 = Vp8Packet.parse(packet.getPayload());
if(packet2 == null) return;
vp8Accumulator.add(packet2);
if (packet.getMarker().booleanValue())
{
encodedFrame = Vp8Packet.depacketize(vp8Accumulator.getPackets());
isKeyframe = isKeyFrame(BitAssistant.bytesFromArray(encodedFrame)).booleanValue();
vp8Accumulator.reset();
}
if (recorder != null && encodedFrame != null)
{
Log.info("Video media " + " " + packet.getPayloadType() + " " + encodedFrame + " " + packet.getTimestamp() + " " + isKeyframe);
recorder.write(BitAssistant.bytesFromArray(encodedFrame), 0, encodedFrame.length, isKeyframe, packet.getTimestamp());
}
} else {
Log.error("recordVideo cannot parse packet data " + data);
}
} catch (Exception e) {
Log.error("Error writing video recording" , e);
}
}
/**
*
*
......@@ -1062,6 +1108,9 @@ public class PluginImpl implements Plugin, PropertyEventListener
this.nickname = nickname;
this.user = user;
this.focusName = focusName;
this.sequenceNumberingViolated = Boolean.valueOf(false);
this.vp8Accumulator = new Vp8Accumulator();
this.lastSequenceNumber = Integer.valueOf(-1);
}
/**
*
......@@ -1105,10 +1154,10 @@ public class PluginImpl implements Plugin, PropertyEventListener
mediaStream.addPropertyChangeListener(streamPropertyChangeListener);
String recordingPath = JiveGlobals.getHomeDirectory() + File.separator + "resources" + File.separator + "spank" + File.separator + "rayo" + File.separator + "video_recordings";
String fileName = "video-" + focusName + "-" + nickname + "-" + System.currentTimeMillis() + ".rtp";
String fileName = "video-" + focusName + "-" + nickname + "-" + System.currentTimeMillis() + ".webm";
try {
recorder = new Recorder(recordingPath, fileName, "rtp", false, 0, 0);
recorder = new Recorder(recordingPath, fileName, "webm", false, 0, 0);
} catch (Exception e) {
Log.error("Error creating video recording " + fileName + " " + recordingPath, e);
......@@ -1133,6 +1182,15 @@ public class PluginImpl implements Plugin, PropertyEventListener
}
}
}
/**
*
*
*/
private Boolean isKeyFrame(byte encodedFrame[])
{
return Boolean.valueOf(encodedFrame != null && encodedFrame.length > 0 && (encodedFrame[0] & 1) == 0);
}
}
public class FocusAgent extends VirtualConnection
......@@ -1759,7 +1817,7 @@ public class PluginImpl implements Plugin, PropertyEventListener
byte[] ulawData = new byte[data.length /2];
AudioConversion.linearToUlaw(data, 0, ulawData, 0);
if (recorder != null) recorder.write(ulawData, 0, ulawData.length);
if (recorder != null) recorder.write(ulawData, 0, ulawData.length, false, 0);
} catch (Exception e) {
......
......@@ -15,6 +15,11 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import org.ebml.*;
import org.ebml.io.*;
import org.ebml.matroska.*;
import org.ebml.util.*;
import org.slf4j.*;
import org.slf4j.Logger;
......@@ -34,11 +39,16 @@ public class Recorder extends Thread
private static String defaultRecordDirectory = ".";
private String recordPath;
private boolean recordRtp;
private boolean recordWebm;
private boolean recordAu;
private static String fileSeparator = System.getProperty("file.separator");
private boolean done;
private boolean pcmu;
private int sampleRate;
private int channels;
private long lastTimecode = 0;
private MatroskaFileWriter mFW;
private FileDataWriter iFW;
public Recorder(String recordDirectory, String recordPath, String recordingType, boolean pcmu, int sampleRate, int channels) throws IOException
......@@ -50,11 +60,20 @@ public class Recorder extends Thread
if (recordingType.equalsIgnoreCase("Rtp")) {
recordRtp = true;
} else if (recordingType.equalsIgnoreCase("Au") == false) {
openFile();
} else if (recordingType.equalsIgnoreCase("webm")) {
recordWebm = true;
openWebmFile();
} else if (recordingType.equalsIgnoreCase("Au")) {
recordAu = true;
openFile();
} else {
throw new IOException("Invalid recording type " + recordingType);
}
openFile();
start();
}
......@@ -90,11 +109,37 @@ public class Recorder extends Thread
}
private void openWebmFile() throws IOException
{
iFW = new FileDataWriter(recordPath);
mFW = new MatroskaFileWriter(iFW);
mFW.writeEBMLHeader();
mFW.writeSegmentHeader();
mFW.writeSegmentInfo();
MatroskaFileTrack track = new MatroskaFileTrack();
track.TrackNo = (short)1;
track.TrackUID = new java.util.Random().nextLong();
track.TrackType = MatroskaDocType.track_video;
track.Name = "VP8";
track.Language = "und";
track.CodecID = "V_VP8";
track.DefaultDuration = 0;
track.Video_PixelWidth = 640;
track.Video_PixelHeight = 360;
track.CodecPrivate = new byte[0];
mFW.TrackList.add(track);
mFW.writeTracks();
}
private void openFile() throws IOException {
File recordFile = new File(recordPath);
try {
synchronized(this) {
synchronized(this)
{
if (recordFile.exists()) {
recordFile.delete();
}
......@@ -104,18 +149,16 @@ public class Recorder extends Thread
fo = new FileOutputStream(recordFile);
bo = new BufferedOutputStream(fo, BUFFER_SIZE);
if (recordRtp == false) {
writeAuHeader();
} else {
/*
* Write RTP header
*/
if (recordRtp)
{
byte[] buf = new byte[16];
buf[0] = (byte) 0x52; // R
buf[1] = (byte) 0x54; // T
buf[2] = (byte) 0x50; // P
bo.write(buf, 0, buf.length);
} else {
writeAuHeader();
}
}
} catch (IOException e) {
......@@ -254,8 +297,10 @@ public class Recorder extends Thread
public byte[] data;
public int offset;
public int length;
public boolean keyframe;
public long timestamp;
public DataToWrite(byte[] data, int offset, int length)
public DataToWrite(byte[] data, int offset, int length, boolean keyframe, long timestamp)
{
/*
* We have to copy the data, otherwise caller could
......@@ -264,6 +309,8 @@ public class Recorder extends Thread
this.data = new byte[length];
this.offset = offset;
this.length = length;
this.keyframe = keyframe;
this.timestamp = timestamp;
System.arraycopy(data, offset, this.data, 0, length);
}
......@@ -271,7 +318,7 @@ public class Recorder extends Thread
private long lastWriteTime;
public void writePacket(byte[] data, int offset, int dataLength)
public void writePacket(byte[] data, int offset, int dataLength, boolean keyframe, long timestamp)
throws IOException {
if (recordRtp) {
......@@ -295,13 +342,17 @@ public class Recorder extends Thread
buf[3] = (byte) (timeChange & 0xff);
System.arraycopy(data, offset, buf, 4, dataLength);
write(buf, 0, buf.length);
write(buf, 0, buf.length, keyframe, timestamp);
} else if (recordWebm) {
} else {
write(data, offset, dataLength);
write(data, offset, dataLength, keyframe, timestamp);
}
}
public void write(int[] data, int offset, int length) throws IOException {
public void write(int[] data, int offset, int length, boolean keyframe, long timestamp) throws IOException
{
byte[] byteData = new byte[length * 2];
for (int i = 0; i < length; i++) {
......@@ -309,16 +360,16 @@ public class Recorder extends Thread
byteData[(2 * i) + 1] = (byte) (data[i + offset] & 0xff);
}
write(byteData, 0, byteData.length);
write(byteData, 0, byteData.length, keyframe, timestamp);
}
public void write(byte[] data, int offset, int length) throws IOException {
public void write(byte[] data, int offset, int length, boolean keyframe, long timestamp) throws IOException {
if (done) {
return;
}
synchronized(dataToWrite) {
dataToWrite.add(new DataToWrite(data, offset, length));
dataToWrite.add(new DataToWrite(data, offset, length, keyframe, timestamp));
dataToWrite.notifyAll();
}
}
......@@ -367,10 +418,42 @@ public class Recorder extends Thread
private void writeData(DataToWrite d) {
try {
synchronized(this) {
synchronized(this)
{
if (recordWebm)
{
long duration = 0;
if (d.keyframe || lastTimecode == 0)
{
if (lastTimecode > 0)
{
Log.info("writeData end cluster " + d.data);
duration = d.timestamp - lastTimecode;
mFW.endCluster();
}
Log.info("writeData start cluster " + d.timestamp);
mFW.startCluster(d.timestamp);
}
lastTimecode = d.timestamp;
MatroskaFileFrame frame = new MatroskaFileFrame();
frame.TrackNo = 1;
frame.Duration = duration;
frame.Timecode = d.timestamp;
frame.Reference = 0;
frame.KeyFrame = d.keyframe;
frame.Data = d.data;
mFW.addFrame(frame);
Log.info("writeData video " + d.data);
} else {
bo.write(d.data, 0, d.length);
dataSize += d.length;
}
}
} catch (IOException e) {
Log.error("Can't record to " + recordPath, e);
done();
......@@ -379,7 +462,14 @@ public class Recorder extends Thread
private void writeDataSize() {
try {
synchronized(this) {
synchronized(this)
{
if (recordWebm)
{
mFW.endCluster();
iFW.close();
} else {
if (bo != null) {
bo.flush();
bo.close();
......@@ -407,6 +497,8 @@ public class Recorder extends Thread
}
}
}
}
} catch (IOException e) {
Log.error("Exception closing recording " + recordPath + " " + e.getMessage(), e);
}
......
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