Friday, April 8, 2016

HTML5 - A player with a clickable transcript

Example 1: a video player with clickable transcript - reading WebVTT file content at once

A few words about the set of five examples presented in this chapter: the code of the examples is larger than usual, but each example integrates blocks of code already presented and detailed in the previous lessons.

CREATING AN ACCESSIBLE PLAYER WITH A CLICKABLE TRANSCRIPT OF THE VIDEO PRESENTATION

It might be interesting to read the content of a track before playing the video. This is what the edX video player does: it reads a single subtitle file and displays it as a transcript on the right. In the transcript you can click on a sentence to make the video jump to the corresponding location. We will see how we can do this using the track API.
edX video player with clickable transcript on the right

EXAMPLE 1: READ THE WEBVTT FILE AT ONCE USING THE TRACK API AND MAKE A CLICKABLE TRANSCRIPT

Here we decided to code something similar, except that this time we can choose the track. In the example we have English and German subtitles, and also another track that contains the chapter descriptions (more on that later). By clicking on a button we display the transcript on the right. Like the edX player, we can click on any sentence in order to force the video to jump to the corresponding location. While the video is playing, the current text is highlighted.
Some important things here:
    1. Browsers do not load all the tracks at the same time, and the way they decide when and which track to load differs from one browser to another. So, when we click on a button to choose the track to display, we need to enforce the loading of the track if it has not been loaded yet.
    2. When a track file is loaded, then we iterate on the different cues and generate the transcript as a set of<li>...</li> elements. One <li> per cue/sentence. 
    3. We set the id attribute of the <li> with the cue.id value. In this way, when we click on a <li> we can get its id and find the corresponding cue start time, and make the video jump to this time location.
    4. We add to each cue an enter and an exit listener. These will be useful for highlighting the current cue. Note that these listeners are not yet supported by FireFox (you can use a cuechange event listener on aTextTrack instead - the source code for FireFox is commented in the example).
edX like video player with clickable transcript
HTML code:
  1. <section id="all">
  2. <button disabled id="buttonEnglish"
  3.          onclick="loadTranscript('en');">
  4.     Display English transcript
  5.  </button>
  6. <button disabled id="buttonDeutsch"
  7.          onclick="loadTranscript('de');">
  8.     Display Deutsch transcript
  9. </button>
  10. </p>
  11. <video id="myVideo" preload="metadata" controls crossOrigin="anonymous">
  12.      <source src="http://...../elephants-dream-medium.mp4"
  13.              type="video/mp4">
  14.      <source src="http://...../elephants-dream-medium.webm"
  15.              type="video/webm">
  16.      <track label="English subtitles"
  17.             kind="subtitles"
  18.             srclang="en"
  19.             src="http://...../elephants-dream-subtitles-en.vtt" >
  20.      <track label="Deutsch subtitles"
  21.             kind="subtitles"
  22.             srclang="de"
  23.             src="http://...../elephants-dream-subtitles-de.vtt"
  24.             default>
  25.      <track label="English chapters"
  26.             kind="chapters"
  27.             srclang="en"
  28.             src="http://...../elephants-dream-chapters-en.vtt">
  29. </video>
  30. <div id="transcript"></div>
  31. </section>
CSS code:
  1. #all {
  2.    background-color: lightgrey;
  3.    border-radius:10px;
  4.    padding: 20px;
  5.    border:1px solid;
  6.    display:inline-block;
  7.    margin:30px;
  8.    width:90%;
  9. }
  10.  
  11. .cues {
  12.    color:blue;
  13. }
  14.  
  15. .cues:hover {
  16.    text-decoration: underline;
  17. }
  18.  
  19. .cues.current {
  20.    color:black;
  21.    font-weight: bold;
  22. }
  23.  
  24. #myVideo {
  25.    display: block;
  26.    float : left;
  27.    margin-right: 3%;
  28.    width: 66%;
  29.    background-color: black;
  30.    position: relative;
  31. }
  32.  
  33. #transcript {
  34.    padding: 10px;
  35.    border:1px solid;
  36.    float: left;
  37.    max-height: 225px;
  38.    overflow: auto;
  39.    width: 25%;
  40.    margin: 0;
  41.    font-size: 14px;
  42.    list-style: none;
  43. }
JavaScript code:
  1. var video, transcriptDiv;
  2. // TextTracks, html tracks, urls of tracks
  3. var tracks, trackElems, tracksURLs = []; 
  4. var buttonEnglish, buttonDeutsch;
  5.  
  6. window.onload = function() {
  7.    console.log("init");
  8.    // when the page is loaded, get the different DOM nodes
  9.    // we're going to work with
  10.    video = document.querySelector("#myVideo");
  11.    transcriptDiv = document.querySelector("#transcript");
  12.    // The tracks as HTML elements
  13.    trackElems = document.querySelectorAll("track");
  14.    // Get the URLs of the vtt files
  15.    for(var i = 0; i < trackElems.length; i++) {
  16.       var currentTrackElem = trackElems[i];
  17.       tracksURLs[i] = currentTrackElem.src;
  18.    }
  19.    buttonEnglish = document.querySelector("#buttonEnglish");
  20.    buttonDeutsch = document.querySelector("#buttonDeutsch");
  21.    // we enable the buttons only in this load callback,
  22.    // we cannot click before the video is in the DOM
  23.    buttonEnglish.disabled = false;
  24.    buttonDeutsch.disabled = false;
  25.    // The tracks as TextTrack JS objects
  26.    tracks = video.textTracks;
  27. };
  28.  
  29. function loadTranscript(lang) {
  30.   // Called when a button is clicked
  31.   // clear current transcript
  32.   clearTranscriptDiv();
  33.   // set all track modes to disabled. We will only activate the
  34.   // one whose content will be displayed as transcript
  35.   disableAllTracks();
  36.   // Locate the track with language = lang
  37.   for(var i = 0; i < tracks.length; i++) {
  38.     // current track
  39.     var track = tracks[i];
  40.     var trackAsHtmlElem = trackElems[i];
  41.     // Only subtitles/captions are ok for this example...
  42.     if((track.language === lang) && (track.kind !== "chapters")) {
  43.        track.mode="showing";
  44.  
  45.        if(trackAsHtmlElem.readyState === 2) {
  46.           // the track has already been loaded
  47.           displayCues(track);
  48.        } else {
  49.           displayCuesAfterTrackLoaded(trackAsHtmlElem, track);
  50.        }
  51.        /* Fallback for FireFox that still does not implement cue enter and exit events
  52.          track.addEventListener("cuechange", function(e) {
  53.              var cue = this.activeCues[0];
  54.              console.log("cue change");
  55.              var transcriptText = document.getElementById(cue.id);
  56.              transcriptText.classList.add("current");
  57.          });
  58.       */
  59.     }
  60.   }
  61. }
  62. function displayCuesAfterTrackLoaded(trackElem, track) {
  63.   // Create a listener that will only be called once the track has
  64.   // been loaded. We cannot display the transcript before
  65.   // the track is loaded
  66.    trackElem.addEventListener('load', function(e) {
  67.       console.log("track loaded");
  68.       displayCues(track);
  69.    });
  70. }
  71. function disableAllTracks() {
  72.   for(var i = 0; i < tracks.length; i++)
  73.      // the track mode is important: disabled tracks do not fire events
  74.      tracks[i].mode = "disabled"
  75. }
  76.  
  77. function displayCues(track) 
  78.    // displays the transcript of a TextTrack
  79.    var cues = track.cues;
  80.    // iterate on all cues of the current track
  81.    for(var i=0, len = cues.length; i < len; i++) {
  82.       // current cue, also add enter and exit listeners to it
  83.       var cue = cues[i];
  84.       addCueListeners(cue);
  85.  
  86.       // Test if the cue content is a voice <v speaker>....</v>
  87.       var voices = getVoices(cue.text);
  88.       var transText="";
  89.       if (voices.length > 0) {
  90.          for (var j = 0; j < voices.length; j++) { // how many voices?
  91.             transText += voices[j].voice + ': ' + removeHTML(voices[j].text);
  92.          }
  93.       } else
  94.          transText = cue.text; // not a voice text
  95.       var clickableTransText = "<li class='cues' id=" + cue.id
  96.                                + " onclick='jumpTo("
  97.                                + cue.startTime + ");'" + ">"
  98.                                + transText + "</li>";
  99.  
  100.       addToTranscriptDiv(clickableTransText);
  101.    }
  102. }
  103.  
  104. function getVoices(speech) { 
  105.    // takes a text content and check if there are voices
  106.    var voices = []; // inside
  107.    var pos = speech.indexOf('<v'); // voices are like <v Michel> ....
  108.    while (pos != -1) {
  109.       endVoice = speech.indexOf('>');
  110.       var voice = speech.substring(pos + 2, endVoice).trim();
  111.       var endSpeech = speech.indexOf('</v>');
  112.       var text = speech.substring(endVoice + 1, endSpeech);
  113.       voices.push({
  114.           'voice': voice,
  115.           'text': text
  116.       });
  117.       speech = speech.substring(endSpeech + 4);
  118.       pos = speech.indexOf('<v');
  119.   }
  120.   return voices;
  121. }
  122.  
  123. function removeHTML(text) {
  124.   var div = document.createElement('div');
  125.   div.innerHTML = text;
  126.   return div.textContent || div.innerText || '';
  127. }
  128. function jumpTo(time) {
  129.   // Make the video jump at the time position + force play
  130.   // if it was not playing
  131.   video.currentTime = time;
  132.   video.play();
  133. }
  134.  
  135. function clearTranscriptDiv() {
  136.   transcriptDiv.innerHTML = "";
  137. }
  138.  
  139. function addToTranscriptDiv(htmlText) {
  140.   transcriptDiv.innerHTML += htmlText;
  141. }
  142.  
  143. function addCueListeners(cue) {
  144.   cue.onenter = function(){
  145.      // Highlight current cue transcript by adding the
  146.      // cue.current CSS class
  147.      console.log('enter id=' + this.id);
  148.      var transcriptText = document.getElementById(this.id);
  149.      transcriptText.classList.add("current");
  150. };
  151.  
  152. cue.onexit = function(){
  153.    console.log('exit id=' + cue.id);
  154.    var transcriptText = document.getElementById(this.id);
  155.    transcriptText.classList.remove("current");
  156. };
  157. } // end of addCueListeners...

EXAMPLE 2: GETTING A WEBVTT FILE USING AJAX/XHR2 AND PARSE IT MANUALLY

This is an old example written in 2012 at a time when the track API was not supported by browsers. It downloads WebVTT files using Ajax and parses it "by hand". Notice the complexity of the code, compared to example 1 that uses the track API instead. We give this example as is. Sometimes, bypassing all APIs can be a valuable solution, especially when support for the track API was very sporadic, as was the case in 2012...
screenshot of JsBin example: video on top and two buttons "english" and "german" at bottom for extracting the track contents in english or grman
This example, adapted from an example from (now offline) dev.opera.com, uses some JavaScript code that takes a WebVTT subtitle (or caption) file as an argument, parses it, and displays the text on screen, in an element with an id of transcript.
Extract from HTML code:
  1. ...
  2. <video preload="metadata" controls >
  3.     <source src="https://..../elephants-dream-medium.mp4" type="video/mp4">
  4.     <source src="https://..../elephants-dream-medium.webm" type="video/webm">
  5.     <track label="English subtitles" kind="subtitles" srclang="en"
  6.            src="https://..../elephants-dream-subtitles-en.vtt" default>
  7.     <track label="Deutsch subtitles" kind="subtitles" srclang="de"
  8.            src="https://..../elephants-dream-subtitles-de.vtt">
  9.     <track label="English chapters" kind="chapters" srclang="en"
  10.            src="https://..../elephants-dream-chapters-en.vtt">
  11. </video>
  12.  ...
  13.    <h3>Video Transcript</h3>
  14.    <button onclick="loadTranscript('en');">English</button>
  15.    <button onclick="loadTranscript('de');">Deutsch</button>
  16.     </div>
  17.     <div id="transcript"></div>
  18. ...
JavaScript code:
  1. // Transcript.js, by dev.opera.com
  2. function loadTranscript(lang) {
  3.    var url = "http://mainline.i3s.unice.fr/mooc/" +
  4.        'elephants-dream-subtitles-' + lang + '.vtt';
  5.    // Will download using Ajax + extract subtitles/captions   
  6.    loadTranscriptFile(url); 
  7. }
  8.  
  9. function loadTranscriptFile(webvttFileUrl) {
  10.    // Using Ajax/XHR2 (explained in detail in Week 3)
  11.    var reqTrans = new XMLHttpRequest();
  12.    reqTrans.open('GET', webvttFileUrl);
  13.    // callback, called only once the response is ready
  14.    reqTrans.onload = function(e) 
  15.        var pattern = /^([0-9]+)$/;
  16.        var patternTimecode = /^([0-9]{2}:[0-9]{2}:[0-9]{2}[,.]{1}[0-9]{3}) --\> ([0-9]
  17.                              {2}:[0-9]{2}:[0-9]{2}[,.]{1}[0-9]{3})(.*)$/;
  18.        var content = this.response; // content of the webVTT file
  19.        var lines = content.split(/\r?\n/); // Get an array of text lines
  20.        var transcript = '';
  21.        for (= 0; i < lines.length; i++) {
  22.          var identifier = pattern.exec(lines[i]);
  23.          // is there an id for this line, if it is, go to next line
  24.          if (identifier) 
  25.            i++;
  26.            var timecode = patternTimecode.exec(lines[i]); 
  27.            // is the current line a timecode?
  28.            if (timecode && i < lines.length) {      
  29.               // if it is go to next line     
  30.               i++;
  31.               // it can only be a text line now
  32.               var text = lines[i];  
  33.                         
  34.               // is the text multiline?
  35.               while (lines[i] !== '' && i < lines.length) {   
  36.                  text = text + '\n' + lines[i];
  37.                  i++;
  38.               }
  39.               var transText = '';
  40.               var voices = getVoices(text);
  41.               // is the extracted text multi voices ? 
  42.               if (voices.length > 0) {
  43.                  // how many voices ?
  44.                  for (var j = 0; j < voices.length; j++) 
  45.                  transText += voices[j].voice + ': '
  46.                            + removeHTML(voices[j].text)
  47.                            + '<br />';
  48.               }
  49.           else 
  50.              // not a voice text
  51.              transText = removeHTML(text) + '<br />'
  52.          transcript += transText;
  53.        }
  54.      }
  55.      var oTrans = document.getElementById('transcript');
  56.      oTrans.innerHTML = transcript;
  57.    }
  58. };
  59.  reqTrans.send(); // send the Ajax request
  60. }
  61.  
  62. function getVoices(speech) {  // takes a text content and check if there are voices 
  63.   var voices = [];            // inside
  64.   var pos = speech.indexOf('<v'); // voices are like <v Michel> ....
  65.   while (pos != -1) {
  66.     endVoice = speech.indexOf('>');
  67.     var voice = speech.substring(pos + 2, endVoice).trim();
  68.     var endSpeech = speech.indexOf('</v>');
  69.     var text = speech.substring(endVoice + 1, endSpeech);
  70.     voices.push({
  71.        'voice': voice,
  72.        'text': text
  73.     });
  74.     speech = speech.substring(endSpeech + 4);
  75.     pos = speech.indexOf('<v');
  76.   }
  77.   return voices;
  78. }
  79.  
  80. function removeHTML(text) {
  81.   var div = document.createElement('div');
  82.   div.innerHTML = text;
  83.   return div.textContent || div.innerText || '';
  84. }

Example 2: showing video description while playing, listening to events, changing the mode of a track

Each track has a mode property (and a mode attribute) that can be: "disabled""hidden" or "showing". More than one track at a time can be in any of these states.  The difference between "hidden" and "disabled" is that hidden tracks can fire events (more on that at the end of the first example) whereas disabled tracks do not fire events.
Here is an example at JSBin that shows the use of the mode property, and basic usage of the cue events in order to capture the current subtitle/caption from JavaScript. You can change the mode of each track in the video element by clicking on buttons, to toggle the mode of each track. All tracks with mode="showing" or mode="hidden" will have the content of their cues displayed in real time in a small area below the video.
In the example below we have a WebVTT file containing the descriptions of a scene being played.
Example that shows how to toggle track modes and listen to events
Extract from HTML code:
  1. <html lang="en">
  2. ...
  3. <body onload="init();">
  4. ...
  5. <p>
  6.   <video id="myVideo" preload="metadata"
  7.         poster ="https://...../sintel.jpg"
  8.         crossorigin="anonymous"
  9.         controls="controls"
  10.         width="640" height="272">
  11.     <source src="https://...../sintel.mp4"
  12.             type="video/mp4" />
  13.     <source src="https://...../sintel.webm"
  14.             type="video/webm" />
  15.     <track src="https://...../sintel-captions.vtt"
  16.            kind="captions"
  17.            label="English Captions"
  18.            default/>
  19.     <track src="https://...../sintel-descriptions.vtt"
  20.            kind="descriptions"
  21.            label="Audio Descriptions" />
  22.     <track src="https://...../sintel-chapters.vtt"
  23.            kind="chapters"
  24.            label="Chapter Markers" />
  25.     <track src="https://...../sintel-thumbs.vtt"
  26.            kind="metadata"
  27.            label="Preview Thumbs" />
  28.   </video>
  29. </p>
  30. <p>
  31.    <div id="currentTrackStatuses"></div>
  32. <p>
  33. <p>
  34.    <div id="subtitlesCaptions"></div>
  35. </p>
  36. <p>
  37.    <button onclick="clearSubtitlesCaptions();">
  38.       Clear subtitles/captions log
  39.    </button>
  40. </p>
  41.  
  42. <p>Click one of these buttons to toggle the mode of each track:</p>
  43. <button onclick="toggleTrack(0);">
  44.      Toggle english caption track mode
  45.  </button>
  46.  <br>
  47. <button onclick="toggleTrack(1);">
  48.      Toggle audio description track mode
  49. </button>
  50. <br>
  51. <button onclick="toggleTrack(2);">
  52.      Toggle chapter track mode
  53. </button>
  54. <br>
  55. <button onclick="toggleTrack(3);">
  56.      Toggle preview thumbnail track modes
  57. </button>
  58.  
  59. </body>
  60. </html>
JavaScript code:
  1. var tracks, video, statusDiv, subtitlesCaptionsDiv;
  2.  
  3. function init() {
  4.    video = document.querySelector("#myVideo");
  5.    statusDiv = document.querySelector("#currentTrackStatuses");
  6.    subtitlesCaptionsDiv = document.querySelector("#subtitlesCaptions");
  7.    tracks = document.querySelectorAll("track");
  8.    video.addEventListener('loadedmetadata', function() {
  9.       console.log("metadata loaded");
  10.       // defines cue listeners for the active track; we can do this only after the video metadata have been loaded
  11.       for(var i=0; i<tracks.length; i++) {
  12.          var t = tracks[i].track;
  13.          if(t.mode === "showing") {
  14.             t.addEventListener('cuechange', logCue, false);
  15.          }
  16.        }
  17.        // display in a div the list of tracks and their status/mode value
  18.        displayTrackStatus();  
  19.     });
  20. }
  21.  
  22. function displayTrackStatus() {
  23.     // display the status / mode value of each track.
  24.     // In red if disabled, in green if showing
  25.     for(var i=0; i<tracks.length; i++) {
  26.        var t = tracks[i].track;
  27.        var mode = t.mode;
  28.        if(mode === "disabled") {
  29.           mode = "<span style='color:red'>" + t.mode + "</span>";
  30.        } else if(mode === "showing") {
  31.           mode = "<span style='color:green'>" + t.mode + "</span>";
  32.        }
  33.        appendToScrollableDiv(statusDiv, "track " ": " + t.label
  34.                                         + " " t.kind+" in "
  35.                                         + mode " mode");
  36.     }
  37. }
  38. function appendToScrollableDiv(div, text) {
  39.    // we've got two scrollable divs. This function
  40.    // appends text to the div passed as a parameter
  41.    // The div is scrollable (thanks to CSS overflow:auto)
  42.    var inner = div.innerHTML;
  43.    div.innerHTML = inner + text + "<br/>";
  44.    // Make it display the last line appended
  45.    div.scrollTop = div.scrollHeight;
  46. }
  47.  
  48. function clearDiv(div) {
  49.    div.innerHTML = '';
  50. }
  51.  
  52. function clearSubtitlesCaptions() {
  53.    clearDiv(subtitlesCaptionsDiv);
  54. }
  55. function toggleTrack(i) {
  56.    // toggles the mode of track i, removes the cue listener
  57.    // if its mode becomes "disabled"
  58.    // adds a cue listener if its mode was "disabled"
  59.    // and becomes "hidden"
  60.    var t = tracks[i].track;
  61.    switch (t.mode) {
  62.       case "disabled":
  63.          t.addEventListener('cuechange', logCue, false);
  64.          t.mode = "hidden";
  65.          break;
  66.       case "hidden":
  67.          t.mode = "showing";
  68.          break;
  69.       case "showing":
  70.          t.removeEventListener('cuechange', logCue, false);
  71.          t.mode = "disabled";
  72.          break;
  73.     }
  74.     // updates the status
  75.     clearDiv(statusDiv);
  76.     displayTrackStatus();
  77.     appendToScrollableDiv(statusDiv,"<br>" + t.label+" are now " +t.mode);
  78. }
  79.  
  80. function logCue() {
  81.    // callback for the cue event
  82.    if(this.activeCues && this.activeCues.length) {
  83.       var t = this.activeCues[0].text; // text of current cue
  84.       appendToScrollableDiv(subtitlesCaptionsDiv, "Active "
  85.                                           + this.kind " changed to: " t);
  86.    }
  87. }

Example 3: adding buttons for choosing the subtitle/caption track

You may have noticed that with many browsers, the standard implementation of the video element does not let the user choose the subtitle language...
Read this article by Ian Devlin about the current status of multiple WebVTT track supports by the different browsers, as at April 2015. Note that currently (in October 2015), neither Chrome nor FireFox offers a menu to choose the track to display. 
However, it's rather easy to implement this feature using the Track API.
Here is a simple example at JSBin: we added two buttons below the video that will enable/disable subtitles/captions and let you choose which track you like. 
Buttons for choosing the track/language under a standard video player
HTML code:
  1. ...
  2. <body onload="init()">
  3.  ...
  4. <video id="myVideo" preload="metadata" controls crossOrigin="anonymous" >
  5.      <source src="http://...../elephants-dream-medium.mp4"
  6.              type="video/mp4">
  7.      <source src="http://...../elephants-dream-medium.webm"
  8.              type="video/webm">
  9.      <track  label="English subtitles"
  10.              kind="subtitles"
  11.              srclang="en"
  12.              src="http://...../elephants-dream-subtitles-en.vtt"
  13.              default>
  14.      <track  label="Deutsch subtitles"
  15.              kind="subtitles"
  16.              srclang="de"
  17.              src="http://...../elephants-dream-subtitles-de.vtt">
  18.      <track  label="English chapters"
  19.              kind="chapters"
  20.              srclang="en"
  21.              src="http://...../elephants-dream-chapters-en.vtt">
  22. </video>
  23. <h3>Current track: <span id="currentLang"></span></h3>
  24. <div id="langButtonDiv"></div>
  25. </section>
  26. ...
JavaScript code:
  1. var langButtonDiv, currentLangSpan, video;
  2.  
  3. function init() {
  4.    langButtonDiv = document.querySelector("#langButtonDiv");
  5.    currentLangSpan = document.querySelector("#currentLang");
  6.    video = document.querySelector("#myVideo");
  7.    console.log("Number of tracks = "
  8.                + video.textTracks.length);
  9.    // Updates the display of the current track activated
  10.    currentLangSpan.innerHTML = activeTrack();
  11.    // Build the buttons for choosing a track
  12.    buildButtons();
  13. }
  14.  
  15. function activeTrack() {
  16.    for (var i = 0; i < video.textTracks.length; i++) {
  17.       if(video.textTracks[i].mode === 'showing') {
  18.          return video.textTracks[i].label + " ("
  19.                 + video.textTracks[i].language + ")";
  20.       }
  21.    }
  22.    return "no subtitles/caption selected";
  23. }
  24.  
  25. function buildButtons() {
  26.    if (video.textTracks) { // if the video contains track elements
  27.       // For each track, create a button
  28.       for (var i = 0; i < video.textTracks.length; i++) {
  29.          // We create buttons only for the caption and subtitle tracks
  30.          var track = video.textTracks[i];
  31.          if((track.kind !== "subtitles") && (track.kind !== "captions"))
  32.             continue;
  33.          // create a button for track number i         
  34.          createButton(video.textTracks[i]); 
  35.       }
  36.    }
  37. }
  38. function createButton(track) {
  39.    // Create a button
  40.    var b = document.createElement("button");
  41.    b.value=track.label;
  42.    // use the lang attribute of the button to keep trace of the
  43.    // associated track language. Will be useful in the click listener
  44.    b.setAttribute("lang", track.language); 
  45.    b.addEventListener('click', function(e) {
  46.      // Check which track is the track with the language we're looking for
  47.      // Get the value of the lang attribute of the clicked button
  48.      var lang = this.getAttribute('lang'); 
  49.      for (var i = 0; i < video.textTracks.length; i++) {
  50.        if (video.textTracks[i].language == lang) {
  51.           video.textTracks[i].mode = 'showing';
  52.        } else {
  53.           video.textTracks[i].mode = 'hidden';
  54.        }
  55.      }
  56.      // Updates the span so that it displays the new active track
  57.     currentLangSpan.innerHTML = activeTrack();
  58.   });
  59.   // Creates a label inside the button
  60.   b.appendChild(document.createTextNode(track.label));
  61.   // Add the button to a div at the end of the HTML document
  62.   langButtonDiv.appendChild(b);
  63. }
External resourceThere is an online tutorial at MDN that goes much further in terms of styling and integrating a "CC" button. If you are interested in building a complete custom video player, you might take a look.

Example 4: making a simple chapter navigation menu

INTRODUCTION

Simple chapter navigation
You can use WebVTT files in order to define chapters. The syntax is exactly the same as for subtitles/caption .vtt files. The only difference is in the declaration of the track. Here is how we declared a chapter track in one of the previous examples (in bold in the example below):
HTML code:
  1. <video id="myVideo" preload="metadata" controls crossOrigin="anonymous">
  2.      <source src="http://...../elephants-dream-medium.mp4"
  3.              type="video/mp4">
  4.      <source src="http://...../elephants-dream-medium.webm"
  5.              type="video/webm">
  6.      <track label="English subtitles"
  7.             kind="subtitles"
  8.             srclang="en"
  9.             src="http://...../elephants-dream-subtitles-en.vtt" >
  10.      <track label="Deutsch subtitles"
  11.             kind="subtitles"
  12.             srclang="de"
  13.             src="http://...../elephants-dream-subtitles-de.vtt"
  14.             default>
  15.      <track label="English chapters"
  16.             kind="chapters"
  17.             srclang="en"
  18.             src="http://...../elephants-dream-chapters-en.vtt">
  19. </video>
If we try this code in an HTML document, nothing special happens. No magic menu, no extra button!
Currently (as at November 2015), no browser takes chapter tracks into account. You can use one of the enhanced video players presented during the HTML5 Part 1 course, but as you will see in this lesson: making your own chapter navigation menu is not complicated.

FIRST, LET'S HAVE A LOOK AT THE .VTT FILES FROM THIS EXAMPLE:

  1. WEBVTT
  2.  
  3. chapter-1
  4. 00:00:00.000 --> 00:00:26.000
  5. Introduction
  6.  
  7. chapter-2
  8. 00:00:28.206 --> 00:01:02.000
  9. Watch out!
  10.  
  11. chapter-3
  12. 00:01:02.034 --> 00:03:10.000
  13. Let's go
  14.  
  15. chapter-4
  16. 00:03:10.014 --> 00:05:40.000
  17. The machine
  18.  
  19. chapter-5
  20. 00:05:41.208 --> 00:07:26.000
  21. Close your eyes
  22.  
  23. chapter-6
  24. 00:07:27.125 --> 00:08:12.000
  25. There's nothing there
  26.  
  27. chapter-7
  28. 00:08:13.000 --> 00:09:07.500
  29. The Colossus of Rhodes
We've got 7 cues (one for each chapter). Each cue id is chapter- followed by the chapter number, then we have the start and end time of the cue/chapter, and the cue content. In this case: the description of the chapter ("Introduction", "Watch out!", "Let's go", etc...).
    1. We add a "show English chapters" button with a click event listener similar to this :

      1. <button disabled id="buttonEnglishChapters" onclick="loadTranscript('en','chapters');">
      2.     Display English chapter markers
      3. </button>
    2. We modify the loadTranscript function from the previous example, so that it matches both thesrclang and the kind attribute of the track.

      Here is a new version: in bold are the source code lines we modified.

      1. function loadTranscript(lang, kind) {
      2.    ...
      3.    // Locate the track with lang and kind that match the parameters
      4.    for(var i = 0; i < tracks.length; i++) {
      5.       ...
      6.       if((track.language === lang) && (track.kind === kind)) {
      7.          // display it contents...
      8.       }
      9.    }
      10. }

FIRST VERSION : CHAPTERS AS CLICKABLE TEXT ON THE RIGHT OF THE VIDEO

Try it on JSBin; this version includes the modifications we presented earlier - nothing more. Notice that we kept the existing buttons to display a clickable transcript:
Simple chapter navigation
Look at the JavaScript and HTML tab of the JSBin example to see the source code. It's the same as in the clickable transcript example, except for the small changes we explained earlier.
This sort of navigation, illustrated in the video player below, is fairly popular.
Example of video player that uses text based chapter navigation
In addition to the clickable chapter list, this one displays an enhanced progress bar created using a canvas. The small squares are drawn depending on the chapter cues start and end time, etc. You can try to modify the provided example in order to add such an enhanced progress indicator.
However, we will see how we can do better by using JSON objects as cue contents. This is the topic of the two next lessons!

4 comments:

  1. Awesome! I would like to thank you for sharing that with us. So I am trying to use Example 1 in my projecrt, but I need to run the function 'loadTranscript()' from js file, not from Html. I need it runs as soon the page is loaded. I tryed to do some changes, but it only works with the button 'buttonEnglish' or the other.

    Could give some clue about solving that issue?

    Thanks a lot.

    ReplyDelete
  2. Thanks for sharing this informative content crackphilia

    ReplyDelete
  3. i am unable to load transcript on my windows chrome. Mac it was working

    ReplyDelete