/******************************************************************************* | |
* Copyright (c) 2009, 2015 IBM Corporation and others. | |
* All rights reserved. This program and the accompanying materials | |
* are made available under the terms of the Eclipse Public License v1.0 | |
* which accompanies this distribution, and is available at | |
* http://www.eclipse.org/legal/epl-v10.html | |
* | |
* Contributors: | |
* IBM Corporation - initial API and implementation | |
*******************************************************************************/ | |
// | |
// Ad-hoc codes to bridge the gap between the HTML5 spec and browser implementations | |
// | |
if (true) { // if (typeof TextTrack == 'undefined') { // As of Mar 2015, no major browser sufficiently supports TextTrack. | |
(function () { | |
TextTrack = function (kind, label, language) { // See: http://www.w3.org/TR/html5/embedded-content-0.html#texttrack | |
this.kind = kind; | |
this.label = label; | |
this.language = language; | |
this.mode = TextTrack.HIDDEN; | |
this.cues = new TextTrackCueList(); | |
this.activeCues = new TextTrackCueList(); | |
}; | |
TextTrack.DISABLED = 0; | |
TextTrack.HIDDEN = 1; | |
TextTrack.SHOWING = 2; | |
TextTrack.prototype.addCue = function (cue) { | |
cue.track = cue.__track__ = this; | |
this.cues.__addCue__(cue); | |
}; | |
TextTrack.prototype.removeCue = function (cue) { | |
cue.track = cue.__track__ = null; | |
this.cues.__removeCue__(cue); | |
}; | |
TextTrack.prototype.toString = function () { | |
return 'TextTrack <' + this.kind + '> ' + this.label + ' (' + this.language + ') : ' + this.cues; | |
}; | |
TextTrackList = function () { // See: http://www.w3.org/TR/html5/embedded-content-0.html#texttracklist | |
var list = []; | |
list.getTrackById = function (id) { | |
for (var i = 0; i < this.length; ++i) { | |
var e = this[i]; | |
if (e.id == id) { | |
return e; | |
} | |
} | |
return null; | |
}; | |
list.toString = function () { | |
return 'TextTrackList [\n\t' + this.join(',\n\t') + '\n]'; | |
}; | |
return list; | |
}; | |
TextTrackCue = function (startTime, endTime, text) { // See: http://www.w3.org/TR/html5/embedded-content-0.html#texttrackcue | |
if (typeof VTTCue != 'undefined') { | |
return new VTTCue(startTime, endTime, text); | |
} | |
else { | |
this.startTime = startTime; | |
this.endTime = endTime; | |
this.text = text; | |
} | |
}; | |
TextTrackCue.prototype.toString = function () { | |
return 'TextTrackCue <' + this.id + '> ' + this.startTime + ' -> ' + this.endTime + ' : ' + this.text + (this.pauseOnExit ? ' * pause-on-exit * ' : ' '); | |
}; | |
TextTrackCueList = function () { // See: http://www.w3.org/TR/html5/embedded-content-0.html#texttrackcuelist | |
var list = []; | |
list.getCueById = function (id) { | |
for (var i = 0; i < this.length; ++i) { | |
var e = this[i]; | |
if (e.id == id) { | |
return e; | |
} | |
} | |
return null; | |
}; | |
list.__addCue__ = function (cue) { | |
this.push(cue); | |
}; | |
list.__removeCue__ = function (cue) { | |
for (var i = 0; i < this.length; ++i) { | |
if (this[i] === cue) { | |
this.splice(i--, 1); | |
} | |
} | |
}; | |
list.toString = function () { | |
return 'TextTrackCueList [\n\t' + this.join(',\n\t') + '\n]'; | |
}; | |
return list; | |
}; | |
var fixMediaElement = function (m) { | |
m.textTracks = m.__textTracks__ = new TextTrackList(); | |
m.addTextTrack = m.__addTextTrack__ = addTextTrack; | |
m.__lastTime__ = m.currentTime; | |
m.addEventListener('timeupdate', updateCues, false); | |
m.addEventListener('seeked', updateCues, false); | |
}; | |
var fixTrackElement = function (t) { | |
t.kind = t.getAttribute('kind'); | |
t.src = t.getAttribute('src'); | |
t.srclang = t.getAttribute('srclang'); | |
t.label = t.getAttribute('label'); | |
t['default'] = t.getAttribute('default') != null; // .default causes an error in IE | |
if (!t.dataset) { | |
var d = {}; | |
var a = t.attributes; | |
for (var i = 0; i < a.length; ++i) { | |
if (a[i].name.indexOf('data-') == 0) { | |
d[a[i].name.substr(5)] = a[i].value; | |
} | |
} | |
t.dataset = d; | |
} | |
}; | |
var addTextTrack = function (kind, label, language) { // HTMLMediaElement#addTextTrack | |
var t = new TextTrack(kind, label, language); | |
this.__textTracks__.push(t); | |
return t; | |
}; | |
var updateCues = function (e) { // http://dev.w3.org/html5/spec/media-elements.html#media-playback | |
var video = e.target; | |
var currentTime = video.currentTime; | |
var lastTime = video.__lastTime__; video.__lastTime__ = currentTime; | |
var isMonotonicIncrease = currentTime > lastTime && currentTime <= lastTime + .5; // major browsers fire timeupdate events every 250 ms | |
var currentCues = []; | |
var otherCues = []; | |
var conflicted = false; | |
var tracks = video.__textTracks__; | |
for (var i = 0; i < tracks.length; ++i) { | |
var t = tracks[i]; | |
if (t.mode != TextTrack.DISABLED) { | |
var cues = t.cues; | |
for (var j = 0; j < cues.length; ++j) { | |
var c = cues[j]; | |
if (c.startTime <= currentTime && c.endTime > currentTime) { | |
currentCues.push(c); | |
c.__missed__ = false; | |
conflicted = conflicted || !c.__active__; | |
} | |
else { | |
otherCues.push(c); | |
c.__missed__ = isMonotonicIncrease && c.startTime >= lastTime && c.endTime <= currentTime; | |
conflicted = conflicted || c.__active__ || c.__missed__; | |
} | |
} | |
} | |
} | |
if (!conflicted) { | |
return; | |
} | |
var events = []; | |
for (var i = 0; i < otherCues.length; ++i) { | |
var c = otherCues[i]; | |
if (c.pauseOnExit && (c.__active__ || c.__missed__)) { | |
video.pause(); | |
} | |
if (c.__missed__) { | |
events.push({ type: 'enter', timeStamp: c.startTime, target: c }); | |
} | |
if (c.__active__ || c.__missed__) { | |
events.push({ type: 'exit', timeStamp: c.endTime, target: c }); | |
} | |
c.__active__ = false; | |
c.__track__.activeCues.__removeCue__(c); | |
} | |
for (var i = 0; i < currentCues.length; ++i) { | |
var c = currentCues[i]; | |
if (!c.__active__) { | |
events.push({ type: 'enter', timeStamp: c.startTime, target: c }); | |
} | |
c.__active__ = true; | |
c.__track__.activeCues.__addCue__(c); | |
} | |
events.sort(function (a, b) { return a.timeStamp - b.timeStamp }); | |
for (var i = 0; i < events.length; ++i) { | |
var e = events[i]; | |
var f = e.target['on' + e.type]; | |
if (f) { | |
f.call(e.target, e); | |
} | |
} | |
}; | |
var videoElems = document.getElementsByTagName('video'); | |
for (var i = 0; i < videoElems.length; ++i) { | |
fixMediaElement(videoElems[i]); | |
} | |
var audioElems = document.getElementsByTagName('audio'); | |
for (var i = 0; i < audioElems.length; ++i) { | |
fixMediaElement(audioElems[i]); | |
} | |
var trackElems = document.getElementsByTagName('track'); | |
for (var i = 0; i < trackElems.length; ++i) { | |
var t = trackElems[i]; | |
fixTrackElement(t); | |
t.track = t.__track__ = t.parentNode.__addTextTrack__(t.kind, t.label, t.srclang); | |
} | |
})(); | |
} | |
if (true) { // if (typeof MediaController == 'undefined') { // As of Mar 2015, no major browser sufficiently supports MediaController. | |
(function () { | |
MediaController = function () {}; // See: http://www.w3.org/TR/html5/embedded-content-0.html#mediacontroller | |
MediaController.prototype.__syncTime__ = function (kicker) { | |
if (kicker.__seekedForSync__) { | |
kicker.__seekedForSync__ = false; | |
return; | |
} | |
var slaveElems = getSlaveElementsOf(this); | |
var currentTime = kicker.currentTime; | |
for (var i = 0; i < slaveElems.length; ++i) { | |
var s = slaveElems[i]; | |
if (s !== kicker) { | |
s.__seekedForSync__ = true; | |
s.currentTime = currentTime; | |
} | |
} | |
}; | |
MediaController.prototype.__syncPlayback__ = function (kicker) { | |
var slaveElems = getSlaveElementsOf(this); | |
var readyState = 0; | |
var paused = false; | |
for (var i = 0; i < slaveElems.length; ++i) { | |
var s = slaveElems[i]; | |
readyState = Math.max(readyState, s.readyState); | |
paused = paused || s.ended || (s.paused && !s.__pausedForSync__); | |
} | |
this.readyState = readyState; | |
this.paused = paused || readyState < 4; // < HAVE_ENOUGH_DATA | |
for (var i = 0; i < slaveElems.length; ++i) { | |
var s = slaveElems[i]; | |
if (this.paused) { | |
if (!s.paused) { | |
s.__pausedForSync__ = true; | |
s.pause(); | |
} | |
else { | |
} | |
} | |
else { | |
if (s.paused) { | |
s.__pausedForSync__ = false; | |
s.play(); | |
} | |
else { | |
s.__pausedForSync__ = false; | |
} | |
} | |
} | |
}; | |
var getSlaveElementsOf = function (controller) { | |
var videoElems = document.getElementsByTagName('video'); | |
var audioElems = document.getElementsByTagName('audio'); | |
var slaveElems = []; | |
for (var i = 0; i < videoElems.length; ++i) { | |
var v = videoElems[i]; | |
if (v.controller === controller) { | |
slaveElems.push(v); | |
} | |
} | |
for (var i = 0; i < audioElems.length; ++i) { | |
var a = audioElems[i]; | |
if (a.controller === controller) { | |
slaveElems.push(a); | |
} | |
} | |
return slaveElems; | |
}; | |
var controllers = {}; | |
var fixMediaElement = function (m) { | |
m.mediaGroup = m.getAttribute('mediagroup'); | |
if (!m.mediaGroup) { | |
return; | |
} | |
m.controller = controllers[m.mediaGroup] || (controllers[m.mediaGroup] = new MediaController()); | |
m.addEventListener('seeked', syncTime, false); | |
m.addEventListener('seeked', syncPlayback, false); | |
m.addEventListener('playing', syncPlayback, false); | |
m.addEventListener('waiting', syncPlayback, false); | |
m.addEventListener('pause', syncPlayback, false); | |
m.addEventListener('ended', syncPlayback, false); | |
m.__pausedForSync__ = true; | |
}; | |
var syncTime = function (e) { | |
e.target.controller.__syncTime__(e.target); | |
}; | |
var syncPlayback = function (e) { | |
e.target.controller.__syncPlayback__(e.target); | |
}; | |
var videoElems = document.getElementsByTagName('video'); | |
for (var i = 0; i < videoElems.length; ++i) { | |
fixMediaElement(videoElems[i]); | |
} | |
var audioElems = document.getElementsByTagName('audio'); | |
for (var i = 0; i < audioElems.length; ++i) { | |
fixMediaElement(audioElems[i]); | |
} | |
})(); | |
} | |