Commit e377e557 authored by Alexandre Leray's avatar Alexandre Leray
Browse files

Half-backed commit to share the beggining of the timeline changes with

Michael
parent 07695c84
......@@ -161,7 +161,10 @@
function resetTimelines() {
/* create timeline */
$("body").timeline({
var master = new VoidPlayer($('#timelineslide').get(0));
var sync = $.aaMediaSync(master, {trace: false});
$("#timelineslider").data("sync", sync).timeline({
currentTime: function (elt) {
return $(elt).data("datetime");
},
......@@ -186,20 +189,16 @@
$(elt).removeClass("active");
},
start: function (elt) {
// console.log("body start for", elt);
return $.datetimecode_parse($(elt).attr("data-start"));
},
end: function (elt) {
// console.log("body end for", elt);
// start is defaultDate for end
var start = $.datetimecode_parse($(elt).attr("data-start"));
var end = $(elt).data("end");
if (end) { return $.datetimecode_parse(end, start); }
},
setCurrentTime: function (elt, t) {
// console.log("body.timeline.setCurrentTime", t);
// shoot a setCurrentTime "event" to any contained player
// $(".aaplayer", elt).aaplayer("setCurrentTime", (t/1000));
try { $("audio,video", elt).get(0).currentTime = (t/1000); }
catch (e) {}
......@@ -213,7 +212,8 @@
if (driver) {
driver = driver.get(0);
timelinesByURL[url] = driver;
$(driver).timeline({
var sync = $.aaMediaSync(driver);
$(driver).data('sync', sync).timeline({
show: function (elt) {
$(elt).addClass("active")
.closest('section.section1')
......@@ -228,16 +228,21 @@
//console.log("WARNING, no media found for about=", url);
}
}
return timelinesByURL[url];
return timelinesByURL[url];
}
/* Activate temporal html! */
$("[data-start]", $canvas).each(function () {
var about_url = $.trim($(this).closest("[about]").attr("about"));
// use the body as timeline if no about is given
var timeline = about_url ? timelineForURL(about_url) : $("body").get(0);
var timeline = about_url ? timelineForURL(about_url) : $("#timeslider").get(0);
if (timeline) {
$(timeline).timeline("add", this);
// add embedded media to sync
var sync = $(timeline).data("sync");
$("audio|video", this).each(function() {
sync.add(this, $(this).attr('data-start'), $(this).attr('data-end'));
});
}
});
......
(function ($) {
//////////////////////////////
// SYNCING CODE
// var when_all_ready_callbacks = [];
// SEEKING: Become the driver (if no one else is already), remember my playstate, pause all
// SEEKED: If I am the driver, (pause yourself -- ff hack), when_all_ready: relinquish driver role, play all (if I was playing when seeking began)
// TIMEUPDATE: If I am the driver, this is a "scrub": Set other elements' currentTime accordingly
// PLAY: Become the driver (if no one else is already), (pause self -- ff hack?): when_all_ready: play all -- relinquish driver role (with some delay!?)
// allow request for play to work together with a scrub
function aaMediaSync (element, opts) {
var that = {element: element},
syncedmedia = [element],
uid = 0,
driver = null,
initiatingGroupPlay = false,
driver_scrubstart_paused = undefined;
opts = $.extend({
trace: false
}, opts);
bindevents(element);
function remove (array, elt) {
for (var i=0, l=array.length; i<l; i++) {
if (array[i] === elt) { array.splice(i, 1); return i; }
}
}
function add(elt, start, end) {
var id = ++uid;
syncedmedia.push(elt);
// console.log("aamediasync.add", elt, start, end);
$(elt).data("aamediasync", {start: start, end: end});
bindevents(elt);
}
that.add = add;
function get_all_ready () {
var i, l, m;
for (i=0, l=syncedmedia.length; i<l; i++) {
m = syncedmedia[i];
if (!is_visible(m)) { continue; }
if (! ((m.readyState >= 3) && !m.seeking) ) {
return false;
}
}
return true;
}
function when_all_ready (callback) {
// abstract me (use syncedmedia array)
// Does this need to be written to work with the EVENTS SYSTEM (to stay properly in sync ?!)
if (opts.trace) console.log("when_all_ready", syncedmedia.length);
if (get_all_ready() === true) { callback(); } else {
window.setTimeout(function () { when_all_ready(callback) }, 100);
// when_all_ready_callbacks.push(callback);
}
}
function get_start_time (elt) {
if (elt === element)
return 0;
else {
var data = $(elt).data("aamediasync");
return data.start || 0;
}
}
function setCurrentTime(elt, t) {
// if (!is_visible(elt)) return;
if (elt.setCurrentTime) {
elt.setCurrentTime(t);
} else {
elt.currentTime = t;
}
}
function getRelativeTime(ref, forElt) {
// ref.currentTime is used as reference forElt
// returns currentTime for forElt, given ref.currentTime
var reftime = (ref === syncedmedia[0]) ? ref.currentTime : ref.currentTime + get_start_time(ref);
return (reftime - get_start_time(forElt));
}
function bind (elt, msg, callback) {
if (elt.element !== undefined) {
// Bind to DOM element, Arrange for callback to have the JS object as this (a.l.d. element)
$(elt.element).bind(msg, function () {
callback.call(elt)
});
} else {
// Bind as normal
$(elt).bind(msg, callback);
}
}
function is_visible (elt) {
if (elt.element !== undefined) {
return $(elt.element).is(":visible");
} else {
return $(elt).is(":visible");
}
}
function enter (elt) {
// sync elt time to timeline
var rt = getRelativeTime(element, elt);
if (opts.trace) console.log("enter", elt, rt);
setCurrentTime(elt, rt);
syncedmedia.push(elt);
if (!element.paused) {
// if (trace) console.log("enter: trigger group play");
element.pause();
element.play();
}
}
that.enter = enter;
function exit (elt) {
if (opts.trace) console.log("exit");
remove(syncedmedia, elt);
elt.pause();
}
that.exit = exit;
function groupplay (triggering_elt) {
if (opts.trace) console.log("initiate groupplay");
initiatingGroupPlay = true;
if (triggering_elt) triggering_elt.pause();
when_all_ready(function () {
if (opts.trace) console.log("ready, calling play");
// timeline.play();
$(syncedmedia).each(function () {
if (is_visible(this)) {
this.play()
}
});
window.setTimeout(function () {
initiatingGroupPlay = false;
}, 1000);
});
}
function bindevents (elt) {
bind(elt, "seeking", function () {
if (opts.trace) console.log("seeking", this);
if (driver === null) {
driver = this;
driver_scrubstart_paused = this.paused;
// PAUSE ALL
// timeline.pause();
$(syncedmedia).each(function () { this.pause(); });
}
});
bind(elt, "seeked", function () {
if (driver === this) {
// driver = null;
this.pause(); // HACK TO OVERRIDE (firefox) scrub resuming play
when_all_ready(function () {
// console.log("driver = null", driver_scrubstart_paused);
driver = null;
// restore play state
if (!driver_scrubstart_paused) {
// console.log("restore play state audio");
// timeline.play();
$(syncedmedia).each(function () {
if (is_visible(this)) { this.play(); }
});
}
});
}
})
bind(elt, "timeupdate", function () {
var media = $(this).data("media");
if (driver === this) {
// SCRUB (timeupdate while buffering = scrub event ?!)
if (opts.trace) console.log("scrub", this.currentTime, this);
// SET OTHER TIMES BASED OFF OF THIS ELEMENT
//timeline.setCurrentTime(get_start_time(this) + this.currentTime);
var eventreceiver = this;
$(syncedmedia).each(function () {
if (this !== eventreceiver) {
var newtime = getRelativeTime(eventreceiver, this);
// console.log("getRelativeTime", eventreceiver, this, newtime);
setCurrentTime(this, newtime);
// var time = timeline.currentTime - get_start_time(this);
// if (time >= 0) { this.currentTime = time; }
}
});
}
});
bind(elt, "play", function () {
if (initiatingGroupPlay === false) {
groupplay(this);
/*
if (opts.trace) console.log("initiate groupplay");
initiatingGroupPlay = true;
this.pause();
when_all_ready(function () {
if (opts.trace) console.log("ready, calling play");
// timeline.play();
$(syncedmedia).each(function () {
if (is_visible(this)) {
this.play()
}
});
window.setTimeout(function () {
initiatingGroupPlay = false;
}, 1000);
});
*/
}
});
bind(elt, "pause", function () {
// console.log("audio: pause");
var eventreceiver = this;
if (is_visible(this) && initiatingGroupPlay === false && driver === null) {
// TRIGGER GROUP PAUSE
// timeline.pause();
$(syncedmedia).each(function () {
if (this !== eventreceiver) {
this.pause();
}
});
}
});
}
return that;
}
$.aaMediaSync = aaMediaSync;
})(jQuery);
......@@ -30,10 +30,9 @@ var aTimeline = function (options) {
endIndex = -1,
toShow = {},
toHide = {},
activeItems = {},
settings = {};
activeItems = {};
$.extend(settings, options);
var settings = $.extend({}, options);
// element wrapper
function timeline_item (elt, start, end, show, hide) {
......@@ -56,12 +55,9 @@ var aTimeline = function (options) {
// addTitleByStart
/* maintain min/maxTime */
/* FIXME: timecodes using a period rather than a comma lead cannot be parsed */
if ((minTime === undefined) || (newtitle.start < minTime)) { minTime = newtitle.start; }
if ((maxTime === undefined) || (newtitle.start > maxTime)) { maxTime = newtitle.start; }
if ((maxTime === undefined) || (newtitle.end && (newtitle.end > maxTime))) { maxTime = newtitle.end; }
//console.log(newtitle.elt);
//console.log(minTime, maxTime);
/* insert annotation in the correct (sorted) location */
for (var i=0; i<titlesByStart.length; i++) {
......@@ -125,7 +121,11 @@ var aTimeline = function (options) {
}
function updateForTime (time, controller) {
if (titlesByStart.length === 0) { return; }
// console.log("updateForTime", time);
if (titlesByStart.length === 0) {
// console.log("no titlesByStart");
return;
}
var n;
/* check against lastTime to optimize search */
// valid range for i: -1 (pre first title)
......@@ -196,6 +196,11 @@ var aTimeline = function (options) {
/* setCurrentTime : MM: new NOV 2011 */
// console.log("setCurrentTime", settings);
/*
if (settings.ontimeupdate) {
settings.ontimeupdate(time);
}
*/
if (settings.setCurrentTime) {
for (tid in activeItems) {
var elt = activeItems[tid];
......@@ -239,24 +244,39 @@ var aTimeline = function (options) {
that.add = add;
function setCurrentTime (ct, evt_controller) {
// console.log("timeline.setCurrentTime", ct);
currentTime = ct;
updateForTime(ct, evt_controller);
}
that.setCurrentTime = setCurrentTime;
that.setCurrentTimeFromElement = function (elt, ct) {
// console.log("timeline.setCurrentTimeFromElement", elt, ct);
for (tid in activeItems) {
var item = activeItems[tid];
if (elt === item.elt) {
// console.log("found element", item.start);
that.setCurrentTime(ct + item.start, elt);
return;
}
}
console.log("timeline.setCurrentTimeFromElement, warning: elt not found");
}
that.getCurrentTime = function () { return currentTime; };
that.getMinTime = function () { return minTime; }
that.getMaxTime = function () { return maxTime; }
function debug () {
$.log("titlesByStart");
console.log("titlesByStart");
for (var i=0; i<titlesByStart.length; i++) {
var t = titlesByStart[i];
$.log(" ", t.elt, t.start, "("+t.end+")");
console.log(" ", t.elt, t.start, "("+t.end+")");
}
$.log("titlesByEnd");
console.log("titlesByEnd");
for (var i=0; i<titlesByEnd.length; i++) {
var t = titlesByEnd[i];
$.log(" ", t.elt, t.end, "("+t.start+")");
console.log(" ", t.elt, t.end, "("+t.start+")");
}
}
that.debug = debug;
......@@ -266,16 +286,18 @@ var aTimeline = function (options) {
// finally the plugin method itself
// based on http://docs.jquery.com/Plugins/Authoring
var settings = {
var defaults = {
currentTime: function (elt) { return elt.currentTime; },
show: function (elt) { $(elt).trigger("show"); },
hide: function (elt) { $(elt).trigger("hide"); },
start : function (elt) { return $.timecode_parse($(elt).attr("data-start")); },
end : function (elt) { return $.timecode_parse($(elt).attr("data-end")); }
}
var methods = {
init : function(options) {
var opts = {};
$.extend(opts, settings, options);
init : function(opts) {
opts = $.extend({}, defaults, opts);
return this.each(function() {
var elt = this;
......@@ -283,25 +305,28 @@ var methods = {
data = $this.data('timeline');
if (! data) {
// FIRST TIME INIT
data = {target: $this};
data.options = opts;
// data.tt = tt();
$(this).data('timeline', data);
}
// init ALWAYS creates a fresh timeline (so it can be used to reset
// the element and drop evt. dead refs)
data.timeline = aTimeline({
// console.log("init timeline", opts);
data.timeline = aTimeline(opts);
/*
data.timeline = tt({
show: opts.show,
hide: opts.hide,
setCurrentTime: opts.setCurrentTime
});
*/
$this.bind("timeupdate", function (event, controller) {
//console.log("timeline: timeupdate", event);
// console.log("timeline: timeupdate", event);
// allow a wrapped getCurrentTime for the element (via playable?)
var ct = opts.currentTime(elt);
//console.log("timeline: timeupdate", event.target, ct);
// console.log("timeline: timeupdate", event.target, ct);
data.timeline.setCurrentTime(ct, controller);
return true;
});
......@@ -327,7 +352,8 @@ var methods = {
return data.timeline.getCurrentTime();
} else {
data.timeline.setCurrentTime(t);
// $(this.elt).trigger("updatetime");
// console.log("currentTime", data.target);
$(this).trigger("timeupdate");
return this;
}
},
......@@ -340,7 +366,11 @@ var methods = {
add : function( selector, options ) {
var data = this.data('timeline');
options = options || {};
var media = [];
$(selector).each(function () {
// console.log("add", this);
var start = options.start || data.options.start;
var end = options.end || data.options.end;
if (typeof(start) == "function") {
......@@ -349,12 +379,11 @@ var methods = {
if (typeof(end) == "function") {
end = end(this);
}
// if (start) start = datetimecode_parse(start);
// if (end) end = datetimecode_parse(end, start);
if (options.debug) console.log("add", this, start, end);
// console.log("timeline.add", this, start, end);
data.timeline.add(this, start, end, options.show, options.hide);
// NEW (Dec 2011) Watch added elements for timeupdate events
/*
$(this).bind("timeupdate", function (e) {
// console.log("NEW timeline.timeupdate", this, e);
var sectionTime = $(this).data("currentTime");
......@@ -365,6 +394,7 @@ var methods = {
// data.timeline.setCurrentTimeFromChild(this, );
return false;
});
*/
});
// console.log("end of add");
return this;
......
/**
* VoidPlayer is released under the GNU Affero GPL version 3.
* More information at http://www.gnu.org/licenses/agpl-3.0.html
*/
(function($) {
window.VoidPlayer = function (element) {
this.element = element;
this.$element = $(element);
this.paused = true;
this.duration = this.$element.attr("data-duration").toSeconds();
this.currentTime = 0;
/* Void player is always ready ! */
this.readyState = 4;
this.seeking = false;
this.interval_id = undefined;
// console.log("duration", this.duration);
}
VoidPlayer.plugins = {};
VoidPlayer.prototype.play = function () {
// console.log("voidplayer.play");
if (!this.interval_id) {
// console.log("VoidPlayer.play setInterval,", this.interval_id);
this.paused = false;
var that = this;
this.start = new Date().getTime() - (this.currentTime * 1000);
this.interval_id = window.setInterval(function () {
var now = new Date();
that.currentTime = (now-that.start)/1000;
that.$element.trigger("timeupdate");
}, 250);
// THIS MUST BE AT THE END (TO AVOID UNSTABLE/RACE CONDITIONS)
this.$element.trigger("play");
}
}
VoidPlayer.prototype.pause = function () {
if (this.interval_id) {
// console.log("VoidPlayer.pause");
this.paused = true;
window.clearInterval(this.interval_id)
this.interval_id = null;
this.$element.trigger("pause");
}
}
/*
Firefox event sequence on <video> seek:
seeking
timeupdate
seeked
canplay
canplaythrough
Chrome event sequence on <video> seek:
seeked
timeupdate
seeking
*/
VoidPlayer.prototype.setCurrentTime = function (t) {
this.currentTime = t;
this.start = new Date().getTime() - (this.currentTime * 1000);
this.$element.trigger("seeking");
this.$element.trigger("timeupdate");
this.$element.trigger("seeked");
}
})(jQuery);
// jQuery.media timecode funcs
/**
* function String.toSeconds ()
*
* Convert any number to seconds
*/
String.prototype.toSeconds = function () {
var time = this;
if (/^([0-9]{1,2}:)?[0-9]{1,2}:[0-9]{1,2}(\.[0-9]+)?(,[0-9]+)?$/.test(time)) {
time = time.split(':', 3);
if (time.length == 3) {
var ms = time[2].split(',', 2);
ms[1] = ms[1] ? ms[1] : 0;
return ((((parseInt(time[0], 10) * 3600) + (parseInt(time[1], 10) * 60) + parseFloat(ms[0])) * 1000) + parseInt(ms[1], 10)) / 1000;
}
var ms = time[1].split(',', 1);
ms[1] = ms[1] ? ms[1] : 0;