<html lang="en">
<head>
<meta charset=utf-8>
<title>Video player with clickable transcript</title>
</head>
<body>
<section id="all">
<h1>Using the track API to extract the content of webVTT files in <code><track></code> elements</h1>
<p>Click on the buttons under the video to extract the english or german subtitles
</p>
<p>Look at the HTML and JS code.</p>
<p>
<button disabled
id="buttonEnglish"
onclick="loadTranscript('en');">
Display English transcript
</button>
<button disabled
id="buttonDeutsch"
onclick="loadTranscript('de');">
Display Deutsch transcript
</button>
</p>
<video id="myVideo" preload="metadata" controls crossOrigin="anonymous">
<source src="https://mainline.i3s.unice.fr/mooc/elephants-dream-medium.mp4" type="video/mp4">
<source src="https://mainline.i3s.unice.fr/mooc/elephants-dream-medium.webm" type="video/webm">
<track label="English subtitles" kind="subtitles" srclang="en" src="https://mainline.i3s.unice.fr/mooc/elephants-dream-subtitles-en.vtt" >
<track label="Deutsch subtitles" kind="subtitles" srclang="de" src="https://mainline.i3s.unice.fr/mooc/elephants-dream-subtitles-de.vtt" default>
<track label="English chapters" kind="chapters" srclang="en" src="https://mainline.i3s.unice.fr/mooc/elephants-dream-chapters-en.vtt">
</video>
<div id="transcript"></div>
</section>
</body>
</html>
#all {
background-color: lightgrey;
border-radius:10px;
padding: 20px;
border:1px solid;
display:inline-block;
/*height:500px;*/
margin:30px;
width:90%;
}
.cues {
color:blue;
}
.cues:hover {
text-decoration: underline;
}
.cues.current {
color:black;
font-weight: bold;
}
#myVideo {
display: block;
float : left;
margin-right: 2.85714%;
width: 65.71429%;
background-color: black;
position: relative;
}
#transcript {
padding: 10px;
border:1px solid;
float: left;
max-height: 225px;
overflow: auto;
width: 25%;
margin: 0;
font-size: 14px;
list-style: none;
}
var video, transcriptDiv;
var tracks, trackElems, tracksURLs = [];
var buttonEnglish, buttonDeutsch;
window.onload = function() {
console.log("init");
// when the page is loaded
video = document.querySelector("#myVideo");
transcriptDiv = document.querySelector("#transcript");
// The tracks as HTML elements
trackElems = document.querySelectorAll("track");
for(var i = 0; i < trackElems.length; i++) {
var currentTrackElem = trackElems[i];
tracksURLs[i] = currentTrackElem.src;
}
buttonEnglish = document.querySelector("#buttonEnglish");
buttonDeutsch = document.querySelector("#buttonDeutsch");
// we enable the buttons and show transcript
buttonEnglish.disabled = false;
buttonDeutsch.disabled = false;
// The tracks as JS objects
tracks = video.textTracks;
};
function loadTranscript(lang) {
// clear current transcript
clearTranscriptDiv();
// set all track mode to disabled. We will only activate the
// one whose content will be displayed as transcript
disableAllTracks();
// Locate the track with language = lang
for(var i = 0; i < tracks.length; i++) {
// current track
var track = tracks[i];
var trackAsHtmlElem = trackElems[i];
if((track.language === lang) && (track.kind !== "chapters")) {
track.mode="showing";
if(trackAsHtmlElem.readyState === 2) {
// the track has already been loaded
displayCues(track);
} else {
displayCuesAfterTrackLoaded(trackAsHtmlElem, track);
}
/* FOR FIREFOX....
track.addEventListener("cuechange", function(e) {
var cue = this.activeCues[0];
console.log("cue change");
var transcriptText = document.getElementById(cue.id);
transcriptText.classList.add("current");
});
*/
}
}
}
function displayCuesAfterTrackLoaded(trackElem, track) {
// Create a listener that will be called only when the track has
// been loaded
trackElem.addEventListener('load', function(e) {
console.log("track loaded");
displayCues(track);
});
}
function disableAllTracks() {
for(var i = 0; i < tracks.length; i++)
tracks[i].mode = "disabled";
}
function displayCues(track) {
var cues = track.cues;
//append all the subtitle texts to
for(var i=0, len = cues.length; i < len; i++) {
var cue = cues[i];
addCueListeners(cue);
var voices = getVoices(cue.text);
var transText="";
if (voices.length > 0) {
for (var j = 0; j < voices.length; j++) { // how many voices ?
transText += voices[j].voice + ': ' + removeHTML(voices[j].text);
}
} else
transText = cue.text; // not a voice text
var clickableTransText = "<li class='cues' id=" + cue.id + " onclick='jumpTo(" + cue.startTime + ");'" + ">" + transText + "</li>";
addToTranscriptDiv(clickableTransText);
}
}
function getVoices(speech) { // takes a text content and check if there are voices
var voices = []; // inside
var pos = speech.indexOf('<v'); // voices are like <v michel> ....
while (pos != -1) {
endVoice = speech.indexOf('>');
var voice = speech.substring(pos + 2, endVoice).trim();
var endSpeech = speech.indexOf('</v>');
var text = speech.substring(endVoice + 1, endSpeech);
voices.push({
'voice': voice,
'text': text
});
speech = speech.substring(endSpeech + 4);
pos = speech.indexOf('<v');
}
return voices;
}
function removeHTML(text) {
var div = document.createElement('div');
div.innerHTML = text;
return div.textContent || div.innerText || '';
}
function jumpTo(time) {
video.currentTime = time;
video.play();
}
function clearTranscriptDiv() {
transcriptDiv.innerHTML = "";
}
function addToTranscriptDiv(htmlText) {
transcriptDiv.innerHTML += htmlText;
}
function addCueListeners(cue) {
cue.onenter = function(){
console.log('enter id=' + this.id);
var transcriptText = document.getElementById(this.id);
transcriptText.classList.add("current");
};
cue.onexit = function(){
console.log('exit id=' + cue.id);
var transcriptText = document.getElementById(this.id); transcriptText.classList.remove("current");
};
}
Output
300px
You can jump to the latest bin by adding /latest
to your URL
Keyboard Shortcuts
Shortcut | Action |
---|---|
ctrl + [num] | Toggle nth panel |
ctrl + 0 | Close focused panel |
ctrl + enter | Re-render output. If console visible: run JS in console |
Ctrl + l | Clear the console |
ctrl + / | Toggle comment on selected lines |
ctrl + ] | Indents selected lines |
ctrl + [ | Unindents selected lines |
tab | Code complete & Emmet expand |
ctrl + shift + L | Beautify code in active panel |
ctrl + s | Save & lock current Bin from further changes |
ctrl + shift + s | Open the share options |
ctrl + y | Archive Bin |
Complete list of JS Bin shortcuts |
JS Bin URLs
URL | Action |
---|---|
/ | Show the full rendered output. This content will update in real time as it's updated from the /edit url. |
/edit | Edit the current bin |
/watch | Follow a Code Casting session |
/embed | Create an embeddable version of the bin |
/latest | Load the very latest bin (/latest goes in place of the revision) |
/[username]/last | View the last edited bin for this user |
/[username]/last/edit | Edit the last edited bin for this user |
/[username]/last/watch | Follow the Code Casting session for the latest bin for this user |
/quiet | Remove analytics and edit button from rendered output |
.js | Load only the JavaScript for a bin |
.css | Load only the CSS for a bin |
Except for username prefixed urls, the url may start with http://jsbin.com/abc and the url fragments can be added to the url to view it differently. |