Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Enrique Barra committed Mar 18, 2013
1 parent fc867c2 commit 6e14539
Show file tree
Hide file tree
Showing 9 changed files with 2,573 additions and 0 deletions.
72 changes: 72 additions & 0 deletions css/navigation.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
body {
min-width: 836px;
}
#video {
min-width: 455px;
padding: 0;
margin: 0;
}
#controls {
margin-top:5px;
}
#button {
display: inline;
}
#play {
background:url(../images/buttons.png) no-repeat top left;
border:none;
height: 30px;
width: 30px;
padding: 5px;
float: left;
display: inline-block;
}
#positionview {
float: left;
width: 80%;
display: inline;
}
#transportbar {
height: 15px;
width: 100%;
border: 2px solid black;
}
#position {
background: #D7BC28;
height: 15px;
width: 0px;
display: block;
}
#segments {
width: 100%;
margin: 0;
padding: 0;
border: 2px solid;
border-top: none;
position: relative;
background: none;
height: 10px;
list-style-type: none;
}
.segment {
border-right: 1px solid;
background: none;
height: 10px;
float: left;
cursor: pointer;
position: absolute;
}
.selector {
width: 100%;
height: 100%;
}
#time {
position: relative;
float: right;
}
#keys {
list-style-type: none;
}
#keys span {
text-transform: uppercase;
}
Binary file added images/0.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/buttons.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
51 changes: 51 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@

<!DOCTYPE html>
<html lang="en">
<head>
<title>Navigation using WebVTT</title>
<link href="css/navigation.css" type="text/css" rel="stylesheet"/>
</head>
<body>
<div role="application">
<div id="videoBox" style="float:left; width:58%;">
<h4>HTML5 video accessibility and the WebVTT file format</h4>
<video poster="videos/webvtt_talk.png" style="width:100%" preload="metadata">
<source src="videos/webvtt_talk.webm">
<source src="videos/webvtt_talk.mp4">
<track id="nav" src="videos/webvtt_talk_navigation.vtt" kind="chapters" srclang="en"></track>
<track id="cc" src="videos/webvtt_talk_captions.vtt" kind="captions" label="captions" srclang="en" default></track>
</video>
<div id="controls">
<div id="button"><input id="play" type="image" src="images/0.gif" alt="Play video"></div>
<div id="positionview">
<div id="transportbar">
<div id="position"></div>
</div>
<ul id="segments" title="chapter navigation" aria-describedby="keys"></ul>
</div>
<div id="time">
<span id="curTime">00:00</span>/<span id="duration">00:00</span>
</div>
</div>
<div style="display: block; clear: both;"></div>
<ul id="keys">
<li><span>space</span> = play / pause toggle</li>
<li><span>enter</span> = navigate to chapter</li>
<li><span>tab</span> = navigate chapters</li>
<li><span>ctl-alt-downarrow</span> = navigate text elements</li>
</ul>
</div>
<div id="transcriptBox" style="width:40%; float:right;">
<h4>Navigation to specific time points.</h4>
<p style="font-size:small;">
Click on link to navigate to video fragment. Press space to toggle video play/pause.
</p>
<div id="navigation"
style="padding:5px; background-color:#FAF9F8; border:2px black solid;">
<ul id="chapters">
</ul>
</div>
</div>
<script type="text/javascript" src="js/navigation.js"></script>
</body>
</html>
247 changes: 247 additions & 0 deletions js/navigation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
var video, track;
var cues = [];
var xhr;

// get video element, track, and duration element
video = document.getElementsByTagName('video')[0];
track = video.querySelectorAll('track')[0];
duration = document.getElementById('duration');

// display duration and chapters once video is loaded
video.addEventListener("loadedmetadata", init, false);
if (video.readyState >= video.HAVE_METADATA) {
init.apply(video); // missed the event
}

// update transport bar while playing
position = document.getElementById('position');
curTime = document.getElementById('curTime');
video.addEventListener("timeupdate", curTimeUpdate, false);

// play/pause button
play = document.getElementById('play');
play.addEventListener('click', togglePlay, false);

// click on transport bar sets playback position
transportbar = document.getElementById('transportbar');
transportbar.addEventListener("click", seek, false);

// pause video when current chapter is finished
video.addEventListener("timeupdate", endChapter, false);


// display duration and chapters
function init(evt) {
// update duration display
duration.innerHTML = video.duration.toFixed(2);

// grab chapters out of <track>
retrieve(track.getAttribute('src'));

// display chapters in list and transport bar
displayChapters();
paintChapterbar();
}

// pause/play button
function togglePlay() {
if (video.paused == false) {
video.pause();
play.style.backgroundPosition = '0 0';
} else {
video.play();
play.style.backgroundPosition = '0 -75px';
}
}

// capture onkeydown on the navigation to allow space bar to toggle play/pause
function videoPlayPause(evt) {
if (evt.keyCode == "32") { // space bar
togglePlay();
}
if (evt.keyCode == "13") { // enter key
seekChapter(this.getAttribute('data-chapter'));
// stop event from bubbling
evt = evt||event; /* get IE event ( not passed ) */
evt.stopPropagation? evt.stopPropagation() : evt.cancelBubble = true;
}
}

// update transport bar time display
function curTimeUpdate(evt) {
var bar_width = document.getElementById('positionview').offsetWidth;
curTime.innerHTML = video.currentTime.toFixed(2);
position.style.width = Math.round(bar_width*video.currentTime/video.duration) + "px";
}

// seek on transport bar
function seek(evt) {
var bar_width = document.getElementById('positionview').offsetWidth;
var clickpos = evt.pageX - this.offsetLeft;
var clickpct = clickpos / bar_width;
video.currentTime = clickpct * video.duration;
// clear chapter selection
for (i = 0; i < cues.length; i++) {
var segid = "segment" + i;
var segment = document.getElementById(segid);
segment.style.backgroundColor = "";
}
}

// seek chapters
function seekChapter(chapter) {
if (chapter) {
video.currentTime = parseFloat(cues[chapter].start);
for (i = 0; i < cues.length; i++) {
var segid = "segment" + i;
var segment = document.getElementById(segid);
if (i == parseInt(chapter)) {
segment.style.backgroundColor = "green";
} else {
segment.style.backgroundColor = "";
}
}
}
}

// pause on chapter end
function endChapter(evt) {
var curChapter = video.getAttribute('data-chapter');
if (video.currentTime >= cues[curChapter] && !video.paused) togglePlay();
}

// retrieve chapters via xhr and process them
function retrieve(url) {
xhr = new XMLHttpRequest();
if (xhr != null) {
xhr.open("GET", url, false /* sync */);
xhr.setRequestHeader('Content-Type', 'text/text; charset=utf-8');

xhr.onreadystatechange = function() {
if (xhr.readyState == 4 /* complete */) {
if (xhr.status != 200) {
alert('Unable to retrieve file.');
} else {
cues = parseWebVTT(xhr.responseText);
}
}
}
xhr.send();
} else {
alert('Error retrieving file.');
}
}

// display chapter list on screen
function displayChapters() {
// create navigation list on right
var chapters = document.getElementById('chapters');
for (i=0; i < cues.length; i++) {
var item = document.createElement('li');
item.id = cues[i].id;
var link = document.createElement('a');
link.href = '#videoBox';
link.innerHTML = cues[i].id + ': ' + cues[i].content;
link.setAttribute('data-chapter', i);
link.addEventListener('keydown', videoPlayPause, false);
link.onclick = function () {
seekChapter(this.getAttribute('data-chapter'));
}
item.appendChild(link);
chapters.appendChild(item);
}
}

// paint chapters into chapter bar
function paintChapterbar() {
// create time segments under transportbar
var duration = parseFloat(video.duration);
var segments = document.getElementById('segments');
var bar_width = document.getElementById('positionview').offsetWidth;
// TODO: bug - the offsetWidth is unknown at load state
var_width = 558;

var perc = bar_width / duration;

for (i=0; i < cues.length && duration > 0; i++) {
var segment = document.createElement('li');
var start = parseFloat(cues[i].start);
var end = parseFloat(cues[i].end);
segment.id = "segment" + i;
segment.className = "segment";
segment.title = cues[i].content + "\npress enter to navigate, space to toggle play";
segment.style.left = Math.round((start * perc) - 0.5) + 'px';
segment.style.width = Math.round((end - start) * perc) + 'px';
segment.setAttribute('data-chapter', i);
segment.setAttribute('tabindex', '0');
segment.setAttribute('role', 'button');
segment.onclick = function () {
seekChapter(this.getAttribute('data-chapter'));
}
segment.addEventListener('keydown', videoPlayPause, false);
segments.appendChild(segment);
}
}

// Function to parse webvtt file
function parseWebVTT(data) {
var srt;
// check WEBVTT identifier
if (data.substring(0,6) != "WEBVTT") {
alert("Missing WEBVTT header: Not a WebVTT file - trying SRT.");
srt = data;
} else {
// remove WEBVTT identifier line
srt = data.split('\n').slice(1).join('\n');
}

// clean up string a bit
srt = srt.replace(/\r+/g, ''); // remove dos newlines
srt = srt.replace(/^\s+|\s+$/g, ''); // trim white space start and end

// srt = srt.replace(/<[a-zA-Z\/][^>]*>/g, ''); // remove all html tags for security reasons

// parse cues
var cuelist = srt.split('\n\n');
for (i = 0; i < cuelist.length; i++) {
var cue = cuelist[i];
var content = "", start, end, id = "";
var s = cue.split(/\n/);
var t = 0;
// is there a cue identifier present?
if (!s[t].match(/(\d+):(\d+):(\d+)/)) {
// cue identifier present
id = s[0];
t = 1;
}
// is the next line the time string
if (!s[t].match(/(\d+):(\d+):(\d+)/)) {
// file format error: next cue
continue;
}
// parse time string
var m = s[t].match(/(\d+):(\d+):(\d+)(?:.(\d+))?\s*--?>\s*(\d+):(\d+):(\d+)(?:.(\d+))?/);
if (m) {
start =
(parseInt(m[1], 10) * 60 * 60) +
(parseInt(m[2], 10) * 60) +
(parseInt(m[3], 10)) +
(parseInt(m[4], 10) / 1000);
end =
(parseInt(m[5], 10) * 60 * 60) +
(parseInt(m[6], 10) * 60) +
(parseInt(m[7], 10)) +
(parseInt(m[8], 10) / 1000);
} else {
// Unrecognized timestring: next cue
continue;
}

// concatenate text lines to html text
content = s.slice(t+1).join("<br>");

// add parsed cue
cues.push({id: id, start: start, end: end, content: content});
}
return cues;
}
Binary file added videos/webvtt_talk.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added videos/webvtt_talk.webm
Binary file not shown.
Loading

0 comments on commit 6e14539

Please sign in to comment.