In this lesson, we are going to show:
The addTextTrack method for adding a TextTrack to an html <track> element,
The VTTCue constructor, for creating cues programmatically, and
the addCue method for adding cues on the fly to a TextTrack, etc.
These methods will allow us to create TextTrack objects and cues on the fly.
The presented example shows how we can create "sound sprites": small sounds that are parts of a mp3 file, and that can be played separately. Each sound will be defined as a cue in a track associated with the <audio> element.
The demo below, adapted from an original demo by Sam Dutton, uses a single mp3 file that contains recorded animal sounds.
HTML code:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Audio sprites with the track element</title>
<meta charset="UTF-8">
</head>
<body>
<div id="container">
<h1>Playing audio sprites with the track element</h1>
<p>A demo by Sam Dutton, adapted for JsBin by M.Buffa</p>
<div id="soundButtons" class="isSupported"></div>
<h2>How it works</h2>
<p>An Audio object is created for <a href="audio/animalSounds.mp3">animalSounds.mp3</a>, which is made up of multiple different sounds: <em>purr</em>, <em>woof</em>, and so on.</p>
<p>A <code>TextTrack</code> is then constructed with a TextTrackCue added for each sound. Each cue has a startTime, an endTime and an ID.</p>
<p>When a button is clicked, a cue with the same ID as the button is found using the <code>TextTrack</code> <code>getCueById()</code> method. The Audio object <code>currentTime</code> is set to the startTime of the cue. A <code>timeupdate</code> event listener stops play at the <code>endTime</code> of the cue.</p>
<p>This demo is based on a code example in the <a href="http://dev.w3.org/html5/spec/media-elements.html#text-track-api" title="W3C TextTrack API documentation">W3C TextTrack API documentation</a>.</p>
<p>For more information about the track element, take a look at <a href="http://www.html5rocks.com/en/tutorials/track/basics/" title="HTML5 Rocks article: Getting started with the track element">Getting started with the track element</a> on HTML5 Rocks.</p>
</div> <!-- container -->
</body>
</html>
CSS code:
a {
color: #6699ff;
text-decoration: none;
}
a:hover {
color: #88aaff;
text-decoration: underline;
}
body {
background: #666;
font-family: Arial, sans-serif;
padding: 50px;
}
#soundButtons button {
color: green;
font-size: 20px;
height: 67px;
margin: 0 9px 0 0;
padding: 6px 4px 6px 3px;
width: 67px;
}
code {
font-family: Consolas, Courier New, monospace;
font-weight: bold;
font-size: 20px;
}
div#container {
background: #000;
margin: 0 auto 0 auto;
max-width: 685px;
padding: 20px 30px 30px 30px;
}
div#soundButtons {
border-bottom: 1px solid green;
height: 67px;
padding: 0 0 30px 0;
}
div#warningMessage {
background: #333;
border: 1px dashed #ffff00;
padding: 10px 10px 10px 20px;
}
h1 {
border-bottom: 1px solid green;
color: white;
font-family: Arial, sans-serif;
margin: 0 0 30px 0;
padding: 0 0 10px 0;
}
h2 {
color: #ccc;
font-family: Arial, sans-serif;
margin: 30px 0 0 0;
}
img {
border: none;
}
p {
color: #ddd;
font-size: 18px;
line-height: 1.5em;
}
div#warningMessage p {
color: #dddd00;
font-size: 14px;
font-weight: bold;
}
pre {
color: white;
}
.hidden {
display: none;
margin: 0 !important;
max-height: 0;
opacity: 0;
padding: 0 !important;
}
.visible {
display: block;
max-height: inherit;
opacity: 1;
}
JS code:
// This demo is based on code at http://dev.w3.org/html5/spec/media-elements.html#text-track-api
window.onload = function() {
let audio = new Audio("https://mainline.i3s.unice.fr/mooc/animalSounds.mp3");
audio.addEventListener("loadedmetadata", function() {
let track = audio.addTextTrack("metadata", "sprite track", "en");
track.mode = "hidden";
// for browsers that do not implement the getCueById() method
if (typeof track.getCueById !== "function") {
track.getCueById = function(id) {
let cues = track.cues;
for (let i = 0; i != track.cues.length; ++i) {
if (cues[i].id === id) {
return cues[i];
}
}
};
}
let sounds = [{
id: "purr",
startTime: 0.200,
endTime: 1.800
}, {
id: "meow",
startTime: 2.300,
endTime: 3.300
}, {
id: "bark",
startTime: 3.900,
endTime: 4.300
}, {
id: "baa",
startTime: 5.000,
endTime: 5.800
}, {
id: "moo",
startTime: 6.500,
endTime: 8.200
}, {
id: "bleat",
startTime: 8.500,
endTime: 9.400
}, {
id: "woof",
startTime: 9.900,
endTime: 10.400
}, {
id: "cluck",
startTime: 11.100,
endTime: 13.400
}, {
id: "mew",
startTime: 13.800,
endTime: 15.600
}];
for (let i = 0; i !== sounds.length; ++i) {
let sound = sounds[i];
let cue = new VTTCue(sound.startTime, sound.endTime, sound.id); // change in spec
cue.id = sound.id;
track.addCue(cue);
document.querySelector("#soundButtons").innerHTML += "<button class='playSound' id=" + sound.id + ">" + sound.id + "</button>";
}
let endTime;
audio.addEventListener("timeupdate", (event) => {
if (event.target.currentTime > endTime)
event.target.pause();
});
function playSound(id) {
let cue = track.getCueById(id);
audio.currentTime = cue.startTime;
endTime = cue.endTime;
audio.play();
}
let buttons = document.querySelectorAll("button.playSound");
for(i = 0; i < buttons.length; i++) {
buttons[i].addEventListener("click", (e) => {
playSound(e.target.id);
});
}
});
};
function addListenerToButton(i) {
}
Below is the sound file. You can try to play it:
The demo uses a JavaScript array for defining the different animal sounds in this audio file:
var sounds = [
{
id: "purr",
startTime: 0.200,
endTime: 1.800
},
{
id: "meow",
startTime: 2.300,
endTime: 3.300
},
{
id: "bark",
startTime: 3.900,
endTime: 4.300
},
{
id: "baa",
startTime: 5.000,
endTime: 5.800
}
...
];
The idea is to create a track on the fly, then add cues within this track. Each cue will be created with the id, the start and end time taken from the above JavaScript object. In the end, we will have a track with individual cues located at the time location where an animal sound is in the mp3 file.
Then we generate buttons in the HTML document, and when the user clicks on a button, the getCueById method is called, then the start and end time properties of the cue are accessed and the sound is played (using the currentTime property of the audio element).
Polyfill for getCueById: Note that this method is not available on all browsers yet. A simple polyfill is used in the examples presented. If the getCueById method is not implemented (this is the case in some browsers), it's easy to use this small polyfill:
// for browsers that do not implement the getCueById() method
// let's assume we're adding the getCueById function to a TextTrack object
//named "track"
if (typeof track.getCueById !== "function") {
track.getCueById = function(id) {
var cues = track.cues;
for (var i = 0; i != track.cues.length; ++i) {
if (cues[i].id === id) {
return cues[i];
}
}
};
}
To add a TextTrack to a track element, use the addTextTrack method (of the audio or video element). The function's signature is addTextTrack(kind[,label[,language]]) where kind is our familiar choice between subtitles, captions, chapters, etc. The optional label is any text you'd like to use describing the track; and the optional language is from our usual list of BCP-47 abbreviations, eg 'de', 'en', 'es', 'fr' (etc).
The VTTCue constructor enables us to create our own cue class-instances programmatically. We create a cue instance by using the new keyword. The constructor function expects three familiar arguments, thus: new VTTCue(startTime, endTime, id) - more detail is available from the MDN and the W3C's two applicable groups.
To add cue-instances to a TextTrack on-the-fly, use the track object's addCue method, eg track.addCue(cue). The argument is a cue instance - as above. Note that the track must be a TextTrack object because addCue does not work with HTMLTrackElement Objects.
HTML source code extract:
...
<h1>Playing audio sprites with the track element</h1>
<p>A demo by Sam Dutton, adapted for JsBin by M.Buffa</p>
<div id="soundButtons" class="isSupported"></div>
...
window.onload = function() {
// Create an audio element programmatically
var audio = newAudio("https://mainline.i3s.unice.fr/mooc/animalSounds.mp3");
audio.addEventListener("loadedmetadata", function() {
// When the audio file has its metadata loaded, we can add
// a new track to it, with mode = hidden. It will fire events
// even if it is hidden
var track = audio.addTextTrack("metadata", "sprite track", "en");
track.mode = "hidden";
// for browsers that do not implement the getCueById() method
if (typeof track.getCueById !== "function") {
track.getCueById = function(id) {
var cues = track.cues;
for (var i = 0; i != track.cues.length; ++i) {
if (cues[i].id === id) {
return cues[i];
}
}
};
}
var sounds = [
{
id: "purr",
startTime: 0.200,
endTime: 1.800
},
{
id: "meow",
startTime: 2.300,
endTime: 3.300
},
...
];
for (var i = 0; i !== sounds.length; ++i) {
// for each animal sound, create a cue with id, start and end time
var sound = sounds[i];
var cue = new VTTCue(sound.startTime, sound.endTime, sound.id);
cue.id = sound.id;
// add it to the track
track.addCue(cue);
// create a button and add it to the HTML document
document.querySelector("#soundButtons").innerHTML +=
"<button class='playSound' id="
+ sound.id + ">" +sound.id
+ "</button>";
}
var endTime;
audio.addEventListener("timeupdate", function(event) {
// When we play a sound, we set the endtime var.
// We need to listen when the audio file is being played,
// in order to pause it when endTime is reached.
if (event.target.currentTime > endTime)
event.target.pause();
});
function playSound(id) {
// Plays the sound corresponding to the cue with id equal
// to the one passed as a parameter. We set the endTime var
// and position the audio currentTime at the start time
// of the sound
var cue = track.getCueById(id);
audio.currentTime = cue.startTime;
endTime = cue.endTime;
audio.play();
};
// create listeners for all buttons
var buttons = document.querySelectorAll("button.playSound");
for(var i=; i < buttons.length; i++) {
buttons[i].addEventListener("click", function(e) {
playSound(this.id);
});
}
});
};