blob: 2d7fc0fa5733af4978e6f62439c094125171deb9 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 2012 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
*******************************************************************************/
//
// Plays video descriptions (including extended video descriptions and external audio fragments) on the draft HTML5 spec
//
(function () {
var TEXT_RESOURCES = { playLabel: '\u518d\u751f', pauseLabel: '\u4e00\u6642\u505c\u6b62', stopLabel: '\u505c\u6b62', skipLabel: '10\u79d2\u9032\u3080', backLabel: '10\u79d2\u623b\u308b', volUpLabel: '\u97f3\u91cf\u5927', volDnLabel: '\u97f3\u91cf\u5c0f', timeFormat: '%m\u5206%s\u79d2', showTextLabel: '\u30c6\u30ad\u30b9\u30c8\u3092\u8868\u793a' };
var FORMAT_REPLACE = { MSIE: 'm4a', AppleWebKit: 'm4a', _: 'oga' }; // formats for .* external audio resources (e.g., 'foo.*' is replaced with 'foo.m4a' on MSIE)
// Parses external text track resources
//
var TextTrackParser = [];
TextTrackParser.read = function (xhr) {
for (var i = 0; i < this.length; ++i) {
var r = this[i];
if (r.isReadable(xhr)) {
return r.readCues(xhr);
}
}
return null;
};
TextTrackParser.register = function (name, isReadable, readCues) {
this.push({ name: name, isReadable: isReadable, readCues: readCues });
};
// TTML (ignores layout and styles)
//
(function () {
var TTML_NAMESPACE = 'http://www.w3.org/ns/ttml';
var XML_NAMESPACE = 'http://www.w3.org/XML/1998/namespace';
var TVD_NAMESPACE = 'http://www.eclipse.org/actf/ai/tvd';
var frameRate = 30;
var subFrameRate = 1;
var timeToSecond = function (exp) {
if (!exp) return null;
var v = exp.match(/^(\d{2,}):(\d{2}):(\d{2})(?:\.(\d+)|:(\d{2})(?:\.(\d+))?)?$/);
var h = v[1] * 60 * 60;
var m = v[2] * 60;
var s = parseFloat(v[3] + '.' + (v[4] || 0));
var f = parseInt(v[5] || 0) / frameRate;
var u = parseInt(v[6] || 0) / frameRate / subFrameRate;
return h + m + s + f + u;
};
var wildcardToFormat = function (url) {
if (!url) return null;
var v = navigator.userAgent.match(/MSIE|AppleWebKit/) || ['_'];
var f = FORMAT_REPLACE[v[0]];
return url.replace('*', f);
};
var isTTML = function (xhr) {
var xml = xhr.responseXML;
if (!xml) return false;
var tts = xml.getElementsByTagName('tt'); // xml.getElementsByTagNameNS(TTML_NAMESPACE, 'tt'); // for IE
return tts.length == 1;
};
var readCues = function (xhr) {
var xml = xhr.responseXML;
var ps = xml.getElementsByTagName('p'); // xml.getElementsByTagNameNS(TTML_NAMESPACE, 'p'); // for IE
var cues = [];
for (var i = 0; i < ps.length; ++i) {
var p = ps[i];
var id = p.getAttribute('xml:id'); // p.getAttributeNS(XML_NAMESPACE, 'id'); // for IE
var begin = timeToSecond(p.getAttribute('begin'));
var end = timeToSecond(p.getAttribute('end'));
var dur = timeToSecond(p.getAttribute('dur'));
var text = p.textContent || p.firstChild.nodeValue;
var extended = p.getAttribute('tvd:extended') == 'true'; // p.getAttributeNS(TVD_NAMESPACE, 'extended') == 'true'; // for IE
var external = wildcardToFormat(p.getAttribute('tvd:external')); // p.getAttributeNS(TVD_NAMESPACE, 'external'); // for IE
end = end || (begin + (extended ? 0 : dur));
dur = dur || (end - begin);
var cue = new TextTrackCue(id, begin, end, text, '', extended);
cue.__duration__ = dur;
cue.__external__ = external;
cues.push(cue);
}
return cues;
};
TextTrackParser.register('TTML', isTTML, readCues);
})();
// WebVTT (ignores layout and styles)
//
(function () {
var timeToSecond = function (exp) {
if (!exp) return null;
var v = exp.match(/^(?:(\d{2,}):)?(\d{2}):(\d{2}\.\d{3})$/);
var h = (v[1] || 0) * 60 * 60;
var m = v[2] * 60;
var s = parseFloat(v[3]);
return h + m + s;
};
var wildcardToFormat = function (url) {
if (!url) return null;
var v = navigator.userAgent.match(/MSIE|AppleWebKit/) || ['_'];
var f = FORMAT_REPLACE[v[0]];
return url.replace('*', f);
};
var isWebVTT = function (xhr) {
var txt = xhr.responseText;
return txt.match(/^WEBVTT\b/);
};
var readCues = function (xhr) {
var txt = xhr.responseText.replace(/\r\n|\r/g, '\n');
var elems = txt.split(/\n{2,}/);
var cues = [];
for (var i = 0; i < elems.length; ++i) {
var e = elems[i];
var m = e.match(/^(?:(\w+)\n)?((?:\d{2,})?:\d{2}:\d{2}\.\d{3})[ \t]+-->[ \t]+((?:\d{2,})?:\d{2}:\d{2}\.\d{3})(?:[ \t]+([^\n]+))?\n([\w\W]+)$/m);
if (m) {
var id = m[1] || '';
var begin = timeToSecond(m[2]);
var end = timeToSecond(m[3]);
var settings = m[4] || '';
var extended = settings.match(/\bextended\b/);
var text = m[5];
var m1 = settings.match(/\bduration=((?:\d{2,}:)?\d{2}:\d{2}\.\d{3})/);
var dur = m1 ? timeToSecond(m1[1]) : 0;
var m2 = settings.match(/\bexternal=([^,]+)/);
var external = wildcardToFormat(m2 ? m2[1] : '');
end = end || (begin + (extended ? 0 : dur));
dur = dur || (end - begin);
var cue = new TextTrackCue(id, begin, end, text, '', extended);
cue.__duration__ = dur;
cue.__external__ = external;
cues.push(cue);
}
}
return cues;
};
TextTrackParser.register('WebVTT', isWebVTT, readCues);
})();
// Shows/reads aloud captions/descriptions
//
var initCues = function (track, video) {
var onenter = function (e) {
var cue = e.target;
if (cue.track.__kind__ == 'descriptions') {
if (cue.__audio__) { // external resource exists
cue.__audio__.currentTime = 0;
cue.__audio__.play();
video.__descriptions__.setAttribute('aria-live', 'off');
showText(cue.text, video.__descriptions__);
}
else { // uses screen reader
video.__descriptions__.setAttribute('aria-live', 'assertive');
showText(cue.text, video.__descriptions__);
}
}
else if (cue.track.__kind__ == 'captions') {
showText(cue.text, video.__captions__);
}
};
var onexit = function (e) {
var cue = e.target;
if (cue.track.__kind__ == 'descriptions') {
var isExtended = cue.pauseOnExit && video.paused;
if (isExtended) {
if (cue.__audio__) {
if (cue.__audio__.ended) {
showText('', video.__descriptions__);
video.play();
}
else {
cue.__audio__.addEventListener('ended', function () {
showText('', video.__descriptions__);
video.play();
}, false);
}
}
else {
if (cue.endTime - cue.startTime >= cue.__duration__) {
showText('', video.__descriptions__);
video.play();
}
else {
setTimeout(function () {
showText('', video.__descriptions__);
video.play();
}, (cue.__duration__ - cue.endTime + cue.startTime) * 1000);
}
}
}
else {
if (cue.__audio__) {
cue.__audio__.pause();
showText('', video.__descriptions__);
}
else {
showText('', video.__descriptions__);
}
}
}
else if (cue.track.__kind__ == 'captions') {
showText('', video.__captions__);
}
};
var showText = function (text, container) {
var oldText = container.firstChild;
var newText = document.createElement('span');
newText.appendChild(document.createTextNode(text));
container.replaceChild(newText, oldText);
};
var cues = track.cues;
for (var i = 0; i < cues.length; ++i) {
var c = cues[i];
if (c.__external__) {
c.__audio__ = new Audio(c.__external__);
}
c.onenter = onenter;
c.onexit = onexit;
}
};
var loadCues = function (track, video) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
switch (xhr.status) {
case 200:
case 0: // for local files
var cues = TextTrackParser.read(xhr);
if (cues) {
for (var i = 0; i < cues.length; ++i) {
track.track.addCue(cues[i]);
}
track.track.mode = track['default'] ? TextTrack.SHOWING : TextTrack.DISABLED; // .default causes an error in IE
initCues(track.track, video);
}
break;
}
}
};
xhr.open('GET', track.src, true);
xhr.send(null);
};
// Sets up control buttons and caption/description display areas
//
var createVDControls = function (video) {
var paused = video.paused;
var e = document.createElement('div');
e.className = 'vd-controls';
video.__controls__ = video.parentNode.appendChild(e);
var toggleButton = e.appendChild(document.createElement('div')).appendChild(document.createElement('button'));
toggleButton.appendChild(document.createTextNode(''));
toggleButton.accessKey = 'p';
toggleButton.onclick = function () {
if (paused) {
this.__update__(paused = false);
video.play();
}
else {
this.__update__(paused = true);
video.pause();
}
};
toggleButton.__update__ = function () {
this.firstChild.nodeValue = paused ? TEXT_RESOURCES.playLabel : TEXT_RESOURCES.pauseLabel;
};
toggleButton.__update__();
var stopButton = e.appendChild(document.createElement('div')).appendChild(document.createElement('button'));
stopButton.appendChild(document.createTextNode(TEXT_RESOURCES.stopLabel));
stopButton.accessKey = 's';
stopButton.onclick = function () {
video.currentTime = 0;
video.play();
video.pause();
toggleButton.__update__(paused = true);
};
var backButton = e.appendChild(document.createElement('div')).appendChild(document.createElement('button'));
backButton.appendChild(document.createTextNode(TEXT_RESOURCES.backLabel));
backButton.accessKey = 'b';
backButton.onclick = function () {
video.currentTime -= 10;
video.play();
toggleButton.__update__(paused = false);
};
var skipButton = e.appendChild(document.createElement('div')).appendChild(document.createElement('button'));
skipButton.appendChild(document.createTextNode(TEXT_RESOURCES.skipLabel));
skipButton.accessKey = 'f';
skipButton.onclick = function () {
video.currentTime += 10;
video.play();
toggleButton.__update__(paused = false);
};
var volDnButton = e.appendChild(document.createElement('div')).appendChild(document.createElement('button'));
volDnButton.appendChild(document.createTextNode(TEXT_RESOURCES.volDnLabel));
volDnButton.accessKey = 'n';
volDnButton.onclick = function () {
video.volume -= .1;
};
var volUpButton = e.appendChild(document.createElement('div')).appendChild(document.createElement('button'));
volUpButton.appendChild(document.createTextNode(TEXT_RESOURCES.volUpLabel));
volUpButton.accessKey = 'u';
volUpButton.onclick = function () {
video.volume += .1;
};
var formatTime = function (t) {
var m = parseInt(t / 60);
var s = parseInt(t % 60);
return TEXT_RESOURCES.timeFormat.replace('%m', m).replace('%s', s);
};
var timeDisplay = e.appendChild(document.createElement('div')).appendChild(document.createElement('span'));
timeDisplay.appendChild(document.createTextNode(formatTime(0)));
video.addEventListener('timeupdate', function (e) {
timeDisplay.firstChild.nodeValue = formatTime(e.target.currentTime);
}, false);
video.addEventListener('ended', function (e) {
toggleButton.__update__(paused = true);
}, false);
video.addEventListener('play', function (e) {
toggleButton.disabled = false;
stopButton.disabled = false;
backButton.disabled = false;
skipButton.disabled = false;
}, false);
video.addEventListener('pause', function (e) {
if (paused) { // paused by user (not for extended descriptions)
return;
}
toggleButton.disabled = true;
stopButton.disabled = true;
backButton.disabled = true;
skipButton.disabled = true;
}, false);
};
var createVDDisplays = function (video) {
var e1 = document.createElement('div');
e1.className = 'vd-descriptions';
e1.appendChild(document.createElement('span'));
video.__descriptions__ = video.parentNode.appendChild(e1);
var e2 = document.createElement('div');
e2.className = 'vd-captions';
e2.appendChild(document.createElement('span'));
video.__captions__ = video.parentNode.appendChild(e2);
var e1opt = document.createElement('div');
e1opt.className = 'vd-descriptions-config';
var l1 = document.createElement('label');
var c1 = document.createElement('input');
c1.type = 'checkbox';
c1.onclick = function () {
if (this.checked) {
video.__descriptions__.className = [video.__descriptions__.className, 'vd-show'].join(' ');
}
else {
video.__descriptions__.className = video.__descriptions__.className.replace(/\bvd-show\b/g, '');
}
};
l1.appendChild(c1);
l1.appendChild(document.createTextNode(TEXT_RESOURCES.showTextLabel));
e1opt.appendChild(l1);
video.parentNode.appendChild(e1opt);
}
var fixVideoElement = function (video) {
var container = document.createElement('div');
container.className = 'vd-container';
container.appendChild(video.parentNode.replaceChild(container, video));
createVDControls(video);
createVDDisplays(video);
};
var videoElems = document.getElementsByTagName('video');
for (var i = 0; i < videoElems.length; ++i) {
var v = videoElems[i];
fixVideoElement(v);
var trackElems = v.getElementsByTagName('track');
for (var j = 0; j < trackElems.length; ++j) {
var t = trackElems[j];
if (t.kind == 'descriptions' || t.kind == 'captions' || t.kind == 'metadata' && (t.dataset.kind == 'descriptions' || t.dataset.kind == 'captions')) {
t.track.__kind__ = t.dataset.kind || t.kind;
loadCues(t, v);
}
}
}
})();