diff --git a/record-and-playback/presentation/playback/presentation/0.9.0/lib/writing.js b/record-and-playback/presentation/playback/presentation/0.9.0/lib/writing.js index 3bfa25b6da..12ace8dde7 100755 --- a/record-and-playback/presentation/playback/presentation/0.9.0/lib/writing.js +++ b/record-and-playback/presentation/playback/presentation/0.9.0/lib/writing.js @@ -188,14 +188,6 @@ function isThereDeskshareVideo() { } } -function resyncVideos() { - if (!isThereDeskshareVideo()) return; - var currentTime = Popcorn('#video').currentTime(); - var currentDeskshareVideoTime = Popcorn("#deskshare-video").currentTime(); - if (Math.abs(currentTime - currentDeskshareVideoTime) >= 0.1) - Popcorn("#deskshare-video").currentTime(currentTime); -} - function handlePresentationAreaContent(time) { var meetingDuration = parseFloat(new Popcorn("#video").duration().toFixed(1)); if(time >= meetingDuration) @@ -214,7 +206,6 @@ function handlePresentationAreaContent(time) { sharingDesktop = false; } - resyncVideos(); resizeDeshareVideo(); } @@ -260,15 +251,15 @@ function runPopcorn() { var shapeelements = xmlDoc.getElementsByTagName("svg"); //get the array of values for the first shape (getDataPoints(0) is the first shape). - var array = $(shapeelements[0]).find("g").filter(function(){ //get all the lines from the svg file + var shapesArray = $(shapeelements[0]).find("g").filter(function(){ //get all the lines from the svg file return $(this).attr('class') == 'shape'; }); //create a map from timestamp to id list var timestampToId = {}; - for (var j = 0; j < array.length; j++) { - shapeTime = array[j].getAttribute("timestamp"); - shapeId = array[j].getAttribute("id"); + for (var j = 0; j < shapesArray.length; j++) { + shapeTime = shapesArray[j].getAttribute("timestamp"); + shapeId = shapesArray[j].getAttribute("id"); if (timestampToId[shapeTime] == undefined) { timestampToId[shapeTime] = new Array(0); @@ -277,8 +268,8 @@ function runPopcorn() { } //fill the times array with the times of the svg images. - for (var j = 0; j < array.length; j++) { - times[j] = array[j].getAttribute("timestamp"); + for (var j = 0; j < shapesArray.length; j++) { + times[j] = shapesArray[j].getAttribute("timestamp"); } var times_length = times.length; //get the length of the times array. @@ -353,17 +344,17 @@ function runPopcorn() { svgobj.style.top = "0px"; var next_shape; var shape; - for (var j = 0; j < array.length - 1; j++) { //iterate through all the shapes and pick out the main ones - var time = array[j].getAttribute("timestamp"); - shape = array[j].getAttribute("shape"); - next_shape = array[j+1].getAttribute("shape"); + for (var j = 0; j < shapesArray.length - 1; j++) { //iterate through all the shapes and pick out the main ones + var time = shapesArray[j].getAttribute("timestamp"); + shape = shapesArray[j].getAttribute("shape"); + next_shape = shapesArray[j+1].getAttribute("shape"); - if(shape !== next_shape) { - main_shapes_ids.push(array[j].getAttribute("id")); - } + if(shape !== next_shape) { + main_shapes_ids.push(shapesArray[j].getAttribute("id")); + } } - if (array.length !== 0) { - main_shapes_ids.push(array[array.length-1].getAttribute("id")); //put last value into this array always! + if (shapesArray.length !== 0) { + main_shapes_ids.push(shapesArray[shapesArray.length-1].getAttribute("id")); //put last value into this array always! } var get_shapes_in_time = function(t) { @@ -386,64 +377,95 @@ function runPopcorn() { return shapes; }; + var timeThreshold = 0.5; + // Check if the update is following the playback flow. + // Otherwise is some kind of seek. + var regularPlaybackFlow = function(updateTime, lastUpdateTime) { + if (Math.abs(updateTime - lastUpdateTime) > timeThreshold) { + return false; + } else { + return true; + } + } + + var drawShape = function(shapes, shapeElement, shapeTime, currentTime) { + var visibleShape = false; + if (shapeTime < currentTime) { + // Check if it is a main shape + if(main_shapes_ids.indexOf(shapeElement.getAttribute("id")) > -1) { + var undoTime = parseFloat(shapeElement.getAttribute("undo")); + // And hasn't been undone + if(undoTime === -1 || undoTime > currentTime) { + shapeElement.style.visibility = "visible"; + visibleShape = true; + } + } + } else if (shapeTime == currentTime) { + // Only makes visible the last drawing of a given shape + var shape = shapeElement.getAttribute("shape"); + var shapeIndex = shapes.indexOf(shape); + if (shapeIndex > -1) { + shapes.splice(shapeIndex, 1); + shapeIndex = shapes.indexOf(shape); + if (shapeIndex === -1) { + shapeElement.style.visibility = "visible"; + visibleShape = true; + } + } + } + // It's not a visible shape, force it to be hidden + if (!visibleShape) { + shapeElement.style.visibility = "hidden"; + } + } + + var lastShapeTime = -1; + var updateShapes = function(time) { + var svgDocument = null; + var shapes = get_shapes_in_time(time); + var currentTime = parseFloat(time); + + if (svgobj.contentDocument) svgDocument = svgobj.contentDocument; + else svgDocument = svgobj.getSVGDocument('svgfile'); + + // Abort if there is no SVG available + if (svgDocument == null) return; + + if (regularPlaybackFlow(currentTime, lastShapeTime)) { + for (var i = 0; i < shapesArray.length; i++) { + var shapeTime = parseFloat(shapesArray[i].getAttribute("timestamp")); + if (shapeTime <= currentTime) { + // Draw only inside this interval + var shapeElement = svgDocument.getElementById(shapesArray[i].getAttribute("id")); + drawShape(shapes, shapeElement, shapeTime, currentTime); + } else { + break; + } + } + } else { + for (var i = 0; i < shapesArray.length; i++) { + var shapeTime = parseFloat(shapesArray[i].getAttribute("timestamp")); + var shapeElement = svgDocument.getElementById(shapesArray[i].getAttribute("id")); + drawShape(shapes, shapeElement, shapeTime, currentTime); + } + } + lastShapeTime = currentTime; + } + var p = new Popcorn("#video"); //update 60x / second the position of the next value. p.code({ start: 1, // start time end: p.duration(), onFrame: function(options) { - //console.log("**Popcorn video onframe"); - if(!((p.paused() === true) && (p.seeking() === false))) { - var t = p.currentTime().toFixed(1); //get the time and round to 1 decimal place + var currentTime = p.currentTime(); + if ( (!p.paused() || p.seeking()) && (Math.abs(currentTime - lastFrameTime) >= 0.1) ) { + lastFrameTime = currentTime; + var t = currentTime.toFixed(1); //get the time and round to 1 decimal place - current_shapes = get_shapes_in_time(t); - - //redraw everything (only way to make everything elegant) - for (var i = 0; i < array.length; i++) { - var time_s = array[i].getAttribute("timestamp"); - var time_f = parseFloat(time_s); - - if(svgobj.contentDocument) shape = svgobj.contentDocument.getElementById(array[i].getAttribute("id")); - else shape = svgobj.getSVGDocument('svgfile').getElementById(array[i].getAttribute("id")); - - var shape_i = shape.getAttribute("shape"); - if (time_f < t) { - if(current_shapes.indexOf(shape_i) > -1) { //currently drawing the same shape so don't draw the older steps - shape.style.visibility = "hidden"; //hide older steps to shape - } else if(main_shapes_ids.indexOf(shape.getAttribute("id")) > -1) { //as long as it is a main shape, it can be drawn... no intermediate steps. - if(parseFloat(shape.getAttribute("undo")) === -1) { //As long as the undo event hasn't happened yet... - shape.style.visibility = "visible"; - } else if (parseFloat(shape.getAttribute("undo")) > t) { - shape.style.visibility = "visible"; - } else { - shape.style.visibility = "hidden"; - } - } else { - shape.style.visibility = "hidden"; - } - } else if(time_s === t) { //for the shapes with the time specific to the current time - // only makes visible the last drawing of a given shape - var idx = current_shapes.indexOf(shape_i); - if (idx > -1) { - current_shapes.splice(idx, 1); - idx = current_shapes.indexOf(shape_i); - if (idx > -1) { - shape.style.visibility = "hidden"; - } else { - shape.style.visibility = "visible"; - } - } else { - // this is an inconsistent state, since current_shapes should have at least one drawing of this shape - shape.style.visibility = "hidden"; - } - } else { //for shapes that shouldn't be drawn yet (larger time than current time), don't draw them. - shape.style.visibility = "hidden"; - } - } + updateShapes(t); var next_image = getImageAtTime(t); //fetch the name of the image at this time. - var imageXOffset = 0; - var imageYOffset = 0; if(current_image && (current_image !== next_image) && (next_image !== undefined)){ //changing slide image if(svgobj.contentDocument) { @@ -625,11 +647,12 @@ function defineStartTime() { return temp_start_time; } +var lastFrameTime = 0.0; + var deskshare_image = null; var current_image = "image0"; var previous_image = null; var current_canvas; -var shape; var next_canvas; var next_image; var next_pgid; @@ -638,7 +661,6 @@ var svgfile; //current time var t; var len; -var current_shapes = []; //coordinates for x and y for each second var panAndZoomTimes = []; var viewBoxes = []; @@ -713,8 +735,12 @@ function initPopcorn() { firstLoad = false; generateThumbnails(); - var p = Popcorn("#video"); - p.currentTime(defineStartTime()); + var startTime = defineStartTime(); + console.log("** startTime = " + startTime); + + Popcorn("#video").currentTime(startTime); + if(isThereDeskshareVideo()) + Popcorn("#deskshare-video").currentTime(startTime); } svgobj.addEventListener('load', function() { diff --git a/record-and-playback/presentation/playback/presentation/0.9.0/playback.js b/record-and-playback/presentation/playback/presentation/0.9.0/playback.js index 003d5d356c..c409809061 100755 --- a/record-and-playback/presentation/playback/presentation/0.9.0/playback.js +++ b/record-and-playback/presentation/playback/presentation/0.9.0/playback.js @@ -113,6 +113,11 @@ var RECORDINGS = "/presentation/" + MEETINGID; var SLIDES_XML = RECORDINGS + '/slides_new.xml'; var SHAPES_SVG = RECORDINGS + '/shapes.svg'; var hasVideo = false; +var syncing = false; +var masterVideoSeeked = false; +var primaryMedia; +var secondaryMedias; +var allMedias; /* * Sets the title attribute in a thumbnail. @@ -326,7 +331,11 @@ load_video = function(){ //video.setAttribute('autoplay','autoplay'); document.getElementById("video-area").appendChild(video); - document.dispatchEvent(new CustomEvent('media-ready', {'detail': 'video'})); + + Popcorn("#video").on("canplayall", function() { + console.log("==Video loaded"); + document.dispatchEvent(new CustomEvent('media-ready', {'detail': 'video'})); + }); } load_audio = function() { @@ -360,7 +369,12 @@ load_audio = function() { //leave auto play turned off for accessiblity support //audio.setAttribute('autoplay','autoplay'); document.getElementById("audio-area").appendChild(audio); - document.dispatchEvent(new CustomEvent('media-ready', {'detail': 'audio'})); + + //remember: audio id is 'video' + Popcorn("#video").on("canplayall", function() { + console.log("==Audio loaded"); + document.dispatchEvent(new CustomEvent('media-ready', {'detail': 'audio'})); + }); } load_deskshare_video = function () { @@ -376,14 +390,88 @@ load_deskshare_video = function () { var presentationArea = document.getElementById("presentation-area"); presentationArea.insertBefore(deskshare_video,presentationArea.childNodes[0]); - $('#video').on("play", function() { - Popcorn('#deskshare-video').play(); + setSync(); + + Popcorn("#deskshare-video").on("canplayall", function() { + console.log("==Deskshare video loaded"); + document.dispatchEvent(new CustomEvent('media-ready', {'detail': 'deskshare'})); }); - $('#video').on("pause", function() { - Popcorn('#deskshare-video').pause(); +} + +function setSync() { + //master video + primaryMedia = Popcorn("#video"); + + //slave videos + secondaryMedias = [ Popcorn("#deskshare-video") ]; + + allMedias = [primaryMedia].concat(secondaryMedias); + + //when we play the master video, we play all other videos as well... + primaryMedia.on("play", function() { + for(i = 0; i < secondaryMedias.length ; i++) + secondaryMedias[i].play(); }); - document.dispatchEvent(new CustomEvent('media-ready', {'detail': 'deskshare'})); + //when we pause the master video, we sync + primaryMedia.on("pause", function() { + sync(); + }); + + primaryMedia.on("seeking", function() { + if(primaryMedia.played().length != 0) + masterVideoSeeked = true; + }); + + //when finished seeking, we sync all medias... + primaryMedia.on("seeked", function() { + if(primaryMedia.paused()) + sync(); + else + primaryMedia.pause(); + }); + + + for(i = 0; i < allMedias.length ; i++) { + + allMedias[i].on("waiting", function() { + //if one of the medias is 'waiting', we must sync + if(!primaryMedia.seeking() && !syncing) { + syncing = true; + //pause the master video, causing to pause and sync all videos... + console.log("syncing videos..."); + primaryMedia.pause(); + } + }); + + + allMedias[i].on("canplaythrough", function() { + if(syncing || masterVideoSeeked) { + var allMediasAreReady = true; + for(i = 0; i < allMedias.length ; i++) + allMediasAreReady &= (allMedias[i].media.readyState == 4) + + if(allMediasAreReady) { + syncing = false; + masterVideoSeeked = false; + //play the master video, causing to play all videos... + console.log("resuming..."); + primaryMedia.play(); + } + } + }); + } +} + +function sync() { + for(var i = 0; i < secondaryMedias.length ; i++) { + if(secondaryMedias[i].media.readyState > 1) { + secondaryMedias[i].pause(); + + //set the current time will fire a "canplaythrough" event to tell us that the video can be played... + secondaryMedias[i].currentTime(primaryMedia.currentTime()); + } + } } load_script = function(file){